mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-04-27 13:24:47 -04:00
37752 lines
1.3 MiB
37752 lines
1.3 MiB
<!DOCTYPE html>
|
||
<html style="height:100%">
|
||
<head>
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||
<meta name="format-detection" content="telephone=no">
|
||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo="> <!-- Stop favicon.ico from loading -->
|
||
<style>body {
|
||
height: 100%;
|
||
max-height: 100%;
|
||
overflow: hidden;
|
||
font-family: arial, helvetica, sans-serif;
|
||
font-size: 9pt;
|
||
color: black;
|
||
background: white;
|
||
margin-top: 0;
|
||
margin-left: 0;
|
||
margin-right: 0;
|
||
-webkit-touch-callout: none;
|
||
-webkit-user-select: none;
|
||
-khtml-user-select: none;
|
||
-moz-user-select: none;
|
||
-ms-user-select: none;
|
||
user-select: none;
|
||
}
|
||
|
||
li {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
color: windowtext;
|
||
background-color: window;
|
||
margin: 0;
|
||
padding: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
label:hover {
|
||
background-color: highlight;
|
||
color: highlighttext;
|
||
}
|
||
|
||
a:visited {
|
||
text-decoration: none;
|
||
color: #04f;
|
||
}
|
||
|
||
a:link {
|
||
text-decoration: none;
|
||
color: #04f;
|
||
}
|
||
|
||
a:hover {
|
||
color: #a32;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 11pt;
|
||
font-weight: bold;
|
||
color: black;
|
||
margin-left: 5px;
|
||
margin-top: 10px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
h2 {
|
||
font-size: 9pt;
|
||
font-weight: bold;
|
||
color: black;
|
||
margin-left: 6px;
|
||
margin-top: 6px;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
p {
|
||
margin-left: 6px;
|
||
margin-top: 4px;
|
||
margin-bottom: 0;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
td {
|
||
font-size: 9pt;
|
||
}
|
||
|
||
th {
|
||
font-size: 9pt;
|
||
}
|
||
|
||
th:hover {
|
||
cursor: pointer;
|
||
background: #aaa;
|
||
}
|
||
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 24px;
|
||
background: #c0c0c0;
|
||
}
|
||
|
||
.progressbar {
|
||
position: fixed;
|
||
top: 24px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: #ff9e30;
|
||
}
|
||
|
||
.in {
|
||
margin-left: 40px;
|
||
}
|
||
|
||
.log {
|
||
background: #bbbab5;
|
||
}
|
||
|
||
.log1 {
|
||
background: #bbbab5;
|
||
}
|
||
|
||
.log tbody tr:nth-child(odd) {
|
||
background: #e8eefe;
|
||
}
|
||
|
||
.fullcell {
|
||
position: fixed;
|
||
top: 26px;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 0px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.maincell {
|
||
position: fixed;
|
||
top: 26px;
|
||
right: 0;
|
||
bottom: 0;
|
||
left: 156px;
|
||
overflow: auto;
|
||
padding-left: 2px;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.navbar {
|
||
position: fixed;
|
||
top: 26px;
|
||
left: 0;
|
||
bottom: 0;
|
||
width: 156px;
|
||
border-right: 2px solid #ff9e30;
|
||
vertical-align: top;
|
||
background: #72726f;
|
||
background: linear-gradient(45deg, #72726f 0%,#a6a5a0 100%);
|
||
}
|
||
|
||
.nav1 {
|
||
padding: 1px 0px 1px 8px;
|
||
margin: 0px;
|
||
font-weight: bold;
|
||
color: black;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.nav2 {
|
||
margin-left: 32px;
|
||
margin-top: 0;
|
||
color: black;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.r {
|
||
font-size: 11pt;
|
||
}
|
||
|
||
.r0 {
|
||
background: white;
|
||
}
|
||
|
||
.r1 {
|
||
border-bottom: 1px solid gray;
|
||
text-align: left;
|
||
}
|
||
|
||
.r2 {
|
||
text-align: left;
|
||
}
|
||
|
||
.r3 {
|
||
border-bottom: 1px solid gray;
|
||
text-align: left;
|
||
}
|
||
|
||
.r3:hover {
|
||
background-color: #83827b;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.spread {
|
||
height: 100%;
|
||
width: 100%;
|
||
background-color: white;
|
||
}
|
||
|
||
.timer {
|
||
border: 1px solid #abcae1;
|
||
background-color: #abcae1;
|
||
}
|
||
|
||
.tm {
|
||
font-size: 7pt;
|
||
}
|
||
|
||
.top1 {
|
||
font-size: 14pt;
|
||
font-weight: bold;
|
||
color: white;
|
||
margin-top: 11px;
|
||
}
|
||
|
||
.top2 {
|
||
color: white;
|
||
}
|
||
|
||
.warn {
|
||
font-weight: bold;
|
||
color: #c00000;
|
||
}
|
||
|
||
.icon1 {
|
||
width: 14px;
|
||
height: 15px;
|
||
background-repeat: no-repeat;
|
||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMISURBVHjadJPPb9t0GMY//rbJaEKSxoPFQQ1WxhQ7RaGoIDEpTBMqgk7iBvwNKJdJXDnssEMl7pMGfwOH9YBYh0AglYiBBNKa0cSGNpiRxs7aJE2aH7aTmkPXqTvwXt7L8z56pef5SEEQcHYMwyjWarX3rLr1tu3YOQAlpZhqVv1J1/VvNU0rn9VLpwbtdlspb5ZLNdNcuZBKF1NKmuS8DECn28axm7ScZlnP5b4vXinelmXZfmrQbreVjbsbN3r9YSmTLbA/Ps92K8FgFAbAdYcsLXRRk10af/9BPBa5vXpt9aYsy7YAKG+WS73+sJS+eJmKneZe5QWEiHHreoJb1xNEInHu/Bzw3cM45zNv0usPS+XNcglAGIZRrJnmSiZbYOtRhF93ZvHcHoPRlN3GkL4Ho5HHeOxRqbv8sHVM+uVFaqa5YhhGUVpfXw+6hwP86DJ3fgkzK44hOGZuLkH7KIQIxuw9dkk812Hqu0wmPh9djZIKG8wnogirbpFS0jy0YOqPcMcurjvh3Vc7fPHpHF9//iLLyja75p/sNWxazj73t9qklDRW3ULYjk1yXmb3Xw/P9fA9n35/AMD0yAIgGQ8zOtxnMhFM/ClG/YjkvIzt2Mye5un5Lr4HY9el5RwA2tOsY9FzCCFBICCYOdlPRigphU63jZKEwWDAweMDJt7kmXINBhOkIEAQQiLMpczzJzcpBaFmVRy7iZ4RdA/7TKcBknTymOd5APT6PiAhghlmpHO8ocdw7CZqVkXour7Ycppriwsuy7koEgKQANDy+dPCIokQkhTiciFG4aJPy2mu6bq+OKtpWlXPmZWGtc2H77yOhOD+gxE7//T45sdHzEjQ7Y0IgmPeKszxwdUwTuM39Fyuomla9bTKyY27Gx/3+sMvlYU8lXqI36sef+1UkCS4pMZZyuu89oqL09gmHot8snpt9StZljtnYUqWN8vv10yzcCGV/ux/YFrTc7lK8UrxnizLnWdoPINzvlarLVl1K2879ktPcN5Ts2pV1/UHmqZVz+r/GwBWYYCoNUz0KwAAAABJRU5ErkJggg==");
|
||
}
|
||
|
||
.icon2 {
|
||
width: 14px;
|
||
height: 15px;
|
||
background-repeat: no-repeat;
|
||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAIuSURBVHjalJNBTBNBFIb/2S4tlliFdLBNW0mktibaeivBm9eejFeDZw81kROXHj2SyIEDB65wI3iBI5iYmEjSREtIpI1tQdOm20KBdkuX3ZnnYUVq2Cb6kklm3vxv3vdm3oCI4DTazZ3p3HpM5NZjot3cmR6kY0QEJ9vdnNJcaoMP3xyB3vI2EunP4046xcl5WttK99olHn4URSA2AaNT5qe1rbST1pFgd3NKU93H/P6TJADg8GthIMU1gqODtZlep8QjiegfXzBuUxwdrM1cy9Z/IVIYSn4jpe1vx8jsviAABIBM/Tl9/5Sg/EZKk8JQ+mP+ImiUVjJGp8xDDycB2b1KIgSC0RCMTpk3SisZxxKkpXvqhaXs7aAfnhtukBBXKmFCHVLgj4yjXljKSkv3XDugXlyeM/QKDz24CxIWSJhXBKYJMk2MT3AYeoXXi8tzl3sqAFgXLV+zvJrxhzkUqwdxQQARZl+FbbqeXY7CGHjEj2Z5NcMnXy6o7tEzRkT4mX/7Tisuvok/joBJC5ACl8/LmAIov0EVBZIYvn35gUD89UI4mZ1lRrca2N9+lh8bPeG3RgCyTEBKgAi+1CEAoJ27B6gqGGMAY2idCbRORhvxp++TanVvviaMQ/g8XshzAxACIIn+/pLGOWAqYIoLAODzuKB1K7y6N19j+Y0U3fG3MCw6IMsCyM4Oxvob1l4ze84YQ5u8OD7jUNWhMXz4uI//tx4Syfjg3/iv9msAKbs79bi84QcAAAAASUVORK5CYII=");
|
||
}
|
||
|
||
.icon3 {
|
||
width: 14px;
|
||
height: 15px;
|
||
background-repeat: no-repeat;
|
||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAANGSURBVHjadJNNTBwFAIW//UFWfrbsgDuzBTvu0s6OWhHwYMhCOEAbSESC4dpL07TFg9GkxIRDjT3YhEBNPUjC0SbaJjVNQyJQaxtDpzYmlDYUOjuUrhspO0N0gQVWl/0ZD5ZmPfiSd3t5l/c+h23bFCsajR7Rdf1GPBbHtEwAJFFCDsqoqno0HA7/WJx37BUkk8k6bUYb0g2jxy8G6kQpgK9KAGB9I4llJlizEiuqokxE2iJfCIKw8qIgmUzWTU1OfZXaSve9KQep3tzA9fQJhfhvZHd3yfl85OUg26F6os9W8FaWXevq7vpIEIQVJ4A2ow2lttJ9LTU1hH64Tt1qHHv8a0rnH/LS4iPsK9/iixnUXr1Ck3cfqa10nzajDQG4o9HoEd0wet493Ij/zm18n3yMJxQivbND7Px58thUD3xI+egoBcMgd+FLlO73mTUWe5Ro9Jqrs7Nz2eUu9YbSacriT8lms5Q3NlLR3s6uaVJQFISRC1SUuFm9dInJ6WlCfpGMFPBub6eOueOxOMobb+H4+Ta/f3cZz927FPJ5hOPHqRkZgT/+xFniIjY6yveDg0gOB9l6BbHpBMbiPE7TMvFVCWSXl3DU7secnWXu9GniFy+ymctRIfhYHxvj6pkzBG2bA0Du8SN8VQKmZeLc2zOXybA2P8+mbeNpbydTWUmJ203etvGIInV+PxVA9t/pXvzALYkS6xtJqoRq/rJtqoNB/KdO8Up/P+atWxSA8t5e3vN6mT55kgOrq5TUH2J9I4kkSjjloIxlJiiEDiK2tREYHkbq7yd57x6XOzr4paMD59IS+7q7+WB8nPtuNyVN72CZCeSgjFNV1dY1K6H9fbiBDWk/uwsL5G7e5JuWFhTgVUBrbmZX11memyMgv0a64W3WrISmqmqrw7ZtJq5PfP4sYZ0Nl5ayPjJM4fECKdtGAF5+bt3pxBt+HWXwU37NZKgNiOd6ens+27uyNDU5dTa1lR44JAXwPHiA/fA++ScGTsB1UMHV1MxOQyOGmcBbWTbW1d11ThAEsxgmSZvRBnTD6PCLgcj/wKSpivJTpC0yJgiC+R8ai3CO6Lp+NB6Lt5qWqTzH2ZCD8h1VVW+Ew2GtOP/PAFZGexs+cGPjAAAAAElFTkSuQmCC");
|
||
}
|
||
|
||
.itemBar {
|
||
padding: 7px;
|
||
min-height: 20px;
|
||
margin-top: 4px;
|
||
margin-right: 8px;
|
||
width: auto;
|
||
border-radius: 8px;
|
||
background-color: #7e7d74;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.computeritem {
|
||
cursor: pointer;
|
||
width: auto;
|
||
border-radius: 5px;
|
||
background-color: #a6a5a0;
|
||
height: 28px;
|
||
margin: 4px;
|
||
padding: 2px;
|
||
}
|
||
|
||
.computeritem:hover {
|
||
background-color: #83827b;
|
||
}
|
||
|
||
.us {
|
||
-webkit-touch-callout: initial;
|
||
-webkit-user-select: auto;
|
||
-khtml-user-select: text;
|
||
-moz-user-select: text;
|
||
-ms-user-select: text;
|
||
user-select: text;
|
||
}
|
||
</style>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</head>
|
||
<body onunload="cleanup()">
|
||
<div id="id_topheader" class="header">
|
||
<table id="id_KVMTable" cellpadding="0" cellspacing="0" style="width: 100%; padding: 0px; padding: 0px; margin-top: 0px">
|
||
<tr>
|
||
<td id="id_normalScreenToolbar" class="style6">
|
||
<div>
|
||
<input type="button" class="connectbutton" id="xconnectbutton1" value="Connect" onclick="connectButtonfunction(event, false)" onkeypress="return false" onkeydown="return false">
|
||
<span id="constatus"></span>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<div class="progressbar">
|
||
<div id="id_progressbar" style="height:2px;width:0%;background-color:red"></div>
|
||
</div>
|
||
</div>
|
||
<div id="id_messageview" class="fullcell" style="text-align:center;padding-top:100px;font-size:20px">
|
||
<span id="id_messageviewstr">Disconnected</span>
|
||
</div>
|
||
<div id="id_mainview" style="height:100%;display:none">
|
||
<div id="id_leftbar" class="navbar">
|
||
<br>
|
||
<p id="go1" class="nav1" onclick="go(1)"><a>System Status</a></p>
|
||
<p id="go2" class="nav1" onclick="go(2)"><a>Hardware Information</a></p>
|
||
<p id="go6" class="nav1" onclick="go(6)"><a>Event Log</a></p>
|
||
<p id="go15" class="nav1" onclick="go(15)"><a>Audit Log</a></p>
|
||
<p id="go21" class="nav1" onclick="go(21)"><a>Storage</a></p>
|
||
<p id="go8" class="nav1" onclick="go(8)"><a>Network Settings</a></p>
|
||
<p id="go17" class="nav1" onclick="go(17)"><a>Internet Settings</a></p>
|
||
<p id="go16" class="nav1" onclick="go(16)"><a>Security Settings</a></p>
|
||
<p id="go19" class="nav1" onclick="go(19)"><a>Agent Presence</a></p>
|
||
<p id="go18" class="nav1" onclick="go(18)"><a>System Defense</a></p>
|
||
<p id="go11" class="nav1" onclick="go(11)"><a>User Accounts</a></p>
|
||
<p id="go22" class="nav1" onclick="go(22)"><a>Subscriptions</a></p>
|
||
<p id="go23" class="nav1" onclick="go(23)"><a>Wake Alarms</a></p>
|
||
<p id="go20" class="nav1" onclick="go(20)"><a>Script Editor</a></p>
|
||
<p id="go12" class="nav1" onclick="go(12)"><a>WSMAN Browser</a></p>
|
||
<div id="storagelinks" style="margin-top:4px"></div>
|
||
</div>
|
||
<div id="id_mainarea" class="maincell">
|
||
<div id="id_scriptstatus" style="height:21px;background:#8fac8d;padding:5px;margin-bottom:1px;display:none;overflow:hidden">
|
||
<div style="float:right"><input type="button" value="Stop Script" onclick="script_Stop()"></div>
|
||
<div style="font-size:16px;padding-top:2px;overflow:hidden"> <b>Running Script</b><span style="overflow:hidden" id="id_scriptstatusstr"></span></div>
|
||
</div>
|
||
<div id="id_versionWarning" style="height:21px;background:#8fac8d;padding:5px;margin-bottom:1px;display:none">
|
||
<div style="font-size:16px;float:right;cursor:pointer;padding-right:5px;padding-left:5px;padding-top:2px" onclick="QV('id_versionWarning', false)"><b>X</b></div>
|
||
<div style="font-size:14px;padding-top:2px"> <b>This computer's firmware should be updated, <a style="cursor:pointer" onclick="require('nw.gui').Shell.openExternal('https://security-center.intel.com/advisory.aspx?intelid=INTEL-SA-00075&languageid=en-fr')"><u>please check here</u></a>.</b></div>
|
||
</div>
|
||
<div id="id_mainarea_frame" style="width:100%;height:100%">
|
||
<iframe id="id_StorageIFrame" style="width:100%;height:100%;border:0"></iframe>
|
||
</div>
|
||
<div id="id_mainarea_pad" style="padding:8px;overflow-x:hidden">
|
||
<div id="p0">
|
||
<h1>Loading...</h1>
|
||
</div>
|
||
<div id="p1" style="display:none">
|
||
<h1>System Status</h1>
|
||
<span id="id_TableSysStatus"></span>
|
||
</div>
|
||
<div id="p2" style="display:none">
|
||
<h1 style="margin-bottom:16px">Hardware Information</h1>
|
||
<span id="id_TableSysInfo"></span>
|
||
</div>
|
||
<div id="p6" style="display:none">
|
||
<h1>Event Log</h1>
|
||
<span id="id_TableEventLog"></span>
|
||
<span id="id_TableEventLog2"></span>
|
||
</div>
|
||
<div id="p8" style="display:none">
|
||
<h1>Network Settings</h1>
|
||
<span id="id_TableNetworkSettingsSpan"></span>
|
||
<span id="id_TableWifi2"></span>
|
||
</div>
|
||
<div id="p11" style="display:none">
|
||
<h1>User Accounts</h1>
|
||
<span id="id_TableUserAccounts"></span>
|
||
</div>
|
||
<div id="p12" style="display:none">
|
||
<h1>WSMAN Browser</h1>
|
||
<div>
|
||
<table class="log1" cellpadding="0" cellspacing="0" style='width:100%;border-radius:8px'>
|
||
<tr>
|
||
<td>
|
||
<div style="padding:4px">
|
||
<select id="id_QuerySelect" multiple="multiple" style="width:100%;height:120px"></select>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<input id="id_p12querybutton" type="button" value="Query" style='margin: 4px' onclick="wsmanQuery()">
|
||
<input type="button" value="Clear" style='margin: 4px' onclick="QH('id_wsresults', '')">
|
||
<input id="idx_browserFilter" placeholder="Filter" style='margin: 4px' onkeyup="wsmanFilter()">
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
<br>
|
||
<div class="us" id="id_wsresults"></div>
|
||
</div>
|
||
<div id="p15" style="display:none">
|
||
<span id="id_TableAuditLog1"></span>
|
||
<h1>Audit Log</h1>
|
||
<span id="id_TableAuditLog2"></span>
|
||
</div>
|
||
<div id="p16" style="display:none">
|
||
<h1>Security Settings</h1>
|
||
<span id="id_TableCerts"></span>
|
||
</div>
|
||
<div id="p17" style="display:none">
|
||
<h1>Internet Settings</h1>
|
||
<span id="id_TableRemoteAccess"></span>
|
||
</div>
|
||
<div id="p18" style="display:none">
|
||
<h1>System Defense</h1>
|
||
<span id="id_TableSystemDefense"></span>
|
||
</div>
|
||
<div id="p19" style="display:none">
|
||
<h1>Agent Presence</h1>
|
||
<span id="id_TableSystemAgentPresence"></span>
|
||
</div>
|
||
<div id="p20" style="display:none">
|
||
<h1>Script Editor</h1>
|
||
<div class="log1" style="padding:5px;border-radius:5px">
|
||
<div id='EditScriptStatus' style="float:right;font-weight:bold;padding:5px">Stopped</div>
|
||
<div>
|
||
<input type="button" value="View Editor" title="Switch to script line editor view" id="viewEditorButton" onclick="scriptViewButton(0)">
|
||
<input type="button" value="View Builder" title="Switch to block editor view" id="viewBuilderButton" onclick="scriptViewButton(1)">
|
||
<input type="button" value="New..." title="Clear the script editor" onclick="script_newScriptDlg()">
|
||
<input type="button" value="Load..." title="Load a script from file" onclick="script_runScriptDlg()">
|
||
<input type="button" value="Save..." title="Save a script to file" onclick="script_saveScript(event)">
|
||
<input type="button" value="Restart" title="Compile the script and get ready to run it from the start" onclick="resetScriptButton()">
|
||
<input type="button" value="Continue" title="Run the script from the current execution point" onclick="runScriptButton()">
|
||
<input type="button" value="Break" title="Pause the execution of the script" onclick="breakScriptButton()">
|
||
<input type="button" value="Step" title="Execute one step of the script" onclick="stepScriptButton()">
|
||
</div>
|
||
</div>
|
||
<div id="scriptbuilder" style="display:none">
|
||
<h2>Script Builder</h2>
|
||
<div style="padding:0;margin:0">
|
||
<div style="width:250px;height:400px;float:left;padding:0;margin:0;padding-right:3px">
|
||
<input id="blockfilter" style="width:inherit;height:24px;padding:0;margin:0;border:1px solid gray;margin-bottom:1px" placeholder="Filter blocks..." onkeyup="script_fonfilterchanged()">
|
||
<div id="blocks" style="width:inherit;height:373px;border:1px solid gray;overflow-y:scroll;padding:0;margin:0"></div>
|
||
</div>
|
||
<div id="scriptblocks" style="width:auto;height:400px;padding:0;margin:0;border:1px solid gray;overflow-y:scroll" ondrop="script_fondrop(event, this)" onclick="script_fonclick(event)"></div>
|
||
</div>
|
||
</div>
|
||
<div id="scripteditor">
|
||
<h2>Script</h2>
|
||
<textarea id="scriptarea" style="width:100%;height:176px;resize:vertical;margin:0;padding:0;font-family:Arial,Helvetica,sans-serif" spellcheck="false"></textarea>
|
||
<div style="display:none">
|
||
<br><h2>Compiled Script</h2>
|
||
<textarea id="compiledarea" style="width:100%;height:16px;resize:vertical;margin:0;padding:0" spellcheck="false"></textarea>
|
||
<br>
|
||
</div>
|
||
<h2>Variables</h2>
|
||
<div id="variables" style="width:100%;height:200px;resize:vertical;border:1px solid gray;overflow:scroll;margin:0;padding:0;user-select:text;-webkit-user-select:text;-khtml-user-select:text;-moz-user-select:text;-ms-user-select:text"></div>
|
||
</div>
|
||
<h2>Console</h2>
|
||
<textarea id="console" style="width:100%;height:80px;resize:vertical;margin:0;padding:0;user-select:text;-webkit-user-select:text;-khtml-user-select:text;-moz-user-select:text;-ms-user-select:text" readonly=""></textarea>
|
||
</div>
|
||
<div id="p21" style="display:none">
|
||
<h1>Storage</h1>
|
||
<span id="id_TableSystemStorage"></span>
|
||
</div>
|
||
<div id="p22" style="display:none">
|
||
<h1>Event Subscriptions</h1>
|
||
<span id="id_TableEventSubscriptions"></span>
|
||
</div>
|
||
<div id="p23" style="display:none">
|
||
<h1>Wake Alarms</h1>
|
||
<span id="id_TableAlarm"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="dialog" style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial, Helvetica, sans-serif;border-radius:5px;position:fixed;overflow:auto;top:75px;width:400px;max-height:550px;display:none">
|
||
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
|
||
<div id='id_dialogclose' style="float:right;padding:5px;cursor:pointer" onclick="setDialogMode()"><b>X</b></div>
|
||
<div id='id_dialogtitle' style="padding:5px"></div>
|
||
<div style="width:100%;margin:6px"></div>
|
||
</div>
|
||
<div style="margin-right:16px;margin-left:8px">
|
||
<div id="dialog1" style="margin:auto;text-align:center;margin:3px">
|
||
<div id="id_dialogMessage" style="padding:10px"></div>
|
||
</div>
|
||
<div id="dialog2" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style='height:26px'><input id="d2username" style="float:right;width:200px" onkeyup="updateAccountDialog()"><div>Username</div></div>
|
||
<div style='height:26px'><input id="d2password1" type="password" autocomplete="off" style="float:right;width:200px" onkeyup="updateAccountDialog()"><div>Password*</div></div>
|
||
<div style='height:26px'><input id="d2password2" type="password" autocomplete="off" style="float:right;width:200px" onkeyup="updateAccountDialog()"><div>Confirm Password</div></div>
|
||
<div id="id_d2permissions">
|
||
<div style='height:26px'><select id="d2permission" style="float:right;width:200px"><option value="0">Local<option value="1">Network<option value="2">Any</select><div>Permission</div></div>
|
||
<div>Granted Permissions</div>
|
||
<ul id="id_d2realms" style="list-style-type:none;height:100px;overflow:auto;width:100%;border: 1px solid #000;background-color:white;overflow-x:hidden;margin:0;padding:0"></ul>
|
||
</div>
|
||
<div style="font-size:10px"><br>*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-</div>
|
||
</div>
|
||
<div id="dialog5" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style='height:26px'>
|
||
<select id="d5actionSelect" style="float:right;width:200px"></select>
|
||
<div>Power Action</div>
|
||
</div>
|
||
<div>
|
||
<span style="color:red">Warning:</span> Some power actions may result in data loss and may disconnect the desktop, terminal or disk redirection sessions.
|
||
</div>
|
||
</div>
|
||
<div id="dialog6" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style='height:26px'><input id="d6ConsentText" style="float:right;width:200px" maxlength="6" onkeyup="consentChanged()" onkeypress="return numbersOnly(event)"><div>Consent Code</div></div>
|
||
<div style='height:26px'><select id="d6Display" onchange="changeConsentDisplay()" style="float:right;width:200px"><option value="0">Primary display<option value="1">Secondary display<option id="d6ThirdDisplay" value="2" style="display:none">Third display</select><div>Consent Display</div></div>
|
||
</div>
|
||
<div id="dialog8" style="display:table;margin:3px">
|
||
<div style="margin:3px 0 3px 0;padding-top:5px">
|
||
<input id="idx_d8username" value="admin" style="float:right;width:220px">
|
||
<div style="height:20px">Username</div>
|
||
</div>
|
||
<div style="margin:3px 0 3px 0">
|
||
<input id="idx_d8password" type="password" autocomplete="off" style="float:right;width:220px">
|
||
<div style="height:20px">Password</div>
|
||
</div>
|
||
</div>
|
||
<div id="dialog9" style="margin:auto;margin:3px">
|
||
<input type="checkbox" id='idx_d9redir'>Redirection Port<br>
|
||
<div id="idx_d9kvm_div"><input type="checkbox" id='idx_d9kvm'>KVM Remote Desktop<br></div>
|
||
<input type="checkbox" id='idx_d9ider'>IDE-Redirection<br>
|
||
<input type="checkbox" id='idx_d9sol'>Serial-over-LAN<br>
|
||
</div>
|
||
<div id="dialog10" style="margin:auto;margin:3px">
|
||
<input type="radio" name="d10" id='idx_d10none' value="0">Not Required<br>
|
||
<input type="radio" name="d10" id='idx_d10kvm' value="1">Required for KVM only<br>
|
||
<input type="radio" name="d10" id='idx_d10all' value="4294967295">Always Required<br>
|
||
</div>
|
||
<div id="dialog11" style="margin:auto;margin:3px">
|
||
<div id="id_dialogOptions"></div>
|
||
</div>
|
||
<div id="dialog12" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style='height:26px'><input id="idx_d12name" style="float:right;width:200px" maxlength="32" onkeyup="updateWifiDialog()" title="Maximum 32 characters"><div title="Maximum 32 characters">Profile Name</div></div>
|
||
<div style='height:26px'><input id="idx_d12ssid" style="float:right;width:200px" maxlength="32" onkeyup="updateWifiDialog()" title="Maximum 32 characters"><div title="Maximum 32 characters">SSID</div></div>
|
||
<div style='height:26px'>
|
||
<select id="idx_d12pri" style="float:right;width:200px" onclick="updateWifiDialog()"></select>
|
||
<div>Priority</div>
|
||
</div>
|
||
<div style='height:26px'>
|
||
<select id="idx_d12auth" style="float:right;width:200px" onclick="updateWifiDialog()">
|
||
<!--<option value="7">WPA2 IEEE 802.1x</option>-->
|
||
<option value="6">WPA2 PSK
|
||
<!--<option value="5">WPA IEEE 802.1x</option>-->
|
||
<option value="4">WPA PSK
|
||
<!--<option value="3">Shared Key</option>-->
|
||
<!--<option value="2">Open</option>-->
|
||
</select>
|
||
<div>Authentication</div>
|
||
</div>
|
||
<div style='height:26px'>
|
||
<select id="idx_d12enc" style="float:right;width:200px" onclick="updateWifiDialog()">
|
||
<option id="id_d12e4" value="4">CCMP-AES
|
||
<option id="id_d12e3" value="3">TKIP-RC4
|
||
<option id="id_d12e2" value="2">WEP
|
||
<option id="id_d12e5" value="5">None
|
||
</select>
|
||
<div>Encryption</div>
|
||
</div>
|
||
<div style='height:26px'><input id="idx_d12password1" type="password" style="float:right;width:200px" maxlength="63" onkeyup="updateWifiDialog()" title="Length between 8 and 63 characters"><div title="Length between 8 and 63 characters">Password*</div></div>
|
||
<div style='height:26px'><input id="idx_d12password2" type="password" style="float:right;width:200px" maxlength="63" onkeyup="updateWifiDialog()" title="Length between 8 and 63 characters"><div title="Length between 8 and 63 characters">Confirm Password</div></div>
|
||
</div>
|
||
<div id="dialog19" style="margin:auto;margin:3px">
|
||
This will save the entire state of Intel® AMT for this machine into file. Passwords will not be saved, but some sensitive data may be included.<br><br>
|
||
<input id="idx_d19savestatefilename" style="width:100%" value="amtstate.json">
|
||
</div>
|
||
<div id="dialog20" style="margin:auto;margin:3px">
|
||
<input type="radio" name="d20" id="d20a" value="0">Disabled<br>
|
||
<input type="radio" name="d20" id="d20b" value="1">ICMP response<br>
|
||
<input type="radio" name="d20" id="d20c" value="2">RMCP response<br>
|
||
<input type="radio" name="d20" id="d20d" value="3">ICMP & RMCP response<br><br>
|
||
</div>
|
||
<div id="dialog21" style="margin:auto;margin:3px">
|
||
<input type="radio" name="d21" id="d21o0" onclick="updateIPSetupDlg()"><span id="d21l0"></span><br>
|
||
<input type="radio" name="d21" id="d21o1" onclick="updateIPSetupDlg()"><span id="d21l1"></span><br>
|
||
<div id="id_d21manualdiv">
|
||
<input type="radio" name="d21" id='d21o2' onclick="updateIPSetupDlg()"><span id="d21l2"></span><br><br>
|
||
<div style="margin-left:20px">
|
||
<div style="height:26px"><input id="idx_d21address" onkeyup="updateIPSetupDlg()" style="float:right;width:230px"><div>IP address</div></div>
|
||
<div style="height:26px" id="id_d21subnetdiv"><input id="idx_d21subnet" onkeyup="updateIPSetupDlg()" style="float:right;width:230px"><div>Subnet mark</div></div>
|
||
<div style="height:26px"><input id="idx_d21gateway" onkeyup="updateIPSetupDlg()" style="float:right;width:230px"><div>Gateway</div></div>
|
||
<div style="height:26px"><input id="idx_d21dns1" onkeyup="updateIPSetupDlg()" style="float:right;width:230px"><div>Primary DNS</div></div>
|
||
<div style="height:26px"><input id="idx_d21dns2" onkeyup="updateIPSetupDlg()" style="float:right;width:230px"><div>Alternate DNS</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="dialog23" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style='height:26px'>
|
||
<select id="idx_d23ddns" style="float:right;width:200px" onchange="showEditDnsDlgChange()">
|
||
<option value="0">Disabled
|
||
<option value="1">Disabled, DHCP update
|
||
<option value="2">Enabled
|
||
</select>
|
||
<div>Dynamic DNS client</div>
|
||
</div>
|
||
<div style='height:26px'><input id="idx_d23interval" style="float:right;width:200px"><div>Update Interval (minutes)</div></div>
|
||
<div style='height:26px'><input id="idx_d23ttl" style="float:right;width:200px"><div>TTL (seconds)</div></div>
|
||
<div style="font-size:10px"><br>Defaut Interval is 1440 minutes, Default TTL is 900 seconds.</div>
|
||
</div>
|
||
<div id="dialog24" style="margin:auto;margin:3px">
|
||
<br>
|
||
<div style="height:26px">
|
||
<select id="idx_d24Command" style="float:right;width:200px" onchange="showAdvPowerDlgChange()">
|
||
<option value="2">Power up
|
||
<option value="5">Power cycle
|
||
<option value="8">Power down
|
||
<option value="10">Reset
|
||
<option value="999">Set boot options
|
||
</select>
|
||
<div>Remote Command</div>
|
||
</div>
|
||
<div style="height:80px">
|
||
<div id="idx_d24bootoptions" style="float:right;border:1px solid #666;width:200px;height:72px;overflow-y:scroll;background-color:white">
|
||
<div id="d24dBiosPause"><input type="checkbox" id="d24BiosPause" onchange="showAdvPowerDlgChange()">BIOS Pause<br></div>
|
||
<div id="d24dBiosSetup"><input type="checkbox" id="d24BiosSetup" onchange="showAdvPowerDlgChange()">BIOS Setup<br></div>
|
||
<div id="d24dConfigurationDataReset"><input type="checkbox" id="d24ConfigurationDataReset" onchange="showAdvPowerDlgChange()">Configuration data reset<br></div>
|
||
<div id="d24dForceProgressEvents"><input type="checkbox" id="d24ForceProgressEvents" onchange="showAdvPowerDlgChange()">Force progress events<br></div>
|
||
<div id="d24dLockPowerButton"><input type="checkbox" id="d24LockPowerButton" onchange="showAdvPowerDlgChange()">Lock power button<br></div>
|
||
<div id="d24dLockResetButton"><input type="checkbox" id="d24LockResetButton" onchange="showAdvPowerDlgChange()">Lock reset button<br></div>
|
||
<div id="d24dLockSleepButton"><input type="checkbox" id="d24LockSleepButton" onchange="showAdvPowerDlgChange()">Lock sleep button<br></div>
|
||
<div id="d24dLockKeyboard"><input type="checkbox" id="d24LockKeyboard" onchange="showAdvPowerDlgChange()">Lock keyboard<br></div>
|
||
<div id="d24dUserPasswordBypass"><input type="checkbox" id="d24UserPasswordBypass" onchange="showAdvPowerDlgChange()">BIOS password bypass<br></div>
|
||
<div id="d24dReflashBios"><input type="checkbox" id="d24ReflashBios" onchange="showAdvPowerDlgChange()">Reflash BIOS<br></div>
|
||
<div id="d24dSafeMode"><input type="checkbox" id="d24SafeMode" onchange="showAdvPowerDlgChange()">Safe mode<br></div>
|
||
<div id="d24dUseIDER"><input type="checkbox" id="d24UseIDER" onchange="showAdvPowerDlgChange()">Use IDER<br></div>
|
||
<div id="d24dSerialOverLan"><input type="checkbox" id="d24SerialOverLan" onchange="showAdvPowerDlgChange()">Serial-over-LAN<br></div>
|
||
<div id="d24dSecureErase"><input type="checkbox" id="d24SecureErase" onchange="showAdvPowerDlgChange()">Intel® Remote Secure Erase<br></div>
|
||
</div>
|
||
<div>Boot Settings</div>
|
||
</div>
|
||
<div style="height:26px">
|
||
<select id="idx_d24ForceBootDevice" style="float:right;width:200px" onchange="showAdvPowerDlgChange()">
|
||
<option value="0">None
|
||
<option value="1">Force CD/DVD Boot
|
||
<option value="2">Force PXE Boot
|
||
<option value="3">Force Hard Disk Boot
|
||
<option value="4">Force Diagnostic Boot
|
||
</select>
|
||
<div>Boot Source</div>
|
||
</div>
|
||
<div style="height:26px">
|
||
<select id="idx_d24BootMediaIndex" style="float:right;width:200px" onchange="showAdvPowerDlgChange()">
|
||
<option value="0">None
|
||
<option value="1">Index 1
|
||
<option value="2">Index 2
|
||
<option value="3">Index 3
|
||
<option value="3">Index 4
|
||
</select>
|
||
<div>Boot Media Index</div>
|
||
</div>
|
||
<div style="height:26px" id="idd_d24IDERBootDevice">
|
||
<select id="idx_d24IDERBootDevice" style="float:right;width:200px" onchange="showAdvPowerDlgChange()">
|
||
<option value="0">Boot to floppy
|
||
<option value="1">Boot to CDROM
|
||
</select>
|
||
<div>IDER Boot Device</div>
|
||
</div>
|
||
<div style="height:26px">
|
||
<select id="idx_d24Verbocity" style="float:right;width:200px" onchange="showAdvPowerDlgChange()">
|
||
<option value="0">System Default
|
||
<option id="idx_d24Verbocity1" value="1">Quiet
|
||
<option id="idx_d24Verbocity2" value="2">Verbose
|
||
<option id="idx_d24Verbocity3" value="3">Blank Screen
|
||
</select>
|
||
<div>Verbocity</div>
|
||
</div>
|
||
</div>
|
||
<div id="dialog25" style="margin:auto;margin:3px">
|
||
<div style="text-align:left">
|
||
<div style="height:26px;margin-top:4px"><input id="d25alarm_name" style="float:right;width:180px" maxlength="32" onkeyup="alertDialogUpdate()"><div style="padding-top:4px">Alarm name</div></div>
|
||
<div style="height:26px;margin-top:4px">
|
||
<div style="float:right">
|
||
<input id="d25alarm_sdate" style="width:180px" maxlength="10" onkeyup="alertDialogUpdate()" onkeypress="return numbersOnly(event,45)">
|
||
</div>
|
||
<div style="padding-top:4px">Wake date (year-month-day)</div>
|
||
</div>
|
||
<div style="height:26px;margin-top:4px">
|
||
<div style="float:right">
|
||
<input id="d25alarm_stime" style="width:180px" maxlength="10" onkeyup="alertDialogUpdate()" onkeypress="return numbersOnly(event,58)">
|
||
</div>
|
||
<div style="padding-top:4px">Wake time (hour:min:sec)</div>
|
||
</div>
|
||
<div style="height:26px;margin-top:4px">
|
||
<div style="float:right">
|
||
<input id="d25alarm_interval" style="width:180px" maxlength="10" onkeyup="alertDialogUpdate()" onkeypress="return numbersOnly(event,45)">
|
||
</div>
|
||
<div style="padding-top:4px">Interval (days-hours-min)</div>
|
||
</div>
|
||
<div style="height:26px;margin-top:4px">
|
||
<div style="float:right;width:180px">
|
||
<select id="d25alarm_doc" style="width:100%" onchange="showAdvPowerDlgChange()">
|
||
<option value="0">Keep alarm
|
||
<option value="1">Delete on completion
|
||
</select>
|
||
</div>
|
||
<div style="padding-top:4px">After wake</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:10px;margin-bottom:4px">
|
||
<input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
|
||
<input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
|
||
<div style="height:25px"><input id="idx_dlgDeleteButton" type="button" value="Delete" style="width:80px;display:none" onclick="dialogclose(2)"></div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|
||
<script>/**
|
||
* @description Set of short commonly used methods for handling HTML elements
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.0.1b
|
||
*/
|
||
|
||
// Add startsWith for IE browser
|
||
if (!String.prototype.startsWith) { String.prototype.startsWith = function (str) { return this.lastIndexOf(str, 0) === 0; }; }
|
||
if (!String.prototype.endsWith) { String.prototype.endsWith = function (str) { return this.indexOf(str, this.length - str.length) !== -1; }; }
|
||
|
||
// Quick UI functions, a bit of a replacement for jQuery
|
||
//function Q(x) { if (document.getElementById(x) == null) { console.log('Invalid element: ' + x); } return document.getElementById(x); } // "Q"
|
||
function Q(x) { return document.getElementById(x); } // "Q"
|
||
function QS(x) { try { return Q(x).style; } catch (x) { } } // "Q" style
|
||
function QE(x, y) { try { Q(x).disabled = !y; } catch (x) { } } // "Q" enable
|
||
function QV(x, y) { try { QS(x).display = (y ? '' : 'none'); } catch (x) { } } // "Q" visible
|
||
function QA(x, y) { Q(x).innerHTML += y; } // "Q" append
|
||
function QH(x, y) { Q(x).innerHTML = y; } // "Q" html
|
||
|
||
// Move cursor to end of input box
|
||
function inputBoxFocus(x) { Q(x).focus(); var v = Q(x).value; Q(x).value = ''; Q(x).value = v; }
|
||
|
||
// Binary encoding and decoding functions
|
||
function ReadShort(v, p) { return (v.charCodeAt(p) << 8) + v.charCodeAt(p + 1); }
|
||
function ReadShortX(v, p) { return (v.charCodeAt(p + 1) << 8) + v.charCodeAt(p); }
|
||
function ReadInt(v, p) { return (v.charCodeAt(p) * 0x1000000) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); } // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
|
||
function ReadSInt(v, p) { return (v.charCodeAt(p) << 24) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); }
|
||
function ReadIntX(v, p) { return (v.charCodeAt(p + 3) * 0x1000000) + (v.charCodeAt(p + 2) << 16) + (v.charCodeAt(p + 1) << 8) + v.charCodeAt(p); }
|
||
function ShortToStr(v) { return String.fromCharCode((v >> 8) & 0xFF, v & 0xFF); }
|
||
function ShortToStrX(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF); }
|
||
function IntToStr(v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); }
|
||
function IntToStrX(v) { return String.fromCharCode(v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, (v >> 24) & 0xFF); }
|
||
function MakeToArray(v) { if (!v || v == null || typeof v == 'object') return v; return [v]; }
|
||
function SplitArray(v) { return v.split(','); }
|
||
function Clone(v) { return JSON.parse(JSON.stringify(v)); }
|
||
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
|
||
|
||
// Move an element from one position in an array to a new position
|
||
function ArrayElementMove(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)[0]); };
|
||
|
||
// Print object for HTML
|
||
function ObjectToStringEx(x, c) {
|
||
var r = "";
|
||
if (x != 0 && (!x || x == null)) return "(Null)";
|
||
if (x instanceof Array) { for (var i in x) { r += '<br />' + gap(c) + "Item #" + i + ": " + ObjectToStringEx(x[i], c + 1); } }
|
||
else if (x instanceof Object) { for (var i in x) { r += '<br />' + gap(c) + i + " = " + ObjectToStringEx(x[i], c + 1); } }
|
||
else { r += EscapeHtml(x); }
|
||
return r;
|
||
}
|
||
|
||
// Print object for console
|
||
function ObjectToStringEx2(x, c) {
|
||
var r = "";
|
||
if (x != 0 && (!x || x == null)) return "(Null)";
|
||
if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + "Item #" + i + ": " + ObjectToStringEx2(x[i], c + 1); } }
|
||
else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + " = " + ObjectToStringEx2(x[i], c + 1); } }
|
||
else { r += EscapeHtml(x); }
|
||
return r;
|
||
}
|
||
|
||
// Create an ident gap
|
||
function gap(c) { var x = ''; for (var i = 0; i < (c * 4) ; i++) { x += ' '; } return x; }
|
||
function gap2(c) { var x = ''; for (var i = 0; i < (c * 4) ; i++) { x += ' '; } return x; }
|
||
|
||
// Print an object in html
|
||
function ObjectToString(x) { return ObjectToStringEx(x, 0); }
|
||
function ObjectToString2(x) { return ObjectToStringEx2(x, 0); }
|
||
|
||
// Convert a hex string to a raw string
|
||
function hex2rstr(d) {
|
||
if (typeof d != "string" || d.length == 0) return '';
|
||
var r = '', m = ('' + d).match(/../g), t;
|
||
while (t = m.shift()) r += String.fromCharCode('0x' + t);
|
||
return r
|
||
}
|
||
|
||
// Convert decimal to hex
|
||
function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
|
||
|
||
// Convert a raw string to a hex string
|
||
function rstr2hex(input) {
|
||
var r = '', i;
|
||
for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); }
|
||
return r;
|
||
}
|
||
|
||
// UTF-8 encoding & decoding functions
|
||
function encode_utf8(s) { return unescape(encodeURIComponent(s)); }
|
||
function decode_utf8(s) { return decodeURIComponent(escape(s)); }
|
||
|
||
// Convert a string into a blob
|
||
function data2blob(data) {
|
||
var bytes = new Array(data.length);
|
||
for (var i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i);
|
||
var blob = new Blob([new Uint8Array(bytes)]);
|
||
return blob;
|
||
}
|
||
|
||
// Generate random numbers
|
||
function random(max) { return Math.floor(Math.random() * max); }
|
||
|
||
// Trademarks
|
||
function trademarks(x) { return x.replace(/\(R\)/g, '®').replace(/\(TM\)/g, '™'); }
|
||
|
||
|
||
/**
|
||
* @description WSMAN communication using websocket
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.2.0c
|
||
*/
|
||
|
||
// Construct a WSMAN communication object
|
||
var CreateWsmanComm = function (host, port, user, pass, tls) {
|
||
var obj = {};
|
||
obj.PendingAjax = []; // List of pending AJAX calls. When one frees up, another will start.
|
||
obj.ActiveAjaxCount = 0; // Number of currently active AJAX calls
|
||
obj.MaxActiveAjaxCount = 1; // Maximum number of activate AJAX calls at the same time.
|
||
obj.FailAllError = 0; // Set this to non-zero to fail all AJAX calls with that error status, 999 causes responses to be silent.
|
||
obj.challengeParams = null;
|
||
obj.noncecounter = 1;
|
||
obj.authcounter = 0;
|
||
obj.socket = null;
|
||
obj.socketState = 0;
|
||
obj.host = host;
|
||
obj.port = port;
|
||
obj.user = user;
|
||
obj.pass = pass;
|
||
obj.tls = tls;
|
||
obj.tlsv1only = 0;
|
||
obj.cnonce = Math.random().toString(36).substring(7); // Generate a random client nonce
|
||
obj.inDataCount = 0;
|
||
|
||
// Private method
|
||
//obj.Debug = function (msg) { console.log(msg); }
|
||
|
||
// Private method
|
||
// pri = priority, if set to 1, the call is high priority and put on top of the stack.
|
||
obj.PerformAjax = function (postdata, callback, tag, pri, url, action) {
|
||
if (obj.ActiveAjaxCount < obj.MaxActiveAjaxCount && obj.PendingAjax.length == 0) {
|
||
// There are no pending AJAX calls, perform the call now.
|
||
obj.PerformAjaxEx(postdata, callback, tag, url, action);
|
||
} else {
|
||
// If this is a high priority call, put this call in front of the array, otherwise put it in the back.
|
||
if (pri == 1) { obj.PendingAjax.unshift([postdata, callback, tag, url, action]); } else { obj.PendingAjax.push([postdata, callback, tag, url, action]); }
|
||
}
|
||
}
|
||
|
||
// Private method
|
||
obj.PerformNextAjax = function () {
|
||
if (obj.ActiveAjaxCount >= obj.MaxActiveAjaxCount || obj.PendingAjax.length == 0) return;
|
||
var x = obj.PendingAjax.shift();
|
||
obj.PerformAjaxEx(x[0], x[1], x[2], x[3], x[4]);
|
||
obj.PerformNextAjax();
|
||
}
|
||
|
||
// Private method
|
||
obj.PerformAjaxEx = function (postdata, callback, tag, url, action) {
|
||
if (obj.FailAllError != 0) { obj.gotNextMessagesError({ status: obj.FailAllError }, 'error', null, [postdata, callback, tag, url, action]); return; }
|
||
if (!postdata) postdata = "";
|
||
//console.log("SEND: " + postdata); // DEBUG
|
||
|
||
// We are in a websocket relay environment
|
||
obj.ActiveAjaxCount++;
|
||
return obj.PerformAjaxExNodeJS(postdata, callback, tag, url, action);
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
obj.pendingAjaxCall = [];
|
||
|
||
// Websocket relay specific private method
|
||
obj.PerformAjaxExNodeJS = function (postdata, callback, tag, url, action) { obj.PerformAjaxExNodeJS2(postdata, callback, tag, url, action, 3); }
|
||
|
||
// Websocket relay specific private method
|
||
obj.PerformAjaxExNodeJS2 = function (postdata, callback, tag, url, action, retry) {
|
||
if (retry <= 0 || obj.FailAllError != 0) {
|
||
// Too many retry, fail here.
|
||
obj.ActiveAjaxCount--;
|
||
if (obj.FailAllError != 999) obj.gotNextMessages(null, 'error', { status: ((obj.FailAllError == 0) ? 408 : obj.FailAllError) }, [postdata, callback, tag, url, action]); // 408 is timeout error
|
||
obj.PerformNextAjax();
|
||
return;
|
||
}
|
||
obj.pendingAjaxCall.push([postdata, callback, tag, url, action, retry]);
|
||
if (obj.socketState == 0) { obj.xxConnectHttpSocket(); }
|
||
else if (obj.socketState == 2) { obj.sendRequest(postdata, url, action); }
|
||
}
|
||
|
||
// Websocket relay specific private method (Content Length Encoding)
|
||
obj.sendRequest = function (postdata, url, action) {
|
||
url = url ? url : "/wsman";
|
||
action = action ? action : "POST";
|
||
var h = action + " " + url + " HTTP/1.1\r\n";
|
||
if (obj.challengeParams != null) {
|
||
var response = hex_md5(hex_md5(obj.user + ':' + obj.challengeParams["realm"] + ':' + obj.pass) + ':' + obj.challengeParams["nonce"] + ':' + obj.noncecounter + ':' + obj.cnonce + ':' + obj.challengeParams["qop"] + ':' + hex_md5(action + ':' + url));
|
||
h += 'Authorization: ' + obj.renderDigest({ "username": obj.user, "realm": obj.challengeParams["realm"], "nonce": obj.challengeParams["nonce"], "uri": url, "qop": obj.challengeParams["qop"], "response": response, "nc": obj.noncecounter++, "cnonce": obj.cnonce }) + '\r\n';
|
||
}
|
||
h += 'Host: ' + obj.host + ':' + obj.port + '\r\nContent-Length: ' + postdata.length + '\r\n\r\n' + postdata; // Use Content-Length
|
||
//h += 'Host: ' + obj.host + ':' + obj.port + '\r\nTransfer-Encoding: chunked\r\n\r\n' + postdata.length.toString(16).toUpperCase() + '\r\n' + postdata + '\r\n0\r\n\r\n'; // Use Chunked-Encoding
|
||
_Send(h);
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
obj.parseDigest = function (header) {
|
||
var t = header.substring(7).split(',');
|
||
for (i in t) t[i] = t[i].trim();
|
||
return t.reduce(function (obj, s) { var parts = s.split('='); obj[parts[0]] = parts[1].replace(/"/g, ''); return obj; }, {})
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
obj.renderDigest = function (params) {
|
||
var paramsnames = [];
|
||
for (i in params) { paramsnames.push(i); }
|
||
return 'Digest ' + paramsnames.reduce(function (s1, ii) { return s1 + ',' + ii + '="' + params[ii] + '"' }, '').substring(1);
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
obj.xxConnectHttpSocket = function () {
|
||
//obj.Debug("xxConnectHttpSocket");
|
||
obj.inDataCount = 0;
|
||
obj.socketState = 1;
|
||
obj.socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/webrelay.ashx?p=1&host=" + obj.host + "&port=" + obj.port + "&tls=" + obj.tls + "&tls1only=" + obj.tlsv1only + ((user == '*') ? "&serverauth=1" : "") + ((typeof pass === "undefined") ? ("&serverauth=1&user=" + user) : "")); // The "p=1" indicates to the relay that this is a WSMAN session
|
||
obj.socket.onopen = _OnSocketConnected;
|
||
obj.socket.onmessage = _OnMessage;
|
||
obj.socket.onclose = _OnSocketClosed;
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
function _OnSocketConnected() {
|
||
obj.socketState = 2;
|
||
obj.socketParseState = 0;
|
||
obj.socketAccumulator = '';
|
||
obj.socketHeader = null;
|
||
obj.socketData = '';
|
||
//console.log("xxOnSocketConnected");
|
||
for (i in obj.pendingAjaxCall) { obj.sendRequest(obj.pendingAjaxCall[i][0], obj.pendingAjaxCall[i][3], obj.pendingAjaxCall[i][4]); }
|
||
}
|
||
|
||
function _OnMessage(e) {
|
||
obj.inDataCount++;
|
||
if (typeof e.data == 'object') {
|
||
var f = new FileReader();
|
||
if (f.readAsBinaryString) {
|
||
// Chrome & Firefox (Draft)
|
||
f.onload = function (e) { _OnSocketData(e.target.result); }
|
||
f.readAsBinaryString(new Blob([e.data]));
|
||
} else if (f.readAsArrayBuffer) {
|
||
// Chrome & Firefox (Spec)
|
||
f.onloadend = function (e) { _OnSocketData(e.target.result); }
|
||
f.readAsArrayBuffer(e.data);
|
||
} else {
|
||
// IE10, readAsBinaryString does not exist, use an alternative.
|
||
var binary = "";
|
||
var bytes = new Uint8Array(e.data);
|
||
var length = bytes.byteLength;
|
||
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
|
||
_OnSocketData(binary);
|
||
}
|
||
} else if (typeof e.data == 'string') {
|
||
// We got a string object
|
||
_OnSocketData(e.data);
|
||
}
|
||
};
|
||
|
||
// Websocket relay specific private method
|
||
function _OnSocketData(data) {
|
||
//obj.Debug("_OnSocketData (" + data.length + "): " + data);
|
||
|
||
if (typeof data === 'object') {
|
||
// This is an ArrayBuffer, convert it to a string array (used in IE)
|
||
var binary = "", bytes = new Uint8Array(data), length = bytes.byteLength;
|
||
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
|
||
data = binary;
|
||
}
|
||
else if (typeof data !== 'string') return;
|
||
|
||
//console.log("RECV(" + obj.socketParseState + "): " + data); // DEBUG
|
||
|
||
obj.socketAccumulator += data;
|
||
while (true) {
|
||
if (obj.socketParseState == 0) {
|
||
var headersize = obj.socketAccumulator.indexOf("\r\n\r\n");
|
||
if (headersize < 0) return;
|
||
//obj.Debug(obj.socketAccumulator.substring(0, headersize)); // Display received HTTP header
|
||
obj.socketHeader = obj.socketAccumulator.substring(0, headersize).split("\r\n");
|
||
obj.socketAccumulator = obj.socketAccumulator.substring(headersize + 4);
|
||
obj.socketParseState = 1;
|
||
obj.socketData = '';
|
||
obj.socketXHeader = { Directive: obj.socketHeader[0].split(' ') };
|
||
//console.log("Header", obj.socketXHeader);
|
||
for (i in obj.socketHeader) {
|
||
if (i != 0) {
|
||
var x2 = obj.socketHeader[i].indexOf(':');
|
||
obj.socketXHeader[obj.socketHeader[i].substring(0, x2).toLowerCase()] = obj.socketHeader[i].substring(x2 + 2);
|
||
}
|
||
}
|
||
}
|
||
if (obj.socketParseState == 1) {
|
||
var csize = -1;
|
||
if ((obj.socketXHeader["connection"] != undefined) && (obj.socketXHeader["connection"].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == undefined) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) {
|
||
// The body ends with a close, in this case, we will only process the header
|
||
csize = 0;
|
||
} else if (obj.socketXHeader["content-length"] != undefined) {
|
||
// The body length is specified by the content-length
|
||
csize = parseInt(obj.socketXHeader["content-length"]);
|
||
if (obj.socketAccumulator.length < csize) return;
|
||
var data = obj.socketAccumulator.substring(0, csize);
|
||
obj.socketAccumulator = obj.socketAccumulator.substring(csize);
|
||
obj.socketData = data;
|
||
csize = 0;
|
||
} else {
|
||
// The body is chunked
|
||
var clen = obj.socketAccumulator.indexOf("\r\n");
|
||
if (clen < 0) return; // Chunk length not found, exit now and get more data.
|
||
// Chunk length if found, lets see if we can get the data.
|
||
csize = parseInt(obj.socketAccumulator.substring(0, clen), 16);
|
||
if (isNaN(csize)) { if (obj.websocket) { obj.websocket.close(); } return; } // Critical error, close the socket and exit.
|
||
if (obj.socketAccumulator.length < clen + 2 + csize + 2) return;
|
||
// We got a chunk with all of the data, handle the chunck now.
|
||
var data = obj.socketAccumulator.substring(clen + 2, clen + 2 + csize);
|
||
obj.socketAccumulator = obj.socketAccumulator.substring(clen + 2 + csize + 2);
|
||
obj.socketData += data;
|
||
}
|
||
if (csize == 0) {
|
||
//obj.Debug("_OnSocketData DONE: (" + obj.socketData.length + "): " + obj.socketData);
|
||
_ProcessHttpResponse(obj.socketXHeader, obj.socketData);
|
||
obj.socketParseState = 0;
|
||
obj.socketHeader = null;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
function _ProcessHttpResponse(header, data) {
|
||
//obj.Debug("_ProcessHttpResponse: " + header.Directive[1]);
|
||
|
||
var s = parseInt(header.Directive[1]);
|
||
if (isNaN(s)) {
|
||
s = 602;
|
||
}
|
||
if (s == 401 && ++(obj.authcounter) < 3) {
|
||
obj.challengeParams = obj.parseDigest(header['www-authenticate']); // Set the digest parameters, after this, the socket will close and we will auto-retry
|
||
} else {
|
||
var r = obj.pendingAjaxCall.shift();
|
||
// if (s != 200) { obj.Debug("Error, status=" + s + "\r\n\r\nreq=" + r[0] + "\r\n\r\nresp=" + data); } // Debug: Display the request & response if something did not work.
|
||
obj.authcounter = 0;
|
||
obj.ActiveAjaxCount--;
|
||
obj.gotNextMessages(data, 'success', { status: s }, r);
|
||
obj.PerformNextAjax();
|
||
}
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
function _OnSocketClosed(data) {
|
||
//console.log("_OnSocketClosed");
|
||
if (obj.inDataCount == 0) { obj.tlsv1only = (1 - obj.tlsv1only); }
|
||
obj.socketState = 0;
|
||
if (obj.socket != null) { obj.socket.close(); obj.socket = null; }
|
||
if (obj.pendingAjaxCall.length > 0) {
|
||
var r = obj.pendingAjaxCall.shift();
|
||
var retry = r[5];
|
||
obj.PerformAjaxExNodeJS2(r[0], r[1], r[2], r[3], r[4], --retry);
|
||
}
|
||
}
|
||
|
||
// Websocket relay specific private method
|
||
function _Send(x) {
|
||
//console.log("SEND: " + x); // DEBUG
|
||
if (obj.socketState == 2 && obj.socket != null && obj.socket.readyState == WebSocket.OPEN) {
|
||
var b = new Uint8Array(x.length);
|
||
for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); }
|
||
try { obj.socket.send(b.buffer); } catch (e) { }
|
||
}
|
||
}
|
||
|
||
// Private method
|
||
obj.gotNextMessages = function (data, status, request, callArgs) {
|
||
if (obj.FailAllError == 999) return;
|
||
if (obj.FailAllError != 0) { callArgs[1](null, obj.FailAllError, callArgs[2]); return; }
|
||
if (request.status != 200) { callArgs[1](null, request.status, callArgs[2]); return; }
|
||
callArgs[1](data, 200, callArgs[2]);
|
||
}
|
||
|
||
// Private method
|
||
obj.gotNextMessagesError = function (request, status, errorThrown, callArgs) {
|
||
if (obj.FailAllError == 999) return;
|
||
if (obj.FailAllError != 0) { callArgs[1](null, obj.FailAllError, callArgs[2]); return; }
|
||
callArgs[1](obj, null, { Header: { HttpError: request.status } }, request.status, callArgs[2]);
|
||
}
|
||
|
||
// Cancel all pending queries with given status
|
||
obj.CancelAllQueries = function (s) {
|
||
while (obj.PendingAjax.length > 0) { var x = obj.PendingAjax.shift(); x[1](null, s, x[2]); }
|
||
if (obj.websocket != null) { obj.websocket.close(); obj.websocket = null; obj.socketState = 0; }
|
||
}
|
||
|
||
return obj;
|
||
}
|
||
|
||
/**
|
||
* @description Intel AMT Redirection Transport Module - using websocket relay
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.0.1f
|
||
*/
|
||
|
||
// Construct a MeshServer object
|
||
var CreateAmtRedirect = function (module) {
|
||
var obj = {};
|
||
obj.m = module; // This is the inner module (Terminal or Desktop)
|
||
module.parent = obj;
|
||
obj.State = 0;
|
||
obj.socket = null;
|
||
|
||
obj.host = null;
|
||
obj.port = 0;
|
||
obj.user = null;
|
||
obj.pass = null;
|
||
obj.authuri = "/RedirectionService";
|
||
obj.tlsv1only = 0;
|
||
obj.connectstate = 0;
|
||
obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER
|
||
|
||
obj.amtaccumulator = "";
|
||
obj.amtsequence = 1;
|
||
obj.amtkeepalivetimer = null;
|
||
|
||
obj.onStateChanged = null;
|
||
|
||
// Private method
|
||
//obj.Debug = function (msg) { console.log(msg); }
|
||
|
||
|
||
obj.Start = function (host, port, user, pass, tls) {
|
||
obj.host = host;
|
||
obj.port = port;
|
||
obj.user = user;
|
||
obj.pass = pass;
|
||
obj.connectstate = 0;
|
||
obj.socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + "/webrelay.ashx?p=2&host=" + host + "&port=" + port + "&tls=" + tls + ((user == '*') ? "&serverauth=1" : "") + ((typeof pass === "undefined") ? ("&serverauth=1&user=" + user) : "") + "&tls1only=" + obj.tlsv1only); // The "p=2" indicates to the relay that this is a REDIRECTION session
|
||
obj.socket.onopen = obj.xxOnSocketConnected;
|
||
obj.socket.onmessage = obj.xxOnMessage;
|
||
obj.socket.onclose = obj.xxOnSocketClosed;
|
||
obj.xxStateChange(1);
|
||
}
|
||
|
||
|
||
obj.xxOnSocketConnected = function() {
|
||
//obj.Debug("Redir Socket Connected");
|
||
obj.xxStateChange(2);
|
||
if (obj.protocol == 1) obj.xxSend(obj.RedirectStartSol); // TODO: Put these strings in higher level module to tighten code
|
||
if (obj.protocol == 2) obj.xxSend(obj.RedirectStartKvm); // Don't need these is the feature is not compiled-in.
|
||
if (obj.protocol == 3) obj.xxSend(obj.RedirectStartIder);
|
||
}
|
||
|
||
obj.xxOnMessage = function (e) {
|
||
if (typeof e.data == 'object') {
|
||
var f = new FileReader();
|
||
if (f.readAsBinaryString) {
|
||
// Chrome & Firefox (Draft)
|
||
f.onload = function (e) { obj.xxOnSocketData(e.target.result); }
|
||
f.readAsBinaryString(new Blob([e.data]));
|
||
} else if (f.readAsArrayBuffer) {
|
||
// Chrome & Firefox (Spec)
|
||
f.onloadend = function (e) { obj.xxOnSocketData(e.target.result); }
|
||
f.readAsArrayBuffer(e.data);
|
||
} else {
|
||
// IE10, readAsBinaryString does not exist, use an alternative.
|
||
var binary = "";
|
||
var bytes = new Uint8Array(e.data);
|
||
var length = bytes.byteLength;
|
||
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
|
||
obj.xxOnSocketData(binary);
|
||
}
|
||
} else {
|
||
// If we get a string object, it maybe the WebRTC confirm. Ignore it.
|
||
// obj.debug("MeshDataChannel - OnData - " + typeof e.data + " - " + e.data.length);
|
||
obj.xxOnSocketData(e.data);
|
||
}
|
||
};
|
||
|
||
obj.xxOnSocketData = function (data) {
|
||
if (!data || obj.connectstate == -1) return;
|
||
|
||
if (typeof data === 'object') {
|
||
// This is an ArrayBuffer, convert it to a string array (used in IE)
|
||
var binary = "";
|
||
var bytes = new Uint8Array(data);
|
||
var length = bytes.byteLength;
|
||
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
|
||
data = binary;
|
||
}
|
||
else if (typeof data !== 'string') { return; }
|
||
|
||
if ((obj.protocol == 2 || obj.protocol == 3) && obj.connectstate == 1) { return obj.m.ProcessData(data); } // KVM traffic, forward it directly.
|
||
obj.amtaccumulator += data;
|
||
//obj.Debug("Redir Recv(" + obj.amtaccumulator.length + "): " + rstr2hex(obj.amtaccumulator));
|
||
while (obj.amtaccumulator.length >= 1) {
|
||
var cmdsize = 0;
|
||
switch (obj.amtaccumulator.charCodeAt(0)) {
|
||
case 0x11: // StartRedirectionSessionReply (17)
|
||
if (obj.amtaccumulator.length < 4) return;
|
||
var statuscode = obj.amtaccumulator.charCodeAt(1);
|
||
switch (statuscode) {
|
||
case 0: // STATUS_SUCCESS
|
||
if (obj.amtaccumulator.length < 13) return;
|
||
var oemlen = obj.amtaccumulator.charCodeAt(12);
|
||
if (obj.amtaccumulator.length < 13 + oemlen) return;
|
||
|
||
// Query for available authentication
|
||
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)); // Query authentication support
|
||
cmdsize = (13 + oemlen);
|
||
break;
|
||
default:
|
||
obj.Stop();
|
||
break;
|
||
}
|
||
break;
|
||
case 0x14: // AuthenticateSessionReply (20)
|
||
if (obj.amtaccumulator.length < 9) return;
|
||
var authDataLen = ReadIntX(obj.amtaccumulator, 5);
|
||
if (obj.amtaccumulator.length < 9 + authDataLen) return;
|
||
var status = obj.amtaccumulator.charCodeAt(1);
|
||
var authType = obj.amtaccumulator.charCodeAt(4);
|
||
var authData = [];
|
||
for (i = 0; i < authDataLen; i++) { authData.push(obj.amtaccumulator.charCodeAt(9 + i)); }
|
||
var authDataBuf = obj.amtaccumulator.substring(9, 9 + authDataLen);
|
||
cmdsize = 9 + authDataLen;
|
||
|
||
if (authType == 0) {
|
||
// Query
|
||
if (authData.indexOf(4) >= 0) {
|
||
// Good Digest Auth (With cnonce and all)
|
||
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x04) + IntToStrX(obj.user.length + obj.authuri.length + 8) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00, 0x00));
|
||
}
|
||
else if (authData.indexOf(3) >= 0) {
|
||
// Bad Digest Auth (Not sure why this is supported, cnonce is not used!)
|
||
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x03) + IntToStrX(obj.user.length + obj.authuri.length + 7) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(0x00, 0x00) + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(0x00, 0x00, 0x00));
|
||
}
|
||
else if (authData.indexOf(1) >= 0) {
|
||
// Basic Auth (Probably a good idea to not support this unless this is an old version of Intel AMT)
|
||
obj.xxSend(String.fromCharCode(0x13, 0x00, 0x00, 0x00, 0x01) + IntToStrX(obj.user.length + obj.pass.length + 2) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(obj.pass.length) + obj.pass);
|
||
}
|
||
else obj.Stop();
|
||
}
|
||
else if ((authType == 3 || authType == 4) && status == 1) {
|
||
var curptr = 0;
|
||
|
||
// Realm
|
||
var realmlen = authDataBuf.charCodeAt(curptr);
|
||
var realm = authDataBuf.substring(curptr + 1, curptr + 1 + realmlen);
|
||
curptr += (realmlen + 1);
|
||
|
||
// Nonce
|
||
var noncelen = authDataBuf.charCodeAt(curptr);
|
||
var nonce = authDataBuf.substring(curptr + 1, curptr + 1 + noncelen);
|
||
curptr += (noncelen + 1);
|
||
|
||
// QOP
|
||
var qoplen = 0;
|
||
var qop = null;
|
||
var cnonce = obj.xxRandomNonce(32);
|
||
var snc = '00000002';
|
||
var extra = '';
|
||
if (authType == 4) {
|
||
qoplen = authDataBuf.charCodeAt(curptr);
|
||
qop = authDataBuf.substring(curptr + 1, curptr + 1 + qoplen);
|
||
curptr += (qoplen + 1);
|
||
extra = snc + ":" + cnonce + ":" + qop + ":";
|
||
}
|
||
|
||
var digest = hex_md5(hex_md5(obj.user + ":" + realm + ":" + obj.pass) + ":" + nonce + ":" + extra + hex_md5("POST:" + obj.authuri));
|
||
var totallen = obj.user.length + realm.length + nonce.length + obj.authuri.length + cnonce.length + snc.length + digest.length + 7;
|
||
if (authType == 4) totallen += (qop.length + 1);
|
||
var buf = String.fromCharCode(0x13, 0x00, 0x00, 0x00, authType) + IntToStrX(totallen) + String.fromCharCode(obj.user.length) + obj.user + String.fromCharCode(realm.length) + realm + String.fromCharCode(nonce.length) + nonce + String.fromCharCode(obj.authuri.length) + obj.authuri + String.fromCharCode(cnonce.length) + cnonce + String.fromCharCode(snc.length) + snc + String.fromCharCode(digest.length) + digest;
|
||
if (authType == 4) buf += (String.fromCharCode(qop.length) + qop);
|
||
obj.xxSend(buf);
|
||
}
|
||
else
|
||
if (status == 0) { // Success
|
||
if (obj.protocol == 1) {
|
||
// Serial-over-LAN: Send Intel AMT serial settings...
|
||
var MaxTxBuffer = 10000;
|
||
var TxTimeout = 100;
|
||
var TxOverflowTimeout = 0;
|
||
var RxTimeout = 10000;
|
||
var RxFlushTimeout = 100;
|
||
var Heartbeat = 0;//5000;
|
||
obj.xxSend(String.fromCharCode(0x20, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(MaxTxBuffer) + ShortToStrX(TxTimeout) + ShortToStrX(TxOverflowTimeout) + ShortToStrX(RxTimeout) + ShortToStrX(RxFlushTimeout) + ShortToStrX(Heartbeat) + IntToStrX(0));
|
||
}
|
||
if (obj.protocol == 2) {
|
||
// Remote Desktop: Send traffic directly...
|
||
obj.xxSend(String.fromCharCode(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||
}
|
||
if (obj.protocol == 3) {
|
||
// Remote IDER: Send traffic directly...
|
||
obj.connectstate = 1;
|
||
obj.xxStateChange(3);
|
||
}
|
||
} else obj.Stop();
|
||
break;
|
||
case 0x21: // Response to settings (33)
|
||
if (obj.amtaccumulator.length < 23) break;
|
||
cmdsize = 23;
|
||
obj.xxSend(String.fromCharCode(0x27, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + String.fromCharCode(0x00, 0x00, 0x1B, 0x00, 0x00, 0x00));
|
||
if (obj.protocol == 1) { obj.amtkeepalivetimer = setInterval(obj.xxSendAmtKeepAlive, 2000); }
|
||
obj.connectstate = 1;
|
||
obj.xxStateChange(3);
|
||
break;
|
||
case 0x29: // Serial Settings (41)
|
||
if (obj.amtaccumulator.length < 10) break;
|
||
cmdsize = 10;
|
||
break;
|
||
case 0x2A: // Incoming display data (42)
|
||
if (obj.amtaccumulator.length < 10) break;
|
||
var cs = (10 + ((obj.amtaccumulator.charCodeAt(9) & 0xFF) << 8) + (obj.amtaccumulator.charCodeAt(8) & 0xFF));
|
||
if (obj.amtaccumulator.length < cs) break;
|
||
obj.m.ProcessData(obj.amtaccumulator.substring(10, cs));
|
||
cmdsize = cs;
|
||
break;
|
||
case 0x2B: // Keep alive message (43)
|
||
if (obj.amtaccumulator.length < 8) break;
|
||
cmdsize = 8;
|
||
break;
|
||
case 0x41:
|
||
if (obj.amtaccumulator.length < 8) break;
|
||
obj.connectstate = 1;
|
||
obj.m.Start();
|
||
// KVM traffic, forward rest of accumulator directly.
|
||
if (obj.amtaccumulator.length > 8) { obj.m.ProcessData(obj.amtaccumulator.substring(8)); }
|
||
cmdsize = obj.amtaccumulator.length;
|
||
break;
|
||
default:
|
||
console.log("Unknown Intel AMT command: " + obj.amtaccumulator.charCodeAt(0) + " acclen=" + obj.amtaccumulator.length);
|
||
obj.Stop();
|
||
return;
|
||
}
|
||
if (cmdsize == 0) return;
|
||
obj.amtaccumulator = obj.amtaccumulator.substring(cmdsize);
|
||
}
|
||
}
|
||
|
||
obj.xxSend = function (x) {
|
||
//obj.Debug("Redir Send(" + x.length + "): " + rstr2hex(x));
|
||
if (obj.socket != null && obj.socket.readyState == WebSocket.OPEN) {
|
||
var b = new Uint8Array(x.length);
|
||
for (var i = 0; i < x.length; ++i) { b[i] = x.charCodeAt(i); }
|
||
obj.socket.send(b.buffer);
|
||
}
|
||
}
|
||
|
||
obj.Send = function (x) {
|
||
if (obj.socket == null || obj.connectstate != 1) return;
|
||
if (obj.protocol == 1) { obj.xxSend(String.fromCharCode(0x28, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++) + ShortToStrX(x.length) + x); } else { obj.xxSend(x); }
|
||
}
|
||
|
||
obj.xxSendAmtKeepAlive = function () {
|
||
if (obj.socket == null) return;
|
||
obj.xxSend(String.fromCharCode(0x2B, 0x00, 0x00, 0x00) + IntToStrX(obj.amtsequence++));
|
||
}
|
||
|
||
obj.xxRandomNonceX = "abcdef0123456789";
|
||
obj.xxRandomNonce = function (length) {
|
||
var r = "";
|
||
for (var i = 0; i < length; i++) { r += obj.xxRandomNonceX.charAt(Math.floor(Math.random() * obj.xxRandomNonceX.length)); }
|
||
return r;
|
||
}
|
||
|
||
obj.xxOnSocketClosed = function () {
|
||
//obj.Debug("Redir Socket Closed");
|
||
obj.Stop();
|
||
}
|
||
|
||
obj.xxStateChange = function(newstate) {
|
||
if (obj.State == newstate) return;
|
||
obj.State = newstate;
|
||
obj.m.xxStateChange(obj.State);
|
||
if (obj.onStateChanged != null) obj.onStateChanged(obj, obj.State);
|
||
}
|
||
|
||
obj.Stop = function () {
|
||
//obj.Debug("Redir Socket Stopped");
|
||
obj.xxStateChange(0);
|
||
obj.connectstate = -1;
|
||
obj.amtaccumulator = "";
|
||
if (obj.socket != null) { obj.socket.close(); obj.socket = null; }
|
||
if (obj.amtkeepalivetimer != null) { clearInterval(obj.amtkeepalivetimer); obj.amtkeepalivetimer = null; }
|
||
}
|
||
|
||
obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
|
||
obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
|
||
obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
|
||
|
||
return obj;
|
||
}
|
||
/**
|
||
* @description Intel(r) AMT WSMAN Stack
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.2.0
|
||
*/
|
||
|
||
// Construct a MeshServer object
|
||
var WsmanStackCreateService = function (host, port, user, pass, tls, extra) {
|
||
var obj = {};
|
||
//obj.onDebugMessage = null; // Set to a function if you want to get debug messages.
|
||
obj.NextMessageId = 1; // Next message number, used to label WSMAN calls.
|
||
obj.Address = '/wsman';
|
||
obj.comm = CreateWsmanComm(host, port, user, pass, tls, extra);
|
||
|
||
obj.PerformAjax = function (postdata, callback, tag, pri, namespaces) {
|
||
if (namespaces == null) namespaces = '';
|
||
obj.comm.PerformAjax('<?xml version=\"1.0\" encoding=\"utf-8\"?><Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns=\"http://www.w3.org/2003/05/soap-envelope\" ' + namespaces + '><Header><a:Action>' + postdata, function (data, status, tag) {
|
||
if (status != 200) { callback(obj, null, { Header: { HttpError: status } }, status, tag); return; }
|
||
var wsresponse = obj.ParseWsman(data);
|
||
if (!wsresponse || wsresponse == null) { callback(obj, null, { Header: { HttpError: status } }, 601, tag); } else { callback(obj, wsresponse.Header["ResourceURI"], wsresponse, 200, tag); }
|
||
}, tag, pri);
|
||
}
|
||
|
||
// Private method
|
||
//obj.Debug = function (msg) { /*console.log(msg);*/ }
|
||
|
||
// Cancel all pending queries with given status
|
||
obj.CancelAllQueries = function (s) { obj.comm.CancelAllQueries(s); }
|
||
|
||
// Get the last element of a URI string
|
||
obj.GetNameFromUrl = function (resuri) {
|
||
var x = resuri.lastIndexOf("/");
|
||
return (x == -1)?resuri:resuri.substring(x + 1);
|
||
}
|
||
|
||
// Perform a WSMAN Subscribe operation
|
||
obj.ExecSubscribe = function (resuri, delivery, url, callback, tag, pri, selectors, opaque, user, pass) {
|
||
var digest = "", digest2 = "", opaque = "";
|
||
if (user != null && pass != null) { digest = '<t:IssuedTokens xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:se="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><t:RequestSecurityTokenResponse><t:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken</t:TokenType><t:RequestedSecurityToken><se:UsernameToken><se:Username>' + user + '</se:Username><se:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#PasswordText">' + pass + '</se:Password></se:UsernameToken></t:RequestedSecurityToken></t:RequestSecurityTokenResponse></t:IssuedTokens>'; digest2 = '<w:Auth Profile="http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/http/digest"/>'; }
|
||
if (opaque != null) { opaque = '<a:ReferenceParameters><m:arg>' + opaque + '</m:arg></a:ReferenceParameters>'; }
|
||
if (delivery == 'PushWithAck') { delivery = 'dmtf.org/wbem/wsman/1/wsman/PushWithAck'; } else if (delivery == 'Push') { delivery = 'xmlsoap.org/ws/2004/08/eventing/DeliveryModes/Push'; }
|
||
var data = "http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>" + _PutObjToSelectorsXml(selectors) + digest + '</Header><Body><e:Subscribe><e:Delivery Mode="http://schemas.' + delivery + '"><e:NotifyTo><a:Address>' + url + '</a:Address>' + opaque + '</e:NotifyTo>' + digest2 + '</e:Delivery></e:Subscribe>';
|
||
obj.PerformAjax(data + "</Body></Envelope>", callback, tag, pri, 'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing" xmlns:m="http://x.com"');
|
||
}
|
||
|
||
// Perform a WSMAN UnSubscribe operation
|
||
obj.ExecUnSubscribe = function (resuri, callback, tag, pri, selectors) {
|
||
var data = "http://schemas.xmlsoap.org/ws/2004/08/eventing/Unsubscribe</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo>" + _PutObjToSelectorsXml(selectors) + '</Header><Body><e:Unsubscribe/>';
|
||
obj.PerformAjax(data + "</Body></Envelope>", callback, tag, pri, 'xmlns:e="http://schemas.xmlsoap.org/ws/2004/08/eventing"');
|
||
}
|
||
|
||
// Perform a WSMAN PUT operation
|
||
obj.ExecPut = function (resuri, putobj, callback, tag, pri, selectors) {
|
||
var data = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Put</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60.000S</w:OperationTimeout>" + _PutObjToSelectorsXml(selectors) + '</Header><Body>' + _PutObjToBodyXml(resuri, putobj);
|
||
obj.PerformAjax(data + "</Body></Envelope>", callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN CREATE operation
|
||
obj.ExecCreate = function (resuri, putobj, callback, tag, pri, selectors) {
|
||
var objname = obj.GetNameFromUrl(resuri);
|
||
var data = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>" + _PutObjToSelectorsXml(selectors) + "</Header><Body><g:" + objname + " xmlns:g=\"" + resuri + "\">";
|
||
for (var n in putobj) { data += "<g:" + n + ">" + putobj[n] + "</g:" + n + ">" }
|
||
obj.PerformAjax(data + "</g:" + objname + "></Body></Envelope>", callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN DELETE operation
|
||
obj.ExecDelete = function (resuri, putobj, callback, tag, pri) {
|
||
var data = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>" + _PutObjToSelectorsXml(putobj) + "</Header><Body /></Envelope>";
|
||
obj.PerformAjax(data, callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN GET operation
|
||
obj.ExecGet = function (resuri, callback, tag, pri) {
|
||
obj.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body /></Envelope>", callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN method call operation
|
||
obj.ExecMethod = function (resuri, method, args, callback, tag, pri, selectors) {
|
||
var argsxml = "";
|
||
for (var i in args) { if (args[i] != null) { if (Array.isArray(args[i])) { for (var x in args[i]) { argsxml += "<r:" + i + ">" + args[i][x] + "</r:" + i + ">"; } } else { argsxml += "<r:" + i + ">" + args[i] + "</r:" + i + ">"; } } }
|
||
obj.ExecMethodXml(resuri, method, argsxml, callback, tag, pri, selectors);
|
||
}
|
||
|
||
// Perform a WSMAN method call operation. The arguments are already formatted in XML.
|
||
obj.ExecMethodXml = function (resuri, method, argsxml, callback, tag, pri, selectors) {
|
||
obj.PerformAjax(resuri + "/" + method + "</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout>" + _PutObjToSelectorsXml(selectors) + "</Header><Body><r:" + method + '_INPUT' + " xmlns:r=\"" + resuri + "\">" + argsxml + "</r:" + method + "_INPUT></Body></Envelope>", callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN ENUM operation
|
||
obj.ExecEnum = function (resuri, callback, tag, pri) {
|
||
obj.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Enumerate xmlns=\"http://schemas.xmlsoap.org/ws/2004/09/enumeration\" /></Body></Envelope>", callback, tag, pri);
|
||
}
|
||
|
||
// Perform a WSMAN PULL operation
|
||
obj.ExecPull = function (resuri, enumctx, callback, tag, pri) {
|
||
obj.PerformAjax("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull</a:Action><a:To>" + obj.Address + "</a:To><w:ResourceURI>" + resuri + "</w:ResourceURI><a:MessageID>" + (obj.NextMessageId++) + "</a:MessageID><a:ReplyTo><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address></a:ReplyTo><w:OperationTimeout>PT60S</w:OperationTimeout></Header><Body><Pull xmlns=\"http://schemas.xmlsoap.org/ws/2004/09/enumeration\"><EnumerationContext>" + enumctx + "</EnumerationContext></Pull></Body></Envelope>", callback, tag, pri); // </EnumerationContext>--<MaxElements>999</MaxElements><MaxCharacters>99999</MaxCharacters>--</Pull>
|
||
}
|
||
|
||
// Private method
|
||
obj.ParseWsman = function (xml) {
|
||
try {
|
||
if (!xml.childNodes) xml = _turnToXml(xml);
|
||
var r = { Header:{} }, header = xml.getElementsByTagName("Header")[0], t;
|
||
if (!header) header = xml.getElementsByTagName("a:Header")[0];
|
||
if (!header) return null;
|
||
for (var i = 0; i < header.childNodes.length; i++) {
|
||
var child = header.childNodes[i];
|
||
r.Header[child.localName] = child.textContent;
|
||
}
|
||
var body = xml.getElementsByTagName("Body")[0];
|
||
if (!body) body = xml.getElementsByTagName("a:Body")[0];
|
||
if (!body) return null;
|
||
if (body.childNodes.length > 0) {
|
||
t = body.childNodes[0].localName;
|
||
if (t.indexOf("_OUTPUT") == t.length - 7) { t = t.substring(0, t.length - 7); }
|
||
r.Header['Method'] = t;
|
||
r.Body = _ParseWsmanRec(body.childNodes[0]);
|
||
}
|
||
return r;
|
||
} catch (e) {
|
||
console.log("Unable to parse XML: " + xml);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Private method
|
||
function _ParseWsmanRec(node) {
|
||
var data, r = {};
|
||
for (var i = 0; i < node.childNodes.length; i++) {
|
||
var child = node.childNodes[i];
|
||
if ((child.childElementCount == null) || (child.childElementCount == 0)) { data = child.textContent; } else { data = _ParseWsmanRec(child); }
|
||
if (data == 'true') data = true; // Convert 'true' into true
|
||
if (data == 'false') data = false; // Convert 'false' into false
|
||
if ((parseInt(data) + '') === data) data = parseInt(data); // Convert integers
|
||
|
||
var childObj = data;
|
||
if ((child.attributes != null) && (child.attributes.length > 0)) {
|
||
childObj = { 'Value': data };
|
||
for(var j = 0; j < child.attributes.length; j++) {
|
||
childObj['@' + child.attributes[j].name] = child.attributes[j].value;
|
||
}
|
||
}
|
||
|
||
if (r[child.localName] instanceof Array) { r[child.localName].push(childObj); }
|
||
else if (r[child.localName] == null) { r[child.localName] = childObj; }
|
||
else { r[child.localName] = [r[child.localName], childObj]; }
|
||
}
|
||
return r;
|
||
}
|
||
|
||
function _PutObjToBodyXml(resuri, putObj) {
|
||
if (!resuri || putObj == null) return '';
|
||
var objname = obj.GetNameFromUrl(resuri);
|
||
var result = '<r:' + objname + ' xmlns:r="' + resuri + '">';
|
||
|
||
for (var prop in putObj) {
|
||
if (!putObj.hasOwnProperty(prop) || prop.indexOf('__') === 0 || prop.indexOf('@') === 0) continue;
|
||
if (putObj[prop] == null || typeof putObj[prop] === 'function') continue;
|
||
if (typeof putObj[prop] === 'object' && putObj[prop]['ReferenceParameters']) {
|
||
result += '<r:' + prop + '><a:Address>' + putObj[prop].Address + '</a:Address><a:ReferenceParameters><w:ResourceURI>' + putObj[prop]['ReferenceParameters']["ResourceURI"] + '</w:ResourceURI><w:SelectorSet>';
|
||
var selectorArray = putObj[prop]['ReferenceParameters']['SelectorSet']['Selector'];
|
||
if (Array.isArray(selectorArray)) {
|
||
for (var i=0; i< selectorArray.length; i++) {
|
||
result += '<w:Selector' + _ObjectToXmlAttributes(selectorArray[i]) + '>' + selectorArray[i]['Value'] + '</w:Selector>';
|
||
}
|
||
}
|
||
else {
|
||
result += '<w:Selector' + _ObjectToXmlAttributes(selectorArray) + '>' + selectorArray['Value'] + '</w:Selector>';
|
||
}
|
||
result += '</w:SelectorSet></a:ReferenceParameters></r:' + prop + '>';
|
||
}
|
||
else {
|
||
if (Array.isArray(putObj[prop])) {
|
||
for (var i = 0; i < putObj[prop].length; i++) {
|
||
result += '<r:' + prop + '>' + putObj[prop][i].toString() + '</r:' + prop + '>';
|
||
}
|
||
} else {
|
||
result += '<r:' + prop + '>' + putObj[prop].toString() + '</r:' + prop + '>';
|
||
}
|
||
}
|
||
}
|
||
|
||
result += '</r:' + objname + '>';
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
convert
|
||
{ @Name: 'InstanceID', @AttrName: 'Attribute Value'}
|
||
into
|
||
' Name="InstanceID" AttrName="Attribute Value" '
|
||
*/
|
||
function _ObjectToXmlAttributes(objWithAttributes) {
|
||
if(!objWithAttributes) return '';
|
||
var result = ' ';
|
||
for (var propName in objWithAttributes) {
|
||
if (!objWithAttributes.hasOwnProperty(propName) || propName.indexOf('@') !== 0) continue;
|
||
result += propName.substring(1) + '="' + objWithAttributes[propName] + '" ';
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function _PutObjToSelectorsXml(selectorSet) {
|
||
if (!selectorSet) return '';
|
||
if (typeof selectorSet == 'string') return selectorSet;
|
||
if (selectorSet['InstanceID']) return "<w:SelectorSet><w:Selector Name=\"InstanceID\">" + selectorSet['InstanceID'] + "</w:Selector></w:SelectorSet>";
|
||
var result = '<w:SelectorSet>';
|
||
for(var propName in selectorSet) {
|
||
if (!selectorSet.hasOwnProperty(propName)) continue;
|
||
result += '<w:Selector Name="' + propName + '">';
|
||
if (selectorSet[propName]['ReferenceParameters']) {
|
||
result += '<a:EndpointReference>';
|
||
result += '<a:Address>' + selectorSet[propName]['Address'] + '</a:Address><a:ReferenceParameters><w:ResourceURI>' + selectorSet[propName]['ReferenceParameters']['ResourceURI'] + '</w:ResourceURI><w:SelectorSet>';
|
||
var selectorArray = selectorSet[propName]['ReferenceParameters']['SelectorSet']['Selector'];
|
||
if (Array.isArray(selectorArray)) {
|
||
for (var i = 0; i < selectorArray.length; i++) {
|
||
result += '<w:Selector' + _ObjectToXmlAttributes(selectorArray[i]) + '>' + selectorArray[i]['Value'] + '</w:Selector>';
|
||
}
|
||
}
|
||
else {
|
||
result += '<w:Selector' + _ObjectToXmlAttributes(selectorArray) + '>' + selectorArray['Value'] + '</w:Selector>';
|
||
}
|
||
result += '</w:SelectorSet></a:ReferenceParameters></a:EndpointReference>';
|
||
} else {
|
||
result += selectorSet[propName];
|
||
}
|
||
result += '</w:Selector>';
|
||
}
|
||
result += '</w:SelectorSet>';
|
||
return result;
|
||
}
|
||
|
||
function _turnToXml(text) {
|
||
if (window.DOMParser) {
|
||
return new DOMParser().parseFromString(text, "text/xml");
|
||
} else {
|
||
// Internet Explorer
|
||
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
|
||
xmlDoc.async = false;
|
||
xmlDoc.loadXML(text);
|
||
return xmlDoc;
|
||
}
|
||
}
|
||
|
||
/*
|
||
// This is a drop-in replacement to _turnToXml() that works without xml parser dependency.
|
||
Object.defineProperty(Array.prototype, "peek", { value: function () { return (this.length > 0 ? this[this.length - 1] : null); } });
|
||
function _treeBuilder() {
|
||
this.tree = [];
|
||
this.push = function (element) { this.tree.push(element); };
|
||
this.pop = function () { var element = this.tree.pop(); if (this.tree.length > 0) { var x = this.tree.peek(); x.childNodes.push(element); x.childElementCount = x.childNodes.length; } return (element); };
|
||
this.peek = function () { return (this.tree.peek()); }
|
||
this.addNamespace = function (prefix, namespace) { this.tree.peek().nsTable[prefix] = namespace; if (this.tree.peek().attributes.length > 0) { for (var i = 0; i < this.tree.peek().attributes; ++i) { var a = this.tree.peek().attributes[i]; if (prefix == '*' && a.name == a.localName) { a.namespace = namespace; } else if (prefix != '*' && a.name != a.localName) { var pfx = a.name.split(':')[0]; if (pfx == prefix) { a.namespace = namespace; } } } } }
|
||
this.getNamespace = function (prefix) { for (var i = this.tree.length - 1; i >= 0; --i) { if (this.tree[i].nsTable[prefix] != null) { return (this.tree[i].nsTable[prefix]); } } return null; }
|
||
}
|
||
function _turnToXml(text) { if (text == null) return null; return ({ childNodes: [_turnToXmlRec(text)], getElementsByTagName: _getElementsByTagName, getChildElementsByTagName: _getChildElementsByTagName, getElementsByTagNameNS: _getElementsByTagNameNS }); }
|
||
function _getElementsByTagNameNS(ns, name) { var ret = []; _xmlTraverseAllRec(this.childNodes, function (node) { if (node.localName == name && (node.namespace == ns || ns == '*')) { ret.push(node); } }); return ret; }
|
||
function _getElementsByTagName(name) { var ret = []; _xmlTraverseAllRec(this.childNodes, function (node) { if (node.localName == name) { ret.push(node); } }); return ret; }
|
||
function _getChildElementsByTagName(name) { var ret = []; if (this.childNodes != null) { for (var node in this.childNodes) { if (this.childNodes[node].localName == name) { ret.push(this.childNodes[node]); } } } return (ret); }
|
||
function _getChildElementsByTagNameNS(ns, name) { var ret = []; if (this.childNodes != null) { for (var node in this.childNodes) { if (this.childNodes[node].localName == name && (ns == '*' || this.childNodes[node].namespace == ns)) { ret.push(this.childNodes[node]); } } } return (ret); }
|
||
function _xmlTraverseAllRec(nodes, func) { for (var i in nodes) { func(nodes[i]); if (nodes[i].childNodes) { _xmlTraverseAllRec(nodes[i].childNodes, func); } } }
|
||
function _turnToXmlRec(text) {
|
||
var elementStack = new _treeBuilder(), lastElement = null, x1 = text.split('<'), ret = [], element = null, currentElementName = null;
|
||
for (var i in x1) {
|
||
var x2 = x1[i].split('>'), x3 = x2[0].split(' '), elementName = x3[0];
|
||
if ((elementName.length > 0) && (elementName[0] != '?')) {
|
||
if (elementName[0] != '/') {
|
||
var attributes = [], localName, localname2 = elementName.split(' ')[0].split(':'), localName = (localname2.length > 1) ? localname2[1] : localname2[0];
|
||
Object.defineProperty(attributes, "get",
|
||
{
|
||
value: function () {
|
||
if (arguments.length == 1) {
|
||
for (var a in this) { if (this[a].name == arguments[0]) { return (this[a]); } }
|
||
}
|
||
else if (arguments.length == 2) {
|
||
for (var a in this) { if (this[a].name == arguments[1] && (arguments[0] == '*' || this[a].namespace == arguments[0])) { return (this[a]); } }
|
||
}
|
||
else {
|
||
throw ('attributes.get(): Invalid number of parameters');
|
||
}
|
||
}
|
||
});
|
||
elementStack.push({ name: elementName, localName: localName, getChildElementsByTagName: _getChildElementsByTagName, getElementsByTagNameNS: _getElementsByTagNameNS, getChildElementsByTagNameNS: _getChildElementsByTagNameNS, attributes: attributes, childNodes: [], nsTable: {} });
|
||
// Parse Attributes
|
||
if (x3.length > 0) {
|
||
var skip = false;
|
||
for (var j in x3) {
|
||
if (x3[j] == '/') {
|
||
// This is an empty Element
|
||
elementStack.peek().namespace = elementStack.peek().name == elementStack.peek().localName ? elementStack.getNamespace('*') : elementStack.getNamespace(elementStack.peek().name.substring(0, elementStack.peek().name.indexOf(':')));
|
||
elementStack.peek().textContent = '';
|
||
lastElement = elementStack.pop();
|
||
skip = true;
|
||
break;
|
||
}
|
||
var k = x3[j].indexOf('=');
|
||
if (k > 0) {
|
||
var attrName = x3[j].substring(0, k);
|
||
var attrValue = x3[j].substring(k + 2, x3[j].length - 1);
|
||
var attrNS = elementStack.getNamespace('*');
|
||
|
||
if (attrName == 'xmlns') {
|
||
elementStack.addNamespace('*', attrValue);
|
||
attrNS = attrValue;
|
||
} else if (attrName.startsWith('xmlns:')) {
|
||
elementStack.addNamespace(attrName.substring(6), attrValue);
|
||
} else {
|
||
var ax = attrName.split(':');
|
||
if (ax.length == 2) { attrName = ax[1]; attrNS = elementStack.getNamespace(ax[0]); }
|
||
}
|
||
var x = { name: attrName, value: attrValue }
|
||
if (attrNS != null) x.namespace = attrNS;
|
||
elementStack.peek().attributes.push(x);
|
||
}
|
||
}
|
||
if (skip) { continue; }
|
||
}
|
||
elementStack.peek().namespace = elementStack.peek().name == elementStack.peek().localName ? elementStack.getNamespace('*') : elementStack.getNamespace(elementStack.peek().name.substring(0, elementStack.peek().name.indexOf(':')));
|
||
if (x2[1]) { elementStack.peek().textContent = x2[1]; }
|
||
} else { lastElement = elementStack.pop(); }
|
||
}
|
||
}
|
||
return lastElement;
|
||
}
|
||
*/
|
||
|
||
return obj;
|
||
}
|
||
/**
|
||
* @fileoverview Intel(r) AMT Communication StackXX
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.2.0b
|
||
*/
|
||
|
||
/**
|
||
* Construct a AmtStackCreateService object, this ia the main Intel AMT communication stack.
|
||
* @constructor
|
||
*/
|
||
function AmtStackCreateService(wsmanStack) {
|
||
var obj = new Object();
|
||
obj.wsman = wsmanStack;
|
||
obj.pfx = ["http://intel.com/wbem/wscim/1/amt-schema/1/", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/", "http://intel.com/wbem/wscim/1/ips-schema/1/"];
|
||
obj.PendingEnums = [];
|
||
obj.PendingBatchOperations = 0;
|
||
obj.ActiveEnumsCount = 0;
|
||
obj.MaxActiveEnumsCount = 1; // Maximum number of enumerations that can be done at the same time.
|
||
obj.onProcessChanged = null;
|
||
var _MaxProcess = 0;
|
||
var _LastProcess = 0;
|
||
|
||
// Return the number of pending actions
|
||
obj.GetPendingActions = function () { return (obj.PendingEnums.length * 2) + (obj.ActiveEnumsCount) + obj.wsman.comm.PendingAjax.length + obj.wsman.comm.ActiveAjaxCount + obj.PendingBatchOperations; }
|
||
|
||
// Private Method, Update the current processing status, this gives the application an idea of what progress is being done by the WSMAN stack
|
||
function _up() {
|
||
var x = obj.GetPendingActions();
|
||
if (_MaxProcess < x) _MaxProcess = x;
|
||
if (obj.onProcessChanged != null && _LastProcess != x) {
|
||
//console.log("Process Old=" + _LastProcess + ", New=" + x + ", PEnums=" + obj.PendingEnums.length + ", AEnums=" + obj.ActiveEnumsCount + ", PAjax=" + obj.wsman.comm.PendingAjax.length + ", AAjax=" + obj.wsman.comm.ActiveAjaxCount + ", PBatch=" + obj.PendingBatchOperations);
|
||
_LastProcess = x;
|
||
obj.onProcessChanged(x, _MaxProcess);
|
||
}
|
||
if (x == 0) _MaxProcess = 0;
|
||
}
|
||
|
||
// Perform a WSMAN "SUBSCRIBE" operation.
|
||
obj.Subscribe = function (name, delivery, url, callback, tag, pri, selectors, opaque, user, pass) { obj.wsman.ExecSubscribe(obj.CompleteName(name), delivery, url, function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri, selectors, opaque, user, pass); _up(); }
|
||
|
||
// Perform a WSMAN "UNSUBSCRIBE" operation.
|
||
obj.UnSubscribe = function (name, callback, tag, pri, selectors) { obj.wsman.ExecUnSubscribe(obj.CompleteName(name), function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri, selectors); _up(); }
|
||
|
||
// Perform a WSMAN "GET" operation.
|
||
obj.Get = function (name, callback, tag, pri) { obj.wsman.ExecGet(obj.CompleteName(name), function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri); _up(); }
|
||
|
||
// Perform a WSMAN "PUT" operation.
|
||
obj.Put = function (name, putobj, callback, tag, pri, selectors) { obj.wsman.ExecPut(obj.CompleteName(name), putobj, function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri, selectors); _up(); }
|
||
|
||
// Perform a WSMAN "CREATE" operation.
|
||
obj.Create = function (name, putobj, callback, tag, pri) { obj.wsman.ExecCreate(obj.CompleteName(name), putobj, function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri); _up(); }
|
||
|
||
// Perform a WSMAN "DELETE" operation.
|
||
obj.Delete = function (name, putobj, callback, tag, pri) { obj.wsman.ExecDelete(obj.CompleteName(name), putobj, function (ws, resuri, response, xstatus) { _up(); callback(obj, name, response, xstatus, tag); }, 0, pri); _up(); }
|
||
|
||
// Perform a WSMAN method call operation.
|
||
obj.Exec = function (name, method, args, callback, tag, pri, selectors) { obj.wsman.ExecMethod(obj.CompleteName(name), method, args, function (ws, resuri, response, xstatus) { _up(); callback(obj, name, obj.CompleteExecResponse(response), xstatus, tag); }, 0, pri, selectors); _up(); }
|
||
|
||
// Perform a WSMAN method call operation.
|
||
obj.ExecWithXml = function (name, method, args, callback, tag, pri, selectors) { obj.wsman.ExecMethodXml(obj.CompleteName(name), method, execArgumentsToXml(args), function (ws, resuri, response, xstatus) { _up(); callback(obj, name, obj.CompleteExecResponse(response), xstatus, tag); }, 0, pri, selectors); _up(); }
|
||
|
||
// Perform a WSMAN "ENUMERATE" operation.
|
||
obj.Enum = function (name, callback, tag, pri) {
|
||
if (obj.ActiveEnumsCount < obj.MaxActiveEnumsCount) {
|
||
obj.ActiveEnumsCount++; obj.wsman.ExecEnum(obj.CompleteName(name), function (ws, resuri, response, xstatus, tag0) { _up(); _EnumStartSink(name, response, callback, resuri, xstatus, tag0); }, tag, pri);
|
||
} else {
|
||
obj.PendingEnums.push([name, callback, tag, pri]);
|
||
}
|
||
_up();
|
||
}
|
||
|
||
// Private method
|
||
function _EnumStartSink(name, response, callback, resuri, status, tag, pri) {
|
||
if (status != 200) { callback(obj, name, null, status, tag); _EnumDoNext(1); return; }
|
||
if (response == null || response.Header["Method"] != "EnumerateResponse" || !response.Body["EnumerationContext"]) { callback(obj, name, null, 603, tag); _EnumDoNext(1); return; }
|
||
var enumctx = response.Body["EnumerationContext"];
|
||
obj.wsman.ExecPull(resuri, enumctx, function (ws, resuri, response, xstatus) { _EnumContinueSink(name, response, callback, resuri, [], xstatus, tag, pri); });
|
||
}
|
||
|
||
// Private method
|
||
function _EnumContinueSink(name, response, callback, resuri, items, status, tag, pri) {
|
||
if (status != 200) { callback(obj, name, null, status, tag); _EnumDoNext(1); return; }
|
||
if (response == null || response.Header["Method"] != "PullResponse") { callback(obj, name, null, 604, tag); _EnumDoNext(1); return; }
|
||
for (var i in response.Body["Items"]) {
|
||
if (response.Body["Items"][i] instanceof Array) {
|
||
for (var j in response.Body["Items"][i]) { if (typeof response.Body["Items"][i][j] != 'function') { items.push(response.Body["Items"][i][j]); } }
|
||
} else {
|
||
if (typeof response.Body["Items"][i] != 'function') { items.push(response.Body["Items"][i]); }
|
||
}
|
||
}
|
||
if (response.Body["EnumerationContext"]) {
|
||
var enumctx = response.Body["EnumerationContext"];
|
||
obj.wsman.ExecPull(resuri, enumctx, function (ws, resuri, response, xstatus) { _EnumContinueSink(name, response, callback, resuri, items, xstatus, tag, 1); });
|
||
} else {
|
||
_EnumDoNext(1);
|
||
callback(obj, name, items, status, tag);
|
||
_up();
|
||
}
|
||
}
|
||
|
||
// Private method
|
||
function _EnumDoNext(dec) {
|
||
obj.ActiveEnumsCount -= dec;
|
||
if (obj.ActiveEnumsCount >= obj.MaxActiveEnumsCount || obj.PendingEnums.length == 0) return;
|
||
var x = obj.PendingEnums.shift();
|
||
obj.Enum(x[0], x[1], x[2]);
|
||
_EnumDoNext(0);
|
||
}
|
||
|
||
// Perform a batch of WSMAN "ENUM" operations.
|
||
obj.BatchEnum = function (batchname, names, callback, tag, continueOnError, pri) {
|
||
obj.PendingBatchOperations += (names.length * 2);
|
||
_BatchNextEnum(batchname, Clone(names), callback, tag, {}, continueOnError, pri); _up();
|
||
}
|
||
|
||
// Request each enum in the batch, stopping if something does not return status 200
|
||
function _BatchNextEnum(batchname, names, callback, tag, results, continueOnError, pri) {
|
||
obj.PendingBatchOperations -= 2;
|
||
var n = names.shift(), f = obj.Enum;
|
||
if (n[0] == '*') { f = obj.Get; n = n.substring(1); } // If the name starts with a star, do a GET instead of an ENUM. This will reduce round trips.
|
||
//console.log((f == obj.Get?'Get ':'Enum ') + n);
|
||
// Perform a GET/ENUM action
|
||
f(n, function (stack, name, responses, status, tag0) {
|
||
tag0[2][name] = { response: (responses==null?null:responses.Body), responses: responses, status: status };
|
||
if (tag0[1].length == 0 || status == 401 || (continueOnError != true && status != 200 && status != 400)) { obj.PendingBatchOperations -= (names.length * 2); _up(); callback(obj, batchname, tag0[2], status, tag); }
|
||
else { _up(); _BatchNextEnum(batchname, names, callback, tag, tag0[2], pri); }
|
||
}, [batchname, names, results], pri);
|
||
_up();
|
||
}
|
||
|
||
// Perform a batch of WSMAN "GET" operations.
|
||
obj.BatchGet = function (batchname, names, callback, tag, pri) {
|
||
_FetchNext({ name: batchname, names: names, callback: callback, current: 0, responses: {}, tag: tag, pri: pri }); _up();
|
||
}
|
||
|
||
// Private method
|
||
function _FetchNext(batch) {
|
||
if (batch.names.length <= batch.current) {
|
||
batch.callback(obj, batch.name, batch.responses, 200, batch.tag);
|
||
} else {
|
||
obj.wsman.ExecGet(obj.CompleteName(batch.names[batch.current]), function (ws, resuri, response, xstatus) { _Fetched(batch, response, xstatus); }, batch.pri);
|
||
batch.current++;
|
||
}
|
||
_up();
|
||
}
|
||
|
||
// Private method
|
||
function _Fetched(batch, response, status) {
|
||
if (response == null || status != 200) {
|
||
batch.callback(obj, batch.name, null, status, batch.tag);
|
||
} else {
|
||
batch.responses[response.Header["Method"]] = response;
|
||
_FetchNext(batch);
|
||
}
|
||
}
|
||
|
||
// Private method
|
||
obj.CompleteName = function(name) {
|
||
if (name.indexOf("AMT_") == 0) return obj.pfx[0] + name;
|
||
if (name.indexOf("CIM_") == 0) return obj.pfx[1] + name;
|
||
if (name.indexOf("IPS_") == 0) return obj.pfx[2] + name;
|
||
}
|
||
|
||
obj.CompleteExecResponse = function (resp) {
|
||
if (resp && resp != null && resp.Body && (resp.Body["ReturnValue"] != undefined)) { resp.Body.ReturnValueStr = obj.AmtStatusToStr(resp.Body["ReturnValue"]); }
|
||
return resp;
|
||
}
|
||
|
||
obj.RequestPowerStateChange = function (PowerState, callback_func) {
|
||
obj.CIM_PowerManagementService_RequestPowerStateChange(PowerState, "<Address xmlns=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"><ResourceURI xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</ResourceURI><SelectorSet xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\"><Selector Name=\"CreationClassName\">CIM_ComputerSystem</Selector><Selector Name=\"Name\">ManagedSystem</Selector></SelectorSet></ReferenceParameters>", null, null, callback_func);
|
||
}
|
||
|
||
obj.SetBootConfigRole = function (Role, callback_func) {
|
||
obj.CIM_BootService_SetBootConfigRole("<Address xmlns=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\"><ResourceURI xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootConfigSetting</ResourceURI><SelectorSet xmlns=\"http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd\"><Selector Name=\"InstanceID\">Intel(r) AMT: Boot Configuration 0</Selector></SelectorSet></ReferenceParameters>", Role, callback_func);
|
||
}
|
||
|
||
// Cancel all pending queries with given status
|
||
obj.CancelAllQueries = function (s) {
|
||
obj.wsman.CancelAllQueries(s);
|
||
}
|
||
|
||
// Auto generated methods
|
||
obj.AMT_AgentPresenceWatchdog_RegisterAgent = function (callback_func) { obj.Exec("AMT_AgentPresenceWatchdog", "RegisterAgent", {}, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdog_AssertPresence = function (SequenceNumber, callback_func) { obj.Exec("AMT_AgentPresenceWatchdog", "AssertPresence", { "SequenceNumber": SequenceNumber }, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdog_AssertShutdown = function (SequenceNumber, callback_func) { obj.Exec("AMT_AgentPresenceWatchdog", "AssertShutdown", { "SequenceNumber": SequenceNumber }, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdog_AddAction = function (OldState, NewState, EventOnTransition, ActionSd, ActionEac, callback_func, tag, pri, selectors) { obj.Exec("AMT_AgentPresenceWatchdog", "AddAction", { "OldState": OldState, "NewState": NewState, "EventOnTransition": EventOnTransition, "ActionSd": ActionSd, "ActionEac": ActionEac }, callback_func, tag, pri, selectors); }
|
||
obj.AMT_AgentPresenceWatchdog_DeleteAllActions = function (callback_func, tag, pri, selectors) { obj.Exec("AMT_AgentPresenceWatchdog", "DeleteAllActions", {}, callback_func, tag, pri, selectors); }
|
||
obj.AMT_AgentPresenceWatchdogAction_GetActionEac = function (callback_func) { obj.Exec("AMT_AgentPresenceWatchdogAction", "GetActionEac", {}, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdogVA_RegisterAgent = function (callback_func) { obj.Exec("AMT_AgentPresenceWatchdogVA", "RegisterAgent", {}, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdogVA_AssertPresence = function (SequenceNumber, callback_func) { obj.Exec("AMT_AgentPresenceWatchdogVA", "AssertPresence", { "SequenceNumber": SequenceNumber }, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdogVA_AssertShutdown = function (SequenceNumber, callback_func) { obj.Exec("AMT_AgentPresenceWatchdogVA", "AssertShutdown", { "SequenceNumber": SequenceNumber }, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdogVA_AddAction = function (OldState, NewState, EventOnTransition, ActionSd, ActionEac, callback_func) { obj.Exec("AMT_AgentPresenceWatchdogVA", "AddAction", { "OldState": OldState, "NewState": NewState, "EventOnTransition": EventOnTransition, "ActionSd": ActionSd, "ActionEac": ActionEac }, callback_func); }
|
||
obj.AMT_AgentPresenceWatchdogVA_DeleteAllActions = function (_method_dummy, callback_func) { obj.Exec("AMT_AgentPresenceWatchdogVA", "DeleteAllActions", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_AuditLog_ClearLog = function (callback_func) { obj.Exec("AMT_AuditLog", "ClearLog", {}, callback_func); }
|
||
obj.AMT_AuditLog_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("AMT_AuditLog", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.AMT_AuditLog_ReadRecords = function (StartIndex, callback_func, tag) { obj.Exec("AMT_AuditLog", "ReadRecords", { "StartIndex": StartIndex }, callback_func, tag); }
|
||
obj.AMT_AuditLog_SetAuditLock = function (LockTimeoutInSeconds, Flag, Handle, callback_func) { obj.Exec("AMT_AuditLog", "SetAuditLock", { "LockTimeoutInSeconds": LockTimeoutInSeconds, "Flag": Flag, "Handle": Handle }, callback_func); }
|
||
obj.AMT_AuditLog_ExportAuditLogSignature = function (SigningMechanism, callback_func) { obj.Exec("AMT_AuditLog", "ExportAuditLogSignature", { "SigningMechanism": SigningMechanism }, callback_func); }
|
||
obj.AMT_AuditLog_SetSigningKeyMaterial = function (SigningMechanismType, SigningKey, LengthOfCertificates, Certificates, callback_func) { obj.Exec("AMT_AuditLog", "SetSigningKeyMaterial", { "SigningMechanismType": SigningMechanismType, "SigningKey": SigningKey, "LengthOfCertificates": LengthOfCertificates, "Certificates": Certificates }, callback_func); }
|
||
obj.AMT_AuditPolicyRule_SetAuditPolicy = function (Enable, AuditedAppID, EventID, PolicyType, callback_func) { obj.Exec("AMT_AuditPolicyRule", "SetAuditPolicy", { "Enable": Enable, "AuditedAppID": AuditedAppID, "EventID": EventID, "PolicyType": PolicyType }, callback_func); }
|
||
obj.AMT_AuditPolicyRule_SetAuditPolicyBulk = function (Enable, AuditedAppID, EventID, PolicyType, callback_func) { obj.Exec("AMT_AuditPolicyRule", "SetAuditPolicyBulk", { "Enable": Enable, "AuditedAppID": AuditedAppID, "EventID": EventID, "PolicyType": PolicyType }, callback_func); }
|
||
obj.AMT_AuthorizationService_AddUserAclEntryEx = function (DigestUsername, DigestPassword, KerberosUserSid, AccessPermission, Realms, callback_func) { obj.Exec("AMT_AuthorizationService", "AddUserAclEntryEx", { "DigestUsername": DigestUsername, "DigestPassword": DigestPassword, "KerberosUserSid": KerberosUserSid, "AccessPermission": AccessPermission, "Realms": Realms }, callback_func); }
|
||
obj.AMT_AuthorizationService_EnumerateUserAclEntries = function (StartIndex, callback_func) { obj.Exec("AMT_AuthorizationService", "EnumerateUserAclEntries", { "StartIndex": StartIndex }, callback_func); }
|
||
obj.AMT_AuthorizationService_GetUserAclEntryEx = function (Handle, callback_func, tag) { obj.Exec("AMT_AuthorizationService", "GetUserAclEntryEx", { "Handle": Handle }, callback_func, tag); }
|
||
obj.AMT_AuthorizationService_UpdateUserAclEntryEx = function (Handle, DigestUsername, DigestPassword, KerberosUserSid, AccessPermission, Realms, callback_func) { obj.Exec("AMT_AuthorizationService", "UpdateUserAclEntryEx", { "Handle": Handle, "DigestUsername": DigestUsername, "DigestPassword": DigestPassword, "KerberosUserSid": KerberosUserSid, "AccessPermission": AccessPermission, "Realms": Realms }, callback_func); }
|
||
obj.AMT_AuthorizationService_RemoveUserAclEntry = function (Handle, callback_func) { obj.Exec("AMT_AuthorizationService", "RemoveUserAclEntry", { "Handle": Handle }, callback_func); }
|
||
obj.AMT_AuthorizationService_SetAdminAclEntryEx = function (Username, DigestPassword, callback_func) { obj.Exec("AMT_AuthorizationService", "SetAdminAclEntryEx", { "Username": Username, "DigestPassword": DigestPassword }, callback_func); }
|
||
obj.AMT_AuthorizationService_GetAdminAclEntry = function (callback_func) { obj.Exec("AMT_AuthorizationService", "GetAdminAclEntry", {}, callback_func); }
|
||
obj.AMT_AuthorizationService_GetAdminAclEntryStatus = function (callback_func) { obj.Exec("AMT_AuthorizationService", "GetAdminAclEntryStatus", {}, callback_func); }
|
||
obj.AMT_AuthorizationService_GetAdminNetAclEntryStatus = function (callback_func) { obj.Exec("AMT_AuthorizationService", "GetAdminNetAclEntryStatus", {}, callback_func); }
|
||
obj.AMT_AuthorizationService_SetAclEnabledState = function (Handle, Enabled, callback_func, tag) { obj.Exec("AMT_AuthorizationService", "SetAclEnabledState", { "Handle": Handle, "Enabled": Enabled }, callback_func, tag); }
|
||
obj.AMT_AuthorizationService_GetAclEnabledState = function (Handle, callback_func, tag) { obj.Exec("AMT_AuthorizationService", "GetAclEnabledState", { "Handle": Handle }, callback_func, tag); }
|
||
obj.AMT_EndpointAccessControlService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("AMT_EndpointAccessControlService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.AMT_EndpointAccessControlService_GetPosture = function (PostureType, callback_func) { obj.Exec("AMT_EndpointAccessControlService", "GetPosture", { "PostureType": PostureType }, callback_func); }
|
||
obj.AMT_EndpointAccessControlService_GetPostureHash = function (PostureType, callback_func) { obj.Exec("AMT_EndpointAccessControlService", "GetPostureHash", { "PostureType": PostureType }, callback_func); }
|
||
obj.AMT_EndpointAccessControlService_UpdatePostureState = function (UpdateType, callback_func) { obj.Exec("AMT_EndpointAccessControlService", "UpdatePostureState", { "UpdateType": UpdateType }, callback_func); }
|
||
obj.AMT_EndpointAccessControlService_GetEacOptions = function (callback_func) { obj.Exec("AMT_EndpointAccessControlService", "GetEacOptions", {}, callback_func); }
|
||
obj.AMT_EndpointAccessControlService_SetEacOptions = function (EacVendors, PostureHashAlgorithm, callback_func) { obj.Exec("AMT_EndpointAccessControlService", "SetEacOptions", { "EacVendors": EacVendors, "PostureHashAlgorithm": PostureHashAlgorithm }, callback_func); }
|
||
obj.AMT_EnvironmentDetectionSettingData_SetSystemDefensePolicy = function (Policy, callback_func) { obj.Exec("AMT_EnvironmentDetectionSettingData", "SetSystemDefensePolicy", { "Policy": Policy }, callback_func); }
|
||
obj.AMT_EnvironmentDetectionSettingData_EnableVpnRouting = function (Enable, callback_func) { obj.Exec("AMT_EnvironmentDetectionSettingData", "EnableVpnRouting", { "Enable": Enable }, callback_func); }
|
||
obj.AMT_EthernetPortSettings_SetLinkPreference = function (LinkPreference, Timeout, callback_func) { obj.Exec("AMT_EthernetPortSettings", "SetLinkPreference", { "LinkPreference": LinkPreference, "Timeout": Timeout }, callback_func); }
|
||
obj.AMT_HeuristicPacketFilterStatistics_ResetSelectedStats = function (SelectedStatistics, callback_func) { obj.Exec("AMT_HeuristicPacketFilterStatistics", "ResetSelectedStats", { "SelectedStatistics": SelectedStatistics }, callback_func); }
|
||
obj.AMT_KerberosSettingData_GetCredentialCacheState = function (callback_func) { obj.Exec("AMT_KerberosSettingData", "GetCredentialCacheState", {}, callback_func); }
|
||
obj.AMT_KerberosSettingData_SetCredentialCacheState = function (Enable, callback_func) { obj.Exec("AMT_KerberosSettingData", "SetCredentialCacheState", { "Enable": Enable }, callback_func); }
|
||
obj.AMT_MessageLog_CancelIteration = function (IterationIdentifier, callback_func) { obj.Exec("AMT_MessageLog", "CancelIteration", { "IterationIdentifier": IterationIdentifier }, callback_func); }
|
||
obj.AMT_MessageLog_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("AMT_MessageLog", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.AMT_MessageLog_ClearLog = function (callback_func) { obj.Exec("AMT_MessageLog", "ClearLog", { }, callback_func); }
|
||
obj.AMT_MessageLog_GetRecords = function (IterationIdentifier, MaxReadRecords, callback_func, tag) { obj.Exec("AMT_MessageLog", "GetRecords", { "IterationIdentifier": IterationIdentifier, "MaxReadRecords": MaxReadRecords }, callback_func, tag); }
|
||
obj.AMT_MessageLog_GetRecord = function (IterationIdentifier, PositionToNext, callback_func) { obj.Exec("AMT_MessageLog", "GetRecord", { "IterationIdentifier": IterationIdentifier, "PositionToNext": PositionToNext }, callback_func); }
|
||
obj.AMT_MessageLog_PositionAtRecord = function (IterationIdentifier, MoveAbsolute, RecordNumber, callback_func) { obj.Exec("AMT_MessageLog", "PositionAtRecord", { "IterationIdentifier": IterationIdentifier, "MoveAbsolute": MoveAbsolute, "RecordNumber": RecordNumber }, callback_func); }
|
||
obj.AMT_MessageLog_PositionToFirstRecord = function (callback_func, tag) { obj.Exec("AMT_MessageLog", "PositionToFirstRecord", {}, callback_func, tag); }
|
||
obj.AMT_MessageLog_FreezeLog = function (Freeze, callback_func) { obj.Exec("AMT_MessageLog", "FreezeLog", { "Freeze": Freeze }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_AddCRL = function (Url, SerialNumbers, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "AddCRL", { "Url": Url, "SerialNumbers": SerialNumbers }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_ResetCRLList = function (_method_dummy, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "ResetCRLList", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_AddCertificate = function (CertificateBlob, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "AddCertificate", { "CertificateBlob": CertificateBlob }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_AddTrustedRootCertificate = function (CertificateBlob, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "AddTrustedRootCertificate", { "CertificateBlob": CertificateBlob }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_AddKey = function (KeyBlob, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "AddKey", { "KeyBlob": KeyBlob }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_GeneratePKCS10Request = function (KeyPair, DNName, Usage, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "GeneratePKCS10Request", { "KeyPair": KeyPair, "DNName": DNName, "Usage": Usage }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_GeneratePKCS10RequestEx = function (KeyPair, SigningAlgorithm, NullSignedCertificateRequest, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "GeneratePKCS10RequestEx", { "KeyPair": KeyPair, "SigningAlgorithm": SigningAlgorithm, "NullSignedCertificateRequest": NullSignedCertificateRequest }, callback_func); }
|
||
obj.AMT_PublicKeyManagementService_GenerateKeyPair = function (KeyAlgorithm, KeyLength, callback_func) { obj.Exec("AMT_PublicKeyManagementService", "GenerateKeyPair", { "KeyAlgorithm": KeyAlgorithm, "KeyLength": KeyLength }, callback_func); }
|
||
obj.AMT_RedirectionService_RequestStateChange = function (RequestedState, callback_func) { obj.Exec("AMT_RedirectionService", "RequestStateChange", { "RequestedState": RequestedState }, callback_func); }
|
||
obj.AMT_RedirectionService_TerminateSession = function (SessionType, callback_func) { obj.Exec("AMT_RedirectionService", "TerminateSession", { "SessionType": SessionType }, callback_func); }
|
||
obj.AMT_RemoteAccessService_AddMpServer = function (AccessInfo, InfoFormat, Port, AuthMethod, Certificate, Username, Password, CN, callback_func) { obj.Exec("AMT_RemoteAccessService", "AddMpServer", { "AccessInfo": AccessInfo, "InfoFormat": InfoFormat, "Port": Port, "AuthMethod": AuthMethod, "Certificate": Certificate, "Username": Username, "Password": Password, "CN": CN }, callback_func); }
|
||
obj.AMT_RemoteAccessService_AddRemoteAccessPolicyRule = function (Trigger, TunnelLifeTime, ExtendedData, MpServer, callback_func) { obj.Exec("AMT_RemoteAccessService", "AddRemoteAccessPolicyRule", { "Trigger": Trigger, "TunnelLifeTime": TunnelLifeTime, "ExtendedData": ExtendedData, "MpServer": MpServer }, callback_func); }
|
||
obj.AMT_RemoteAccessService_CloseRemoteAccessConnection = function (_method_dummy, callback_func) { obj.Exec("AMT_RemoteAccessService", "CloseRemoteAccessConnection", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_CommitChanges = function (_method_dummy, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "CommitChanges", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_Unprovision = function (ProvisioningMode, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "Unprovision", { "ProvisioningMode": ProvisioningMode }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_PartialUnprovision = function (_method_dummy, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "PartialUnprovision", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_ResetFlashWearOutProtection = function (_method_dummy, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "ResetFlashWearOutProtection", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_ExtendProvisioningPeriod = function (Duration, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "ExtendProvisioningPeriod", { "Duration": Duration }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_SetMEBxPassword = function (Password, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "SetMEBxPassword", { "Password": Password }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_SetTLSPSK = function (PID, PPS, callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "SetTLSPSK", { "PID": PID, "PPS": PPS }, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_GetProvisioningAuditRecord = function (callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "GetProvisioningAuditRecord", {}, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_GetUuid = function (callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "GetUuid", {}, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_GetUnprovisionBlockingComponents = function (callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "GetUnprovisionBlockingComponents", {}, callback_func); }
|
||
obj.AMT_SetupAndConfigurationService_GetProvisioningAuditRecordV2 = function (callback_func) { obj.Exec("AMT_SetupAndConfigurationService", "GetProvisioningAuditRecordV2", {}, callback_func); }
|
||
obj.AMT_SystemDefensePolicy_GetTimeout = function (callback_func) { obj.Exec("AMT_SystemDefensePolicy", "GetTimeout", {}, callback_func); }
|
||
obj.AMT_SystemDefensePolicy_SetTimeout = function (Timeout, callback_func) { obj.Exec("AMT_SystemDefensePolicy", "SetTimeout", { "Timeout": Timeout }, callback_func); }
|
||
obj.AMT_SystemDefensePolicy_UpdateStatistics = function (NetworkInterface, ResetOnRead, callback_func, tag, pri, selectors) { obj.Exec("AMT_SystemDefensePolicy", "UpdateStatistics", { "NetworkInterface": NetworkInterface, "ResetOnRead": ResetOnRead }, callback_func, tag, pri, selectors); }
|
||
obj.AMT_SystemPowerScheme_SetPowerScheme = function (callback_func, schemeInstanceId, tag) { obj.Exec("AMT_SystemPowerScheme", "SetPowerScheme", {}, callback_func, tag, 0, { "InstanceID": schemeInstanceId }); }
|
||
obj.AMT_TimeSynchronizationService_GetLowAccuracyTimeSynch = function (callback_func, tag) { obj.Exec("AMT_TimeSynchronizationService", "GetLowAccuracyTimeSynch", {}, callback_func, tag); }
|
||
obj.AMT_TimeSynchronizationService_SetHighAccuracyTimeSynch = function (Ta0, Tm1, Tm2, callback_func, tag) { obj.Exec("AMT_TimeSynchronizationService", "SetHighAccuracyTimeSynch", { "Ta0": Ta0, "Tm1": Tm1, "Tm2": Tm2 }, callback_func, tag); }
|
||
obj.AMT_UserInitiatedConnectionService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("AMT_UserInitiatedConnectionService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.AMT_WebUIService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("AMT_WebUIService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.AMT_WiFiPortConfigurationService_AddWiFiSettings = function (WiFiEndpoint, WiFiEndpointSettingsInput, IEEE8021xSettingsInput, ClientCredential, CACredential, callback_func) { obj.ExecWithXml("AMT_WiFiPortConfigurationService", "AddWiFiSettings", { "WiFiEndpoint": WiFiEndpoint, "WiFiEndpointSettingsInput": WiFiEndpointSettingsInput, "IEEE8021xSettingsInput": IEEE8021xSettingsInput, "ClientCredential": ClientCredential, "CACredential": CACredential }, callback_func); }
|
||
obj.AMT_WiFiPortConfigurationService_UpdateWiFiSettings = function (WiFiEndpointSettings, WiFiEndpointSettingsInput, IEEE8021xSettingsInput, ClientCredential, CACredential, callback_func) { obj.ExecWithXml("AMT_WiFiPortConfigurationService", "UpdateWiFiSettings", { "WiFiEndpointSettings": WiFiEndpointSettings, "WiFiEndpointSettingsInput": WiFiEndpointSettingsInput, "IEEE8021xSettingsInput": IEEE8021xSettingsInput, "ClientCredential": ClientCredential, "CACredential": CACredential }, callback_func); }
|
||
obj.AMT_WiFiPortConfigurationService_DeleteAllITProfiles = function (_method_dummy, callback_func) { obj.Exec("AMT_WiFiPortConfigurationService", "DeleteAllITProfiles", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.AMT_WiFiPortConfigurationService_DeleteAllUserProfiles = function (_method_dummy, callback_func) { obj.Exec("AMT_WiFiPortConfigurationService", "DeleteAllUserProfiles", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.CIM_Account_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_Account", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_AccountManagementService_CreateAccount = function (System, AccountTemplate, callback_func) { obj.Exec("CIM_AccountManagementService", "CreateAccount", { "System": System, "AccountTemplate": AccountTemplate }, callback_func); }
|
||
obj.CIM_BootConfigSetting_ChangeBootOrder = function (Source, callback_func) { obj.Exec("CIM_BootConfigSetting", "ChangeBootOrder", { "Source": Source }, callback_func); }
|
||
obj.CIM_BootService_SetBootConfigRole = function (BootConfigSetting, Role, callback_func) { obj.Exec("CIM_BootService", "SetBootConfigRole", { "BootConfigSetting": BootConfigSetting, "Role": Role }, callback_func, 0, 1); }
|
||
obj.CIM_Card_ConnectorPower = function (Connector, PoweredOn, callback_func) { obj.Exec("CIM_Card", "ConnectorPower", { "Connector": Connector, "PoweredOn": PoweredOn }, callback_func); }
|
||
obj.CIM_Card_IsCompatible = function (ElementToCheck, callback_func) { obj.Exec("CIM_Card", "IsCompatible", { "ElementToCheck": ElementToCheck }, callback_func); }
|
||
obj.CIM_Chassis_IsCompatible = function (ElementToCheck, callback_func) { obj.Exec("CIM_Chassis", "IsCompatible", { "ElementToCheck": ElementToCheck }, callback_func); }
|
||
obj.CIM_Fan_SetSpeed = function (DesiredSpeed, callback_func) { obj.Exec("CIM_Fan", "SetSpeed", { "DesiredSpeed": DesiredSpeed }, callback_func); }
|
||
obj.CIM_KVMRedirectionSAP_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_KVMRedirectionSAP", "RequestStateChange", { "RequestedState": RequestedState/*, "TimeoutPeriod": TimeoutPeriod */}, callback_func); }
|
||
obj.CIM_MediaAccessDevice_LockMedia = function (Lock, callback_func) { obj.Exec("CIM_MediaAccessDevice", "LockMedia", { "Lock": Lock }, callback_func); }
|
||
obj.CIM_MediaAccessDevice_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_MediaAccessDevice", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_MediaAccessDevice_Reset = function (callback_func) { obj.Exec("CIM_MediaAccessDevice", "Reset", {}, callback_func); }
|
||
obj.CIM_MediaAccessDevice_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_MediaAccessDevice", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_MediaAccessDevice_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_MediaAccessDevice", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_MediaAccessDevice_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_MediaAccessDevice", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_MediaAccessDevice_SaveProperties = function (callback_func) { obj.Exec("CIM_MediaAccessDevice", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_MediaAccessDevice_RestoreProperties = function (callback_func) { obj.Exec("CIM_MediaAccessDevice", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_MediaAccessDevice_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_MediaAccessDevice", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_PhysicalFrame_IsCompatible = function (ElementToCheck, callback_func) { obj.Exec("CIM_PhysicalFrame", "IsCompatible", { "ElementToCheck": ElementToCheck }, callback_func); }
|
||
obj.CIM_PhysicalPackage_IsCompatible = function (ElementToCheck, callback_func) { obj.Exec("CIM_PhysicalPackage", "IsCompatible", { "ElementToCheck": ElementToCheck }, callback_func); }
|
||
obj.CIM_PowerManagementService_RequestPowerStateChange = function (PowerState, ManagedElement, Time, TimeoutPeriod, callback_func) { obj.Exec("CIM_PowerManagementService", "RequestPowerStateChange", { "PowerState": PowerState, "ManagedElement": ManagedElement, "Time": Time, "TimeoutPeriod": TimeoutPeriod }, callback_func, 0, 1); }
|
||
obj.CIM_PowerSupply_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_PowerSupply", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_PowerSupply_Reset = function (callback_func) { obj.Exec("CIM_PowerSupply", "Reset", {}, callback_func); }
|
||
obj.CIM_PowerSupply_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_PowerSupply", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_PowerSupply_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_PowerSupply", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_PowerSupply_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_PowerSupply", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_PowerSupply_SaveProperties = function (callback_func) { obj.Exec("CIM_PowerSupply", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_PowerSupply_RestoreProperties = function (callback_func) { obj.Exec("CIM_PowerSupply", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_PowerSupply_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_PowerSupply", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_Processor_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_Processor", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_Processor_Reset = function (callback_func) { obj.Exec("CIM_Processor", "Reset", {}, callback_func); }
|
||
obj.CIM_Processor_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_Processor", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_Processor_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_Processor", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_Processor_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_Processor", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_Processor_SaveProperties = function (callback_func) { obj.Exec("CIM_Processor", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_Processor_RestoreProperties = function (callback_func) { obj.Exec("CIM_Processor", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_Processor_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_Processor", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_RecordLog_ClearLog = function (callback_func) { obj.Exec("CIM_RecordLog", "ClearLog", {}, callback_func); }
|
||
obj.CIM_RecordLog_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_RecordLog", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_RedirectionService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_RedirectionService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_Sensor_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_Sensor", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_Sensor_Reset = function (callback_func) { obj.Exec("CIM_Sensor", "Reset", {}, callback_func); }
|
||
obj.CIM_Sensor_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_Sensor", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_Sensor_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_Sensor", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_Sensor_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_Sensor", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_Sensor_SaveProperties = function (callback_func) { obj.Exec("CIM_Sensor", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_Sensor_RestoreProperties = function (callback_func) { obj.Exec("CIM_Sensor", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_Sensor_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_Sensor", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_StatisticalData_ResetSelectedStats = function (SelectedStatistics, callback_func) { obj.Exec("CIM_StatisticalData", "ResetSelectedStats", { "SelectedStatistics": SelectedStatistics }, callback_func); }
|
||
obj.CIM_Watchdog_KeepAlive = function (callback_func) { obj.Exec("CIM_Watchdog", "KeepAlive", {}, callback_func); }
|
||
obj.CIM_Watchdog_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_Watchdog", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_Watchdog_Reset = function (callback_func) { obj.Exec("CIM_Watchdog", "Reset", {}, callback_func); }
|
||
obj.CIM_Watchdog_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_Watchdog", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_Watchdog_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_Watchdog", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_Watchdog_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_Watchdog", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_Watchdog_SaveProperties = function (callback_func) { obj.Exec("CIM_Watchdog", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_Watchdog_RestoreProperties = function (callback_func) { obj.Exec("CIM_Watchdog", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_Watchdog_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_Watchdog", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.CIM_WiFiPort_SetPowerState = function (PowerState, Time, callback_func) { obj.Exec("CIM_WiFiPort", "SetPowerState", { "PowerState": PowerState, "Time": Time }, callback_func); }
|
||
obj.CIM_WiFiPort_Reset = function (callback_func) { obj.Exec("CIM_WiFiPort", "Reset", {}, callback_func); }
|
||
obj.CIM_WiFiPort_EnableDevice = function (Enabled, callback_func) { obj.Exec("CIM_WiFiPort", "EnableDevice", { "Enabled": Enabled }, callback_func); }
|
||
obj.CIM_WiFiPort_OnlineDevice = function (Online, callback_func) { obj.Exec("CIM_WiFiPort", "OnlineDevice", { "Online": Online }, callback_func); }
|
||
obj.CIM_WiFiPort_QuiesceDevice = function (Quiesce, callback_func) { obj.Exec("CIM_WiFiPort", "QuiesceDevice", { "Quiesce": Quiesce }, callback_func); }
|
||
obj.CIM_WiFiPort_SaveProperties = function (callback_func) { obj.Exec("CIM_WiFiPort", "SaveProperties", {}, callback_func); }
|
||
obj.CIM_WiFiPort_RestoreProperties = function (callback_func) { obj.Exec("CIM_WiFiPort", "RestoreProperties", {}, callback_func); }
|
||
obj.CIM_WiFiPort_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("CIM_WiFiPort", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.IPS_HostBasedSetupService_Setup = function (NetAdminPassEncryptionType, NetworkAdminPassword, McNonce, Certificate, SigningAlgorithm, DigitalSignature, callback_func) { obj.Exec("IPS_HostBasedSetupService", "Setup", { "NetAdminPassEncryptionType": NetAdminPassEncryptionType, "NetworkAdminPassword": NetworkAdminPassword, "McNonce": McNonce, "Certificate": Certificate, "SigningAlgorithm": SigningAlgorithm, "DigitalSignature": DigitalSignature }, callback_func); }
|
||
obj.IPS_HostBasedSetupService_AddNextCertInChain = function (NextCertificate, IsLeafCertificate, IsRootCertificate, callback_func) { obj.Exec("IPS_HostBasedSetupService", "AddNextCertInChain", { "NextCertificate": NextCertificate, "IsLeafCertificate": IsLeafCertificate, "IsRootCertificate": IsRootCertificate }, callback_func); }
|
||
obj.IPS_HostBasedSetupService_AdminSetup = function (NetAdminPassEncryptionType, NetworkAdminPassword, McNonce, SigningAlgorithm, DigitalSignature, callback_func) { obj.Exec("IPS_HostBasedSetupService", "AdminSetup", { "NetAdminPassEncryptionType": NetAdminPassEncryptionType, "NetworkAdminPassword": NetworkAdminPassword, "McNonce": McNonce, "SigningAlgorithm": SigningAlgorithm, "DigitalSignature": DigitalSignature }, callback_func); }
|
||
obj.IPS_HostBasedSetupService_UpgradeClientToAdmin = function (McNonce, SigningAlgorithm, DigitalSignature, callback_func) { obj.Exec("IPS_HostBasedSetupService", "UpgradeClientToAdmin", { "McNonce": McNonce, "SigningAlgorithm": SigningAlgorithm, "DigitalSignature": DigitalSignature }, callback_func); }
|
||
obj.IPS_HostBasedSetupService_DisableClientControlMode = function (_method_dummy, callback_func) { obj.Exec("IPS_HostBasedSetupService", "DisableClientControlMode", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.IPS_KVMRedirectionSettingData_TerminateSession = function (callback_func) { obj.Exec("IPS_KVMRedirectionSettingData", "TerminateSession", {}, callback_func); }
|
||
obj.IPS_KVMRedirectionSettingData_DataChannelRead = function (callback_func) { obj.Exec("IPS_KVMRedirectionSettingData", "DataChannelRead", {}, callback_func); }
|
||
obj.IPS_KVMRedirectionSettingData_DataChannelWrite = function (Data, callback_func) { obj.Exec("IPS_KVMRedirectionSettingData", "DataChannelWrite", { "DataMessage": Data }, callback_func); }
|
||
obj.IPS_OptInService_StartOptIn = function (callback_func) { obj.Exec("IPS_OptInService", "StartOptIn", {}, callback_func); }
|
||
obj.IPS_OptInService_CancelOptIn = function (callback_func) { obj.Exec("IPS_OptInService", "CancelOptIn", {}, callback_func); }
|
||
obj.IPS_OptInService_SendOptInCode = function (OptInCode, callback_func) { obj.Exec("IPS_OptInService", "SendOptInCode", { "OptInCode": OptInCode }, callback_func); }
|
||
obj.IPS_OptInService_StartService = function (callback_func) { obj.Exec("IPS_OptInService", "StartService", {}, callback_func); }
|
||
obj.IPS_OptInService_StopService = function (callback_func) { obj.Exec("IPS_OptInService", "StopService", {}, callback_func); }
|
||
obj.IPS_OptInService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("IPS_OptInService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.IPS_ProvisioningRecordLog_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("IPS_ProvisioningRecordLog", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
obj.IPS_ProvisioningRecordLog_ClearLog = function (_method_dummy, callback_func) { obj.Exec("IPS_ProvisioningRecordLog", "ClearLog", { "_method_dummy": _method_dummy }, callback_func); }
|
||
obj.IPS_SecIOService_RequestStateChange = function (RequestedState, TimeoutPeriod, callback_func) { obj.Exec("IPS_SecIOService", "RequestStateChange", { "RequestedState": RequestedState, "TimeoutPeriod": TimeoutPeriod }, callback_func); }
|
||
|
||
obj.AmtStatusToStr = function (code) { if (obj.AmtStatusCodes[code]) return obj.AmtStatusCodes[code]; else return "UNKNOWN_ERROR" }
|
||
obj.AmtStatusCodes = {
|
||
0x0000: "SUCCESS",
|
||
0x0001: "INTERNAL_ERROR",
|
||
0x0002: "NOT_READY",
|
||
0x0003: "INVALID_PT_MODE",
|
||
0x0004: "INVALID_MESSAGE_LENGTH",
|
||
0x0005: "TABLE_FINGERPRINT_NOT_AVAILABLE",
|
||
0x0006: "INTEGRITY_CHECK_FAILED",
|
||
0x0007: "UNSUPPORTED_ISVS_VERSION",
|
||
0x0008: "APPLICATION_NOT_REGISTERED",
|
||
0x0009: "INVALID_REGISTRATION_DATA",
|
||
0x000A: "APPLICATION_DOES_NOT_EXIST",
|
||
0x000B: "NOT_ENOUGH_STORAGE",
|
||
0x000C: "INVALID_NAME",
|
||
0x000D: "BLOCK_DOES_NOT_EXIST",
|
||
0x000E: "INVALID_BYTE_OFFSET",
|
||
0x000F: "INVALID_BYTE_COUNT",
|
||
0x0010: "NOT_PERMITTED",
|
||
0x0011: "NOT_OWNER",
|
||
0x0012: "BLOCK_LOCKED_BY_OTHER",
|
||
0x0013: "BLOCK_NOT_LOCKED",
|
||
0x0014: "INVALID_GROUP_PERMISSIONS",
|
||
0x0015: "GROUP_DOES_NOT_EXIST",
|
||
0x0016: "INVALID_MEMBER_COUNT",
|
||
0x0017: "MAX_LIMIT_REACHED",
|
||
0x0018: "INVALID_AUTH_TYPE",
|
||
0x0019: "AUTHENTICATION_FAILED",
|
||
0x001A: "INVALID_DHCP_MODE",
|
||
0x001B: "INVALID_IP_ADDRESS",
|
||
0x001C: "INVALID_DOMAIN_NAME",
|
||
0x001D: "UNSUPPORTED_VERSION",
|
||
0x001E: "REQUEST_UNEXPECTED",
|
||
0x001F: "INVALID_TABLE_TYPE",
|
||
0x0020: "INVALID_PROVISIONING_STATE",
|
||
0x0021: "UNSUPPORTED_OBJECT",
|
||
0x0022: "INVALID_TIME",
|
||
0x0023: "INVALID_INDEX",
|
||
0x0024: "INVALID_PARAMETER",
|
||
0x0025: "INVALID_NETMASK",
|
||
0x0026: "FLASH_WRITE_LIMIT_EXCEEDED",
|
||
0x0027: "INVALID_IMAGE_LENGTH",
|
||
0x0028: "INVALID_IMAGE_SIGNATURE",
|
||
0x0029: "PROPOSE_ANOTHER_VERSION",
|
||
0x002A: "INVALID_PID_FORMAT",
|
||
0x002B: "INVALID_PPS_FORMAT",
|
||
0x002C: "BIST_COMMAND_BLOCKED",
|
||
0x002D: "CONNECTION_FAILED",
|
||
0x002E: "CONNECTION_TOO_MANY",
|
||
0x002F: "RNG_GENERATION_IN_PROGRESS",
|
||
0x0030: "RNG_NOT_READY",
|
||
0x0031: "CERTIFICATE_NOT_READY",
|
||
0x0400: "DISABLED_BY_POLICY",
|
||
0x0800: "NETWORK_IF_ERROR_BASE",
|
||
0x0801: "UNSUPPORTED_OEM_NUMBER",
|
||
0x0802: "UNSUPPORTED_BOOT_OPTION",
|
||
0x0803: "INVALID_COMMAND",
|
||
0x0804: "INVALID_SPECIAL_COMMAND",
|
||
0x0805: "INVALID_HANDLE",
|
||
0x0806: "INVALID_PASSWORD",
|
||
0x0807: "INVALID_REALM",
|
||
0x0808: "STORAGE_ACL_ENTRY_IN_USE",
|
||
0x0809: "DATA_MISSING",
|
||
0x080A: "DUPLICATE",
|
||
0x080B: "EVENTLOG_FROZEN",
|
||
0x080C: "PKI_MISSING_KEYS",
|
||
0x080D: "PKI_GENERATING_KEYS",
|
||
0x080E: "INVALID_KEY",
|
||
0x080F: "INVALID_CERT",
|
||
0x0810: "CERT_KEY_NOT_MATCH",
|
||
0x0811: "MAX_KERB_DOMAIN_REACHED",
|
||
0x0812: "UNSUPPORTED",
|
||
0x0813: "INVALID_PRIORITY",
|
||
0x0814: "NOT_FOUND",
|
||
0x0815: "INVALID_CREDENTIALS",
|
||
0x0816: "INVALID_PASSPHRASE",
|
||
0x0818: "NO_ASSOCIATION",
|
||
0x081B: "AUDIT_FAIL",
|
||
0x081C: "BLOCKING_COMPONENT",
|
||
0x0821: "USER_CONSENT_REQUIRED",
|
||
0x1000: "APP_INTERNAL_ERROR",
|
||
0x1001: "NOT_INITIALIZED",
|
||
0x1002: "LIB_VERSION_UNSUPPORTED",
|
||
0x1003: "INVALID_PARAM",
|
||
0x1004: "RESOURCES",
|
||
0x1005: "HARDWARE_ACCESS_ERROR",
|
||
0x1006: "REQUESTOR_NOT_REGISTERED",
|
||
0x1007: "NETWORK_ERROR",
|
||
0x1008: "PARAM_BUFFER_TOO_SHORT",
|
||
0x1009: "COM_NOT_INITIALIZED_IN_THREAD",
|
||
0x100A: "URL_REQUIRED"
|
||
}
|
||
|
||
//
|
||
// Methods used for getting the event log
|
||
//
|
||
|
||
obj.GetMessageLog = function (func, tag) {
|
||
obj.AMT_MessageLog_PositionToFirstRecord(_GetMessageLog0, [func, tag, []]);
|
||
}
|
||
function _GetMessageLog0(stack, name, responses, status, tag) {
|
||
if (status != 200 || responses.Body["ReturnValue"] != '0') { tag[0](obj, null, tag[2]); return; }
|
||
obj.AMT_MessageLog_GetRecords(responses.Body["IterationIdentifier"], 390, _GetMessageLog1, tag);
|
||
}
|
||
function _GetMessageLog1(stack, name, responses, status, tag) {
|
||
if (status != 200 || responses.Body["ReturnValue"] != '0') { tag[0](obj, null, tag[2]); return; }
|
||
var i, j, x, e, AmtMessages = tag[2], t = new Date(), TimeStamp, ra = responses.Body["RecordArray"];
|
||
if (typeof ra === 'string') { responses.Body["RecordArray"] = [responses.Body["RecordArray"]]; }
|
||
|
||
for (i in ra) {
|
||
e = null;
|
||
try {
|
||
|
||
e = window.atob(ra[i]);
|
||
} catch (ex) { }
|
||
if (e != null) {
|
||
TimeStamp = ReadIntX(e, 0);
|
||
if ((TimeStamp > 0) && (TimeStamp < 0xFFFFFFFF)) {
|
||
x = { 'DeviceAddress': e.charCodeAt(4), 'EventSensorType': e.charCodeAt(5), 'EventType': e.charCodeAt(6), 'EventOffset': e.charCodeAt(7), 'EventSourceType': e.charCodeAt(8), 'EventSeverity': e.charCodeAt(9), 'SensorNumber': e.charCodeAt(10), 'Entity': e.charCodeAt(11), 'EntityInstance': e.charCodeAt(12), 'EventData': [], 'Time': new Date((TimeStamp + (t.getTimezoneOffset() * 60)) * 1000) };
|
||
for (j = 13; j < 21; j++) { x['EventData'].push(e.charCodeAt(j)); }
|
||
x['EntityStr'] = _SystemEntityTypes[x['Entity']];
|
||
x['Desc'] = _GetEventDetailStr(x['EventSensorType'], x['EventOffset'], x['EventData'], x['Entity']);
|
||
if (!x['EntityStr']) x['EntityStr'] = "Unknown";
|
||
AmtMessages.push(x);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (responses.Body["NoMoreRecords"] != true) { obj.AMT_MessageLog_GetRecords(responses.Body["IterationIdentifier"], 390, _GetMessageLog1, [tag[0], AmtMessages, tag[2]]); } else { tag[0](obj, AmtMessages, tag[2]); }
|
||
}
|
||
|
||
var _EventTrapSourceTypes = "Platform firmware (e.g. BIOS)|SMI handler|ISV system management software|Alert ASIC|IPMI|BIOS vendor|System board set vendor|System integrator|Third party add-in|OSV|NIC|System management card".split('|');
|
||
var _SystemFirmwareError = "Unspecified.|No system memory is physically installed in the system.|No usable system memory, all installed memory has experienced an unrecoverable failure.|Unrecoverable hard-disk/ATAPI/IDE device failure.|Unrecoverable system-board failure.|Unrecoverable diskette subsystem failure.|Unrecoverable hard-disk controller failure.|Unrecoverable PS/2 or USB keyboard failure.|Removable boot media not found.|Unrecoverable video controller failure.|No video device detected.|Firmware (BIOS) ROM corruption detected.|CPU voltage mismatch (processors that share same supply have mismatched voltage requirements)|CPU speed matching failure".split('|');
|
||
var _SystemFirmwareProgress = "Unspecified.|Memory initialization.|Starting hard-disk initialization and test|Secondary processor(s) initialization|User authentication|User-initiated system setup|USB resource configuration|PCI resource configuration|Option ROM initialization|Video initialization|Cache initialization|SM Bus initialization|Keyboard controller initialization|Embedded controller/management controller initialization|Docking station attachment|Enabling docking station|Docking station ejection|Disabling docking station|Calling operating system wake-up vector|Starting operating system boot process|Baseboard or motherboard initialization|reserved|Floppy initialization|Keyboard test|Pointing device test|Primary processor initialization".split('|');
|
||
var _SystemEntityTypes = "Unspecified|Other|Unknown|Processor|Disk|Peripheral|System management module|System board|Memory module|Processor module|Power supply|Add in card|Front panel board|Back panel board|Power system board|Drive backplane|System internal expansion board|Other system board|Processor board|Power unit|Power module|Power management board|Chassis back panel board|System chassis|Sub chassis|Other chassis board|Disk drive bay|Peripheral bay|Device bay|Fan cooling|Cooling unit|Cable interconnect|Memory device|System management software|BIOS|Intel(r) ME|System bus|Group|Intel(r) ME|External environment|Battery|Processing blade|Connectivity switch|Processor/memory module|I/O module|Processor I/O module|Management controller firmware|IPMI channel|PCI bus|PCI express bus|SCSI bus|SATA/SAS bus|Processor front side bus".split('|');
|
||
obj.RealmNames = "||Redirection|PT Administration|Hardware Asset|Remote Control|Storage|Event Manager|Storage Admin|Agent Presence Local|Agent Presence Remote|Circuit Breaker|Network Time|General Information|Firmware Update|EIT|LocalUN|Endpoint Access Control|Endpoint Access Control Admin|Event Log Reader|Audit Log|ACL Realm|||Local System".split('|');
|
||
obj.WatchdogCurrentStates = { 1: 'Not Started', 2: 'Stopped', 4: 'Running', 8: 'Expired', 16: 'Suspended' };
|
||
|
||
function _GetEventDetailStr(eventSensorType, eventOffset, eventDataField, entity) {
|
||
|
||
if (eventSensorType == 15)
|
||
{
|
||
if (eventDataField[0] == 235) return "Invalid Data";
|
||
if (eventOffset == 0) return _SystemFirmwareError[eventDataField[1]];
|
||
return _SystemFirmwareProgress[eventDataField[1]];
|
||
}
|
||
|
||
if (eventSensorType == 18 && eventDataField[0] == 170) // System watchdog event
|
||
{
|
||
return "Agent watchdog " + char2hex(eventDataField[4]) + char2hex(eventDataField[3]) + char2hex(eventDataField[2]) + char2hex(eventDataField[1]) + "-" + char2hex(eventDataField[6]) + char2hex(eventDataField[5]) + "-... changed to " + obj.WatchdogCurrentStates[eventDataField[7]];
|
||
}
|
||
|
||
/*
|
||
if (eventSensorType == 5 && eventOffset == 0) // System chassis
|
||
{
|
||
return "Case intrusion";
|
||
}
|
||
|
||
if (eventSensorType == 192 && eventOffset == 0 && eventDataField[0] == 170 && eventDataField[1] == 48)
|
||
{
|
||
if (eventDataField[2] == 0) return "A remote Serial Over LAN session was established.";
|
||
if (eventDataField[2] == 1) return "Remote Serial Over LAN session finished. User control was restored.";
|
||
if (eventDataField[2] == 2) return "A remote IDE-Redirection session was established.";
|
||
if (eventDataField[2] == 3) return "Remote IDE-Redirection session finished. User control was restored.";
|
||
}
|
||
|
||
if (eventSensorType == 36)
|
||
{
|
||
long handle = ((long)(eventDataField[1]) << 24) + ((long)(eventDataField[2]) << 16) + ((long)(eventDataField[3]) << 8) + (long)(eventDataField[4]);
|
||
string nic = string.Format("#{0}", eventDataField[0]);
|
||
if (eventDataField[0] == 0xAA) nic = "wired"; // TODO: Add wireless *****
|
||
//if (eventDataField[0] == 0xAA) nic = "wireless";
|
||
|
||
if (handle == 4294967293) { return string.Format("All received packet filter was matched on {0} interface.", nic); }
|
||
if (handle == 4294967292) { return string.Format("All outbound packet filter was matched on {0} interface.", nic); }
|
||
if (handle == 4294967290) { return string.Format("Spoofed packet filter was matched on {0} interface.", nic); }
|
||
return string.Format("Filter {0} was matched on {1} interface.", handle, nic);
|
||
}
|
||
|
||
if (eventSensorType == 192)
|
||
{
|
||
if (eventDataField[2] == 0) return "Security policy invoked. Some or all network traffic (TX) was stopped.";
|
||
if (eventDataField[2] == 2) return "Security policy invoked. Some or all network traffic (RX) was stopped.";
|
||
return "Security policy invoked.";
|
||
}
|
||
|
||
if (eventSensorType == 193)
|
||
{
|
||
if (eventDataField[0] == 0xAA && eventDataField[1] == 0x30 && eventDataField[2] == 0x00 && eventDataField[3] == 0x00) { return "User request for remote connection."; }
|
||
if (eventDataField[0] == 0xAA && eventDataField[1] == 0x20 && eventDataField[2] == 0x03 && eventDataField[3] == 0x01) { return "EAC error: attempt to get posture while NAC in Intel<65> AMT is disabled."; // eventDataField = 0xAA20030100000000 }
|
||
if (eventDataField[0] == 0xAA && eventDataField[1] == 0x20 && eventDataField[2] == 0x04 && eventDataField[3] == 0x00) { return "Certificate revoked. "; }
|
||
}
|
||
*/
|
||
|
||
if (eventSensorType == 6) return "Authentication failed " + (eventDataField[1] + (eventDataField[2] << 8)) + " times. The system may be under attack.";
|
||
if (eventSensorType == 30) return "No bootable media";
|
||
if (eventSensorType == 32) return "Operating system lockup or power interrupt";
|
||
if (eventSensorType == 35) return "System boot failure";
|
||
if (eventSensorType == 37) return "System firmware started (at least one CPU is properly executing).";
|
||
return "Unknown Sensor Type #" + eventSensorType;
|
||
}
|
||
|
||
|
||
|
||
// Useful link: https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/default.htm?turl=WordDocuments%2Fsecurityadminevents.htm
|
||
|
||
var _AmtAuditStringTable =
|
||
{
|
||
16: 'Security Admin',
|
||
17: 'RCO',
|
||
18: 'Redirection Manager',
|
||
19: 'Firmware Update Manager',
|
||
20: 'Security Audit Log',
|
||
21: 'Network Time',
|
||
22: 'Network Administration',
|
||
23: 'Storage Administration',
|
||
24: 'Event Manager',
|
||
25: 'Circuit Breaker Manager',
|
||
26: 'Agent Presence Manager',
|
||
27: 'Wireless Configuration',
|
||
28: 'EAC',
|
||
29: 'KVM',
|
||
30: 'User Opt-In Events',
|
||
32: 'Screen Blanking',
|
||
33: 'Watchdog Events',
|
||
1600: 'Provisioning Started',
|
||
1601: 'Provisioning Completed',
|
||
1602: 'ACL Entry Added',
|
||
1603: 'ACL Entry Modified',
|
||
1604: 'ACL Entry Removed',
|
||
1605: 'ACL Access with Invalid Credentials',
|
||
1606: 'ACL Entry State',
|
||
1607: 'TLS State Changed',
|
||
1608: 'TLS Server Certificate Set',
|
||
1609: 'TLS Server Certificate Remove',
|
||
1610: 'TLS Trusted Root Certificate Added',
|
||
1611: 'TLS Trusted Root Certificate Removed',
|
||
1612: 'TLS Preshared Key Set',
|
||
1613: 'Kerberos Settings Modified',
|
||
1614: 'Kerberos Master Key Modified',
|
||
1615: 'Flash Wear out Counters Reset',
|
||
1616: 'Power Package Modified',
|
||
1617: 'Set Realm Authentication Mode',
|
||
1618: 'Upgrade Client to Admin Control Mode',
|
||
1619: 'Unprovisioning Started',
|
||
1700: 'Performed Power Up',
|
||
1701: 'Performed Power Down',
|
||
1702: 'Performed Power Cycle',
|
||
1703: 'Performed Reset',
|
||
1704: 'Set Boot Options',
|
||
1800: 'IDER Session Opened',
|
||
1801: 'IDER Session Closed',
|
||
1802: 'IDER Enabled',
|
||
1803: 'IDER Disabled',
|
||
1804: 'SoL Session Opened',
|
||
1805: 'SoL Session Closed',
|
||
1806: 'SoL Enabled',
|
||
1807: 'SoL Disabled',
|
||
1808: 'KVM Session Started',
|
||
1809: 'KVM Session Ended',
|
||
1810: 'KVM Enabled',
|
||
1811: 'KVM Disabled',
|
||
1812: 'VNC Password Failed 3 Times',
|
||
1900: 'Firmware Updated',
|
||
1901: 'Firmware Update Failed',
|
||
2000: 'Security Audit Log Cleared',
|
||
2001: 'Security Audit Policy Modified',
|
||
2002: 'Security Audit Log Disabled',
|
||
2003: 'Security Audit Log Enabled',
|
||
2004: 'Security Audit Log Exported',
|
||
2005: 'Security Audit Log Recovered',
|
||
2100: 'Intel® ME Time Set',
|
||
2200: 'TCPIP Parameters Set',
|
||
2201: 'Host Name Set',
|
||
2202: 'Domain Name Set',
|
||
2203: 'VLAN Parameters Set',
|
||
2204: 'Link Policy Set',
|
||
2205: 'IPv6 Parameters Set',
|
||
2300: 'Global Storage Attributes Set',
|
||
2301: 'Storage EACL Modified',
|
||
2302: 'Storage FPACL Modified',
|
||
2303: 'Storage Write Operation',
|
||
2400: 'Alert Subscribed',
|
||
2401: 'Alert Unsubscribed',
|
||
2402: 'Event Log Cleared',
|
||
2403: 'Event Log Frozen',
|
||
2500: 'CB Filter Added',
|
||
2501: 'CB Filter Removed',
|
||
2502: 'CB Policy Added',
|
||
2503: 'CB Policy Removed',
|
||
2504: 'CB Default Policy Set',
|
||
2505: 'CB Heuristics Option Set',
|
||
2506: 'CB Heuristics State Cleared',
|
||
2600: 'Agent Watchdog Added',
|
||
2601: 'Agent Watchdog Removed',
|
||
2602: 'Agent Watchdog Action Set',
|
||
2700: 'Wireless Profile Added',
|
||
2701: 'Wireless Profile Removed',
|
||
2702: 'Wireless Profile Updated',
|
||
2800: 'EAC Posture Signer SET',
|
||
2801: 'EAC Enabled',
|
||
2802: 'EAC Disabled',
|
||
2803: 'EAC Posture State',
|
||
2804: 'EAC Set Options',
|
||
2900: 'KVM Opt-in Enabled',
|
||
2901: 'KVM Opt-in Disabled',
|
||
2902: 'KVM Password Changed',
|
||
2903: 'KVM Consent Succeeded',
|
||
2904: 'KVM Consent Failed',
|
||
3000: 'Opt-In Policy Change',
|
||
3001: 'Send Consent Code Event',
|
||
3002: 'Start Opt-In Blocked Event'
|
||
}
|
||
|
||
// Return human readable extended audit log data
|
||
// TODO: Just put some of them here, but many more still need to be added, helpful link here:
|
||
// https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/default.htm?turl=WordDocuments%2Fsecurityadminevents.htm
|
||
obj.GetAuditLogExtendedDataStr = function (id, data) {
|
||
if ((id == 1602 || id == 1604) && data.charCodeAt(0) == 0) { return data.substring(2, 2 + data.charCodeAt(1)); } // ACL Entry Added/Removed (Digest)
|
||
if (id == 1603) { if (data.charCodeAt(1) == 0) { return data.substring(3); } return null; } // ACL Entry Modified
|
||
if (id == 1605) { return ["Invalid ME access", "Invalid MEBx access"][data.charCodeAt(0)]; } // ACL Access with Invalid Credentials
|
||
if (id == 1606) { var r = ["Disabled", "Enabled"][data.charCodeAt(0)]; if (data.charCodeAt(1) == 0) { r += ", " + data.substring(3); } return r;} // ACL Entry State
|
||
if (id == 1607) { return "Remote " + ["NoAuth", "ServerAuth", "MutualAuth"][data.charCodeAt(0)] + ", Local " + ["NoAuth", "ServerAuth", "MutualAuth"][data.charCodeAt(1)]; } // TLS State Changed
|
||
if (id == 1617) { return obj.RealmNames[ReadInt(data, 0)] + ", " + ["NoAuth", "Auth", "Disabled"][data.charCodeAt(4)]; } // Set Realm Authentication Mode
|
||
if (id == 1619) { return ["BIOS", "MEBx", "Local MEI", "Local WSMAN", "Remote WSAMN"][data.charCodeAt(0)]; } // Intel AMT Unprovisioning Started
|
||
if (id == 1900) { return "From " + ReadShort(data, 0) + "." + ReadShort(data, 2) + "." + ReadShort(data, 4) + "." + ReadShort(data, 6) + " to " + ReadShort(data, 8) + "." + ReadShort(data, 10) + "." + ReadShort(data, 12) + "." + ReadShort(data, 14); } // Firmware Updated
|
||
if (id == 2100) { var t4 = new Date(); t4.setTime(ReadInt(data, 0) * 1000 + (new Date().getTimezoneOffset() * 60000)); return t4.toLocaleString(); } // Intel AMT Time Set
|
||
if (id == 3000) { return "From " + ["None", "KVM", "All"][data.charCodeAt(0)] + " to " + ["None", "KVM", "All"][data.charCodeAt(1)]; } // Opt-In Policy Change
|
||
if (id == 3001) { return ["Success", "Failed 3 times"][data.charCodeAt(0)]; } // Send Consent Code Event
|
||
return null;
|
||
}
|
||
|
||
obj.GetAuditLog = function (func) {
|
||
obj.AMT_AuditLog_ReadRecords(1, _GetAuditLog0, [func, []]);
|
||
}
|
||
|
||
function _GetAuditLog0(stack, name, responses, status, tag) {
|
||
if (status != 200) { tag[0](obj, [], status); return; }
|
||
var ptr, i, e, x, r = tag[1], t = new Date(), TimeStamp;
|
||
|
||
if (responses.Body['RecordsReturned'] > 0) {
|
||
responses.Body['EventRecords'] = MakeToArray(responses.Body['EventRecords']);
|
||
|
||
for (i in responses.Body['EventRecords']) {
|
||
e = null;
|
||
try {
|
||
e = window.atob(responses.Body['EventRecords'][i]);
|
||
} catch (e) {
|
||
console.log(e + " " + responses.Body['EventRecords'][i])
|
||
}
|
||
x = { 'AuditAppID': ReadShort(e, 0), 'EventID': ReadShort(e, 2), 'InitiatorType': e.charCodeAt(4) };
|
||
x['AuditApp'] = _AmtAuditStringTable[x['AuditAppID']];
|
||
x['Event'] = _AmtAuditStringTable[(x['AuditAppID'] * 100) + x['EventID']];
|
||
if (!x['Event']) x['Event'] = '#' + x['EventID'];
|
||
|
||
// Read and process the initiator
|
||
if (x['InitiatorType'] == 0) {
|
||
// HTTP digest
|
||
var userlen = e.charCodeAt(5);
|
||
x['Initiator'] = e.substring(6, 6 + userlen);
|
||
ptr = 6 + userlen;
|
||
}
|
||
if (x['InitiatorType'] == 1) {
|
||
// Kerberos
|
||
x['KerberosUserInDomain'] = ReadInt(e, 5);
|
||
var userlen = e.charCodeAt(9);
|
||
x['Initiator'] = GetSidString(e.substring(10, 10 + userlen));
|
||
ptr = 10 + userlen;
|
||
}
|
||
if (x['InitiatorType'] == 2) {
|
||
// Local
|
||
x['Initiator'] = '<i>Local</i>';
|
||
ptr = 5;
|
||
}
|
||
if (x['InitiatorType'] == 3) {
|
||
// KVM Default Port
|
||
x['Initiator'] = '<i>KVM Default Port</i>';
|
||
ptr = 5;
|
||
}
|
||
|
||
// Read timestamp
|
||
TimeStamp = ReadInt(e, ptr);
|
||
x['Time'] = new Date((TimeStamp + (t.getTimezoneOffset() * 60)) * 1000);
|
||
ptr += 4;
|
||
|
||
// Read network access
|
||
x['MCLocationType'] = e.charCodeAt(ptr++);
|
||
var netlen = e.charCodeAt(ptr++);
|
||
x['NetAddress'] = e.substring(ptr, ptr + netlen);
|
||
|
||
// Read extended data
|
||
ptr += netlen;
|
||
var exlen = e.charCodeAt(ptr++);
|
||
x['Ex'] = e.substring(ptr, ptr + exlen);
|
||
x['ExStr'] = obj.GetAuditLogExtendedDataStr((x['AuditAppID'] * 100) + x['EventID'], x['Ex']);
|
||
|
||
r.push(x);
|
||
}
|
||
}
|
||
if (responses.Body['TotalRecordCount'] > r.length) {
|
||
obj.AMT_AuditLog_ReadRecords(r.length + 1, _GetAuditLog0, [tag[0], r]);
|
||
} else {
|
||
tag[0](obj, r, status);
|
||
}
|
||
}
|
||
|
||
|
||
return obj;
|
||
}
|
||
|
||
|
||
|
||
|
||
// Forge MD5
|
||
function hex_md5(str) { return forge.md.md5.create().update(str).digest().toHex(); }
|
||
|
||
|
||
|
||
// Perform MD5 on raw string and return raw string result
|
||
function rstr_md5(str) { return hex2rstr(hex_md5(str)); }
|
||
|
||
/*
|
||
Convert arguments into selector set and body XML. Used by AMT_WiFiPortConfigurationService_UpdateWiFiSettings.
|
||
args = {
|
||
"WiFiEndpoint": {
|
||
__parameterType: 'reference',
|
||
__resourceUri: 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_WiFiEndpoint',
|
||
Name: 'WiFi Endpoint 0'
|
||
},
|
||
"WiFiEndpointSettingsInput":
|
||
{
|
||
__parameterType: 'instance',
|
||
__namespace: 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_WiFiEndpointSettings',
|
||
ElementName: document.querySelector('#editProfile-profileName').value,
|
||
InstanceID: 'Intel(r) AMT:WiFi Endpoint Settings ' + document.querySelector('#editProfile-profileName').value,
|
||
AuthenticationMethod: document.querySelector('#editProfile-networkAuthentication').value,
|
||
//BSSType: 3, // Intel(r) AMT supports only infrastructure networks
|
||
EncryptionMethod: document.querySelector('#editProfile-encryption').value,
|
||
SSID: document.querySelector('#editProfile-networkName').value,
|
||
Priority: 100,
|
||
PSKPassPhrase: document.querySelector('#editProfile-passPhrase').value
|
||
},
|
||
"IEEE8021xSettingsInput": null,
|
||
"ClientCredential": null,
|
||
"CACredential": null
|
||
},
|
||
*/
|
||
function execArgumentsToXml(args) {
|
||
if(args === undefined || args === null) return null;
|
||
|
||
var result = '';
|
||
for(var argName in args) {
|
||
var arg = args[argName];
|
||
if(!arg) continue;
|
||
if(arg['__parameterType'] === 'reference') result += referenceToXml(argName, arg);
|
||
else result += instanceToXml(argName, arg);
|
||
//if(arg['__isInstance']) result += instanceToXml(argName, arg);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Convert JavaScript object into XML
|
||
|
||
<r:WiFiEndpointSettingsInput xmlns:q="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_WiFiEndpointSettings">
|
||
<q:ElementName>Wireless-Profile-Admin</q:ElementName>
|
||
<q:InstanceID>Intel(r) AMT:WiFi Endpoint Settings Wireless-Profile-Admin</q:InstanceID>
|
||
<q:AuthenticationMethod>6</q:AuthenticationMethod>
|
||
<q:EncryptionMethod>4</q:EncryptionMethod>
|
||
<q:Priority>100</q:Priority>
|
||
<q:PSKPassPhrase>P@ssw0rd</q:PSKPassPhrase>
|
||
</r:WiFiEndpointSettingsInput>
|
||
*/
|
||
function instanceToXml(instanceName, inInstance) {
|
||
if(inInstance === undefined || inInstance === null) return null;
|
||
|
||
var hasNamespace = !!inInstance['__namespace'];
|
||
var startTag = hasNamespace ? '<q:' : '<';
|
||
var endTag = hasNamespace ? '</q:' : '</';
|
||
var namespaceDef = hasNamespace ? (' xmlns:q="' + inInstance['__namespace'] + '"' ): '';
|
||
var result = '<r:' + instanceName + namespaceDef + '>';
|
||
for(var prop in inInstance) {
|
||
if (!inInstance.hasOwnProperty(prop) || prop.indexOf('__') === 0) continue;
|
||
|
||
if (typeof inInstance[prop] === 'function' || Array.isArray(inInstance[prop]) ) continue;
|
||
|
||
if (typeof inInstance[prop] === 'object') {
|
||
//result += startTag + prop +'>' + instanceToXml('prop', inInstance[prop]) + endTag + prop +'>';
|
||
console.error('only convert one level down...');
|
||
}
|
||
else {
|
||
result += startTag + prop +'>' + inInstance[prop].toString() + endTag + prop +'>';
|
||
}
|
||
}
|
||
result += '</r:' + instanceName + '>';
|
||
return result;
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a selector set into XML. Expect no nesting.
|
||
* {
|
||
* selectorName : selectorValue,
|
||
* selectorName : selectorValue,
|
||
* ... ...
|
||
* }
|
||
|
||
<r:WiFiEndpoint>
|
||
<a:Address>http://192.168.1.103:16992/wsman</a:Address>
|
||
<a:ReferenceParameters>
|
||
<w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_WiFiEndpoint</w:ResourceURI>
|
||
<w:SelectorSet>
|
||
<w:Selector Name="Name">WiFi Endpoint 0</w:Selector>
|
||
</w:SelectorSet>
|
||
</a:ReferenceParameters>
|
||
</r:WiFiEndpoint>
|
||
|
||
*/
|
||
function referenceToXml(referenceName, inReference) {
|
||
if(inReference === undefined || inReference === null ) return null;
|
||
|
||
var result = '<r:' + referenceName + '><a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>'+ inReference['__resourceUri']+'</w:ResourceURI><w:SelectorSet>';
|
||
for(var selectorName in inReference) {
|
||
if (!inReference.hasOwnProperty(selectorName) || selectorName.indexOf('__') === 0) continue;
|
||
|
||
if (typeof inReference[selectorName] === 'function' ||
|
||
typeof inReference[selectorName] === 'object' ||
|
||
Array.isArray(inReference[selectorName]) )
|
||
continue;
|
||
|
||
result += '<w:Selector Name="' + selectorName +'">' + inReference[selectorName].toString() + '</w:Selector>';
|
||
}
|
||
|
||
result += '</w:SelectorSet></a:ReferenceParameters></r:' + referenceName + '>';
|
||
return result;
|
||
}
|
||
|
||
// Convert a byte array of SID into string
|
||
function GetSidString(sid) {
|
||
var r = "S-" + sid.charCodeAt(0) + "-" + sid.charCodeAt(7);
|
||
for (var i = 2; i < (sid.length / 4) ; i++) r += "-" + ReadIntX(sid, i * 4);
|
||
return r;
|
||
}
|
||
|
||
// Convert a SID readable string into bytes
|
||
function GetSidByteArray(sidString) {
|
||
if (!sidString || sidString == null) return null;
|
||
var sidParts = sidString.split('-');
|
||
|
||
// Make sure the SID has at least 4 parts and starts with 'S'
|
||
if (sidParts.length < 4 || (sidParts[0] != 's' && sidParts[0] != 'S')) return null;
|
||
|
||
// Check that each part of the SID is really an integer
|
||
for (var i = 1; i < sidParts.length; i++) { var y = parseInt(sidParts[i]); if (y != sidParts[i]) return null; sidParts[i] = y; }
|
||
|
||
// Version (8 bit) + Id count (8 bit) + 48 bit in big endian -- DO NOT use bitwise right shift operator. JavaScript converts the number into a 32 bit integer before shifting. In real world, it's highly likely this part is always 0.
|
||
var r = String.fromCharCode(sidParts[1]) + String.fromCharCode(sidParts.length - 3) + ShortToStr(Math.floor(sidParts[2] / Math.pow(2, 32))) + IntToStr((sidParts[2]) & 0xFFFF);
|
||
|
||
// the rest are in 32 bit in little endian
|
||
for (var i = 3; i < sidParts.length; i++) r += IntToStrX(sidParts[i]);
|
||
return r;
|
||
}
|
||
(function(root, factory) {
|
||
if(typeof define === 'function' && define.amd) {
|
||
define([], factory);
|
||
} else {
|
||
root.forge = factory();
|
||
}
|
||
})(this, function() {
|
||
/**
|
||
* license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
|
||
* Available via the MIT or new BSD license.
|
||
* see: http://github.com/jrburke/almond for details
|
||
*/
|
||
//Going sloppy to avoid 'use strict' string cost, but strict practices should
|
||
//be followed.
|
||
/*jslint sloppy: true */
|
||
/*global setTimeout: false */
|
||
|
||
var requirejs, require, define;
|
||
(function (undef) {
|
||
var main, req, makeMap, handlers,
|
||
defined = {},
|
||
waiting = {},
|
||
config = {},
|
||
defining = {},
|
||
hasOwn = Object.prototype.hasOwnProperty,
|
||
aps = [].slice,
|
||
jsSuffixRegExp = /\.js$/;
|
||
|
||
function hasProp(obj, prop) {
|
||
return hasOwn.call(obj, prop);
|
||
}
|
||
|
||
/**
|
||
* Given a relative module name, like ./something, normalize it to
|
||
* a real name that can be mapped to a path.
|
||
* @param {String} name the relative name
|
||
* @param {String} baseName a real name that the name arg is relative
|
||
* to.
|
||
* @returns {String} normalized name
|
||
*/
|
||
function normalize(name, baseName) {
|
||
var nameParts, nameSegment, mapValue, foundMap, lastIndex,
|
||
foundI, foundStarMap, starI, i, j, part,
|
||
baseParts = baseName && baseName.split("/"),
|
||
map = config.map,
|
||
starMap = (map && map['*']) || {};
|
||
|
||
//Adjust any relative paths.
|
||
if (name && name.charAt(0) === ".") {
|
||
//If have a base name, try to normalize against it,
|
||
//otherwise, assume it is a top-level require that will
|
||
//be relative to baseUrl in the end.
|
||
if (baseName) {
|
||
//Convert baseName to array, and lop off the last part,
|
||
//so that . matches that "directory" and not name of the baseName's
|
||
//module. For instance, baseName of "one/two/three", maps to
|
||
//"one/two/three.js", but we want the directory, "one/two" for
|
||
//this normalization.
|
||
baseParts = baseParts.slice(0, baseParts.length - 1);
|
||
name = name.split('/');
|
||
lastIndex = name.length - 1;
|
||
|
||
// Node .js allowance:
|
||
if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
|
||
name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
|
||
}
|
||
|
||
name = baseParts.concat(name);
|
||
|
||
//start trimDots
|
||
for (i = 0; i < name.length; i += 1) {
|
||
part = name[i];
|
||
if (part === ".") {
|
||
name.splice(i, 1);
|
||
i -= 1;
|
||
} else if (part === "..") {
|
||
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
|
||
//End of the line. Keep at least one non-dot
|
||
//path segment at the front so it can be mapped
|
||
//correctly to disk. Otherwise, there is likely
|
||
//no path mapping for a path starting with '..'.
|
||
//This can still fail, but catches the most reasonable
|
||
//uses of ..
|
||
break;
|
||
} else if (i > 0) {
|
||
name.splice(i - 1, 2);
|
||
i -= 2;
|
||
}
|
||
}
|
||
}
|
||
//end trimDots
|
||
|
||
name = name.join("/");
|
||
} else if (name.indexOf('./') === 0) {
|
||
// No baseName, so this is ID is resolved relative
|
||
// to baseUrl, pull off the leading dot.
|
||
name = name.substring(2);
|
||
}
|
||
}
|
||
|
||
//Apply map config if available.
|
||
if ((baseParts || starMap) && map) {
|
||
nameParts = name.split('/');
|
||
|
||
for (i = nameParts.length; i > 0; i -= 1) {
|
||
nameSegment = nameParts.slice(0, i).join("/");
|
||
|
||
if (baseParts) {
|
||
//Find the longest baseName segment match in the config.
|
||
//So, do joins on the biggest to smallest lengths of baseParts.
|
||
for (j = baseParts.length; j > 0; j -= 1) {
|
||
mapValue = map[baseParts.slice(0, j).join('/')];
|
||
|
||
//baseName segment has config, find if it has one for
|
||
//this name.
|
||
if (mapValue) {
|
||
mapValue = mapValue[nameSegment];
|
||
if (mapValue) {
|
||
//Match, update name to the new value.
|
||
foundMap = mapValue;
|
||
foundI = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (foundMap) {
|
||
break;
|
||
}
|
||
|
||
//Check for a star map match, but just hold on to it,
|
||
//if there is a shorter segment match later in a matching
|
||
//config, then favor over this star map.
|
||
if (!foundStarMap && starMap && starMap[nameSegment]) {
|
||
foundStarMap = starMap[nameSegment];
|
||
starI = i;
|
||
}
|
||
}
|
||
|
||
if (!foundMap && foundStarMap) {
|
||
foundMap = foundStarMap;
|
||
foundI = starI;
|
||
}
|
||
|
||
if (foundMap) {
|
||
nameParts.splice(0, foundI, foundMap);
|
||
name = nameParts.join('/');
|
||
}
|
||
}
|
||
|
||
return name;
|
||
}
|
||
|
||
function makeRequire(relName, forceSync) {
|
||
return function () {
|
||
//A version of a require function that passes a moduleName
|
||
//value for items that may need to
|
||
//look up paths relative to the moduleName
|
||
return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
|
||
};
|
||
}
|
||
|
||
function makeNormalize(relName) {
|
||
return function (name) {
|
||
return normalize(name, relName);
|
||
};
|
||
}
|
||
|
||
function makeLoad(depName) {
|
||
return function (value) {
|
||
defined[depName] = value;
|
||
};
|
||
}
|
||
|
||
function callDep(name) {
|
||
if (hasProp(waiting, name)) {
|
||
var args = waiting[name];
|
||
delete waiting[name];
|
||
defining[name] = true;
|
||
main.apply(undef, args);
|
||
}
|
||
|
||
if (!hasProp(defined, name) && !hasProp(defining, name)) {
|
||
throw new Error('No ' + name);
|
||
}
|
||
return defined[name];
|
||
}
|
||
|
||
//Turns a plugin!resource to [plugin, resource]
|
||
//with the plugin being undefined if the name
|
||
//did not have a plugin prefix.
|
||
function splitPrefix(name) {
|
||
var prefix,
|
||
index = name ? name.indexOf('!') : -1;
|
||
if (index > -1) {
|
||
prefix = name.substring(0, index);
|
||
name = name.substring(index + 1, name.length);
|
||
}
|
||
return [prefix, name];
|
||
}
|
||
|
||
/**
|
||
* Makes a name map, normalizing the name, and using a plugin
|
||
* for normalization if necessary. Grabs a ref to plugin
|
||
* too, as an optimization.
|
||
*/
|
||
makeMap = function (name, relName) {
|
||
var plugin,
|
||
parts = splitPrefix(name),
|
||
prefix = parts[0];
|
||
|
||
name = parts[1];
|
||
|
||
if (prefix) {
|
||
prefix = normalize(prefix, relName);
|
||
plugin = callDep(prefix);
|
||
}
|
||
|
||
//Normalize according
|
||
if (prefix) {
|
||
if (plugin && plugin.normalize) {
|
||
name = plugin.normalize(name, makeNormalize(relName));
|
||
} else {
|
||
name = normalize(name, relName);
|
||
}
|
||
} else {
|
||
name = normalize(name, relName);
|
||
parts = splitPrefix(name);
|
||
prefix = parts[0];
|
||
name = parts[1];
|
||
if (prefix) {
|
||
plugin = callDep(prefix);
|
||
}
|
||
}
|
||
|
||
//Using ridiculous property names for space reasons
|
||
return {
|
||
f: prefix ? prefix + '!' + name : name, //fullName
|
||
n: name,
|
||
pr: prefix,
|
||
p: plugin
|
||
};
|
||
};
|
||
|
||
function makeConfig(name) {
|
||
return function () {
|
||
return (config && config.config && config.config[name]) || {};
|
||
};
|
||
}
|
||
|
||
handlers = {
|
||
require: function (name) {
|
||
return makeRequire(name);
|
||
},
|
||
exports: function (name) {
|
||
var e = defined[name];
|
||
if (typeof e !== 'undefined') {
|
||
return e;
|
||
} else {
|
||
return (defined[name] = {});
|
||
}
|
||
},
|
||
module: function (name) {
|
||
return {
|
||
id: name,
|
||
uri: '',
|
||
exports: defined[name],
|
||
config: makeConfig(name)
|
||
};
|
||
}
|
||
};
|
||
|
||
main = function (name, deps, callback, relName) {
|
||
var cjsModule, depName, ret, map, i,
|
||
args = [],
|
||
callbackType = typeof callback,
|
||
usingExports;
|
||
|
||
//Use name if no relName
|
||
relName = relName || name;
|
||
|
||
//Call the callback to define the module, if necessary.
|
||
if (callbackType === 'undefined' || callbackType === 'function') {
|
||
//Pull out the defined dependencies and pass the ordered
|
||
//values to the callback.
|
||
//Default to [require, exports, module] if no deps
|
||
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
|
||
for (i = 0; i < deps.length; i += 1) {
|
||
map = makeMap(deps[i], relName);
|
||
depName = map.f;
|
||
|
||
//Fast path CommonJS standard dependencies.
|
||
if (depName === "require") {
|
||
args[i] = handlers.require(name);
|
||
} else if (depName === "exports") {
|
||
//CommonJS module spec 1.1
|
||
args[i] = handlers.exports(name);
|
||
usingExports = true;
|
||
} else if (depName === "module") {
|
||
//CommonJS module spec 1.1
|
||
cjsModule = args[i] = handlers.module(name);
|
||
} else if (hasProp(defined, depName) ||
|
||
hasProp(waiting, depName) ||
|
||
hasProp(defining, depName)) {
|
||
args[i] = callDep(depName);
|
||
} else if (map.p) {
|
||
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
|
||
args[i] = defined[depName];
|
||
} else {
|
||
throw new Error(name + ' missing ' + depName);
|
||
}
|
||
}
|
||
|
||
ret = callback ? callback.apply(defined[name], args) : undefined;
|
||
|
||
if (name) {
|
||
//If setting exports via "module" is in play,
|
||
//favor that over return value and exports. After that,
|
||
//favor a non-undefined return value over exports use.
|
||
if (cjsModule && cjsModule.exports !== undef &&
|
||
cjsModule.exports !== defined[name]) {
|
||
defined[name] = cjsModule.exports;
|
||
} else if (ret !== undef || !usingExports) {
|
||
//Use the return value from the function.
|
||
defined[name] = ret;
|
||
}
|
||
}
|
||
} else if (name) {
|
||
//May just be an object definition for the module. Only
|
||
//worry about defining if have a module name.
|
||
defined[name] = callback;
|
||
}
|
||
};
|
||
|
||
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
|
||
if (typeof deps === "string") {
|
||
if (handlers[deps]) {
|
||
//callback in this case is really relName
|
||
return handlers[deps](callback);
|
||
}
|
||
//Just return the module wanted. In this scenario, the
|
||
//deps arg is the module name, and second arg (if passed)
|
||
//is just the relName.
|
||
//Normalize module name, if it contains . or ..
|
||
return callDep(makeMap(deps, callback).f);
|
||
} else if (!deps.splice) {
|
||
//deps is a config object, not an array.
|
||
config = deps;
|
||
if (config.deps) {
|
||
req(config.deps, config.callback);
|
||
}
|
||
if (!callback) {
|
||
return;
|
||
}
|
||
|
||
if (callback.splice) {
|
||
//callback is an array, which means it is a dependency list.
|
||
//Adjust args if there are dependencies
|
||
deps = callback;
|
||
callback = relName;
|
||
relName = null;
|
||
} else {
|
||
deps = undef;
|
||
}
|
||
}
|
||
|
||
//Support require(['a'])
|
||
callback = callback || function () {};
|
||
|
||
//If relName is a function, it is an errback handler,
|
||
//so remove it.
|
||
if (typeof relName === 'function') {
|
||
relName = forceSync;
|
||
forceSync = alt;
|
||
}
|
||
|
||
//Simulate async callback;
|
||
if (forceSync) {
|
||
main(undef, deps, callback, relName);
|
||
} else {
|
||
//Using a non-zero value because of concern for what old browsers
|
||
//do, and latest browsers "upgrade" to 4 if lower value is used:
|
||
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
|
||
//If want a value immediately, use require('id') instead -- something
|
||
//that works in almond on the global level, but not guaranteed and
|
||
//unlikely to work in other AMD implementations.
|
||
setTimeout(function () {
|
||
main(undef, deps, callback, relName);
|
||
}, 4);
|
||
}
|
||
|
||
return req;
|
||
};
|
||
|
||
/**
|
||
* Just drops the config on the floor, but returns req in case
|
||
* the config return value is used.
|
||
*/
|
||
req.config = function (cfg) {
|
||
return req(cfg);
|
||
};
|
||
|
||
/**
|
||
* Expose module registry for debugging and tooling
|
||
*/
|
||
requirejs._defined = defined;
|
||
|
||
define = function (name, deps, callback) {
|
||
|
||
//This module may not have dependencies
|
||
if (!deps.splice) {
|
||
//deps is not an array, so probably means
|
||
//an object literal or factory function for
|
||
//the value. Adjust args.
|
||
callback = deps;
|
||
deps = [];
|
||
}
|
||
|
||
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
|
||
waiting[name] = [name, deps, callback];
|
||
}
|
||
};
|
||
|
||
define.amd = {
|
||
jQuery: true
|
||
};
|
||
}());
|
||
|
||
define("node_modules/almond/almond", function(){});
|
||
|
||
/**
|
||
* Utility functions for web applications.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* Utilities API */
|
||
var util = forge.util = forge.util || {};
|
||
|
||
// define setImmediate and nextTick
|
||
(function() {
|
||
// use native nextTick
|
||
if(typeof process !== 'undefined' && process.nextTick) {
|
||
util.nextTick = process.nextTick;
|
||
if(typeof setImmediate === 'function') {
|
||
util.setImmediate = setImmediate;
|
||
} else {
|
||
// polyfill setImmediate with nextTick, older versions of node
|
||
// (those w/o setImmediate) won't totally starve IO
|
||
util.setImmediate = util.nextTick;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// polyfill nextTick with native setImmediate
|
||
if(typeof setImmediate === 'function') {
|
||
util.setImmediate = setImmediate;
|
||
util.nextTick = function(callback) {
|
||
return setImmediate(callback);
|
||
};
|
||
return;
|
||
}
|
||
|
||
/* Note: A polyfill upgrade pattern is used here to allow combining
|
||
polyfills. For example, MutationObserver is fast, but blocks UI updates,
|
||
so it needs to allow UI updates periodically, so it falls back on
|
||
postMessage or setTimeout. */
|
||
|
||
// polyfill with setTimeout
|
||
util.setImmediate = function(callback) {
|
||
setTimeout(callback, 0);
|
||
};
|
||
|
||
// upgrade polyfill to use postMessage
|
||
if(typeof window !== 'undefined' &&
|
||
typeof window.postMessage === 'function') {
|
||
var msg = 'forge.setImmediate';
|
||
var callbacks = [];
|
||
util.setImmediate = function(callback) {
|
||
callbacks.push(callback);
|
||
// only send message when one hasn't been sent in
|
||
// the current turn of the event loop
|
||
if(callbacks.length === 1) {
|
||
window.postMessage(msg, '*');
|
||
}
|
||
};
|
||
function handler(event) {
|
||
if(event.source === window && event.data === msg) {
|
||
event.stopPropagation();
|
||
var copy = callbacks.slice();
|
||
callbacks.length = 0;
|
||
copy.forEach(function(callback) {
|
||
callback();
|
||
});
|
||
}
|
||
}
|
||
window.addEventListener('message', handler, true);
|
||
}
|
||
|
||
// upgrade polyfill to use MutationObserver
|
||
if(typeof MutationObserver !== 'undefined') {
|
||
// polyfill with MutationObserver
|
||
var now = Date.now();
|
||
var attr = true;
|
||
var div = document.createElement('div');
|
||
var callbacks = [];
|
||
new MutationObserver(function() {
|
||
var copy = callbacks.slice();
|
||
callbacks.length = 0;
|
||
copy.forEach(function(callback) {
|
||
callback();
|
||
});
|
||
}).observe(div, {attributes: true});
|
||
var oldSetImmediate = util.setImmediate;
|
||
util.setImmediate = function(callback) {
|
||
if(Date.now() - now > 15) {
|
||
now = Date.now();
|
||
oldSetImmediate(callback);
|
||
} else {
|
||
callbacks.push(callback);
|
||
// only trigger observer when it hasn't been triggered in
|
||
// the current turn of the event loop
|
||
if(callbacks.length === 1) {
|
||
div.setAttribute('a', attr = !attr);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
util.nextTick = util.setImmediate;
|
||
})();
|
||
|
||
// define isArray
|
||
util.isArray = Array.isArray || function(x) {
|
||
return Object.prototype.toString.call(x) === '[object Array]';
|
||
};
|
||
|
||
// define isArrayBuffer
|
||
util.isArrayBuffer = function(x) {
|
||
return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer;
|
||
};
|
||
|
||
// define isArrayBufferView
|
||
util.isArrayBufferView = function(x) {
|
||
return x && util.isArrayBuffer(x.buffer) && x.byteLength !== undefined;
|
||
};
|
||
|
||
// TODO: set ByteBuffer to best available backing
|
||
util.ByteBuffer = ByteStringBuffer;
|
||
|
||
/** Buffer w/BinaryString backing */
|
||
|
||
/**
|
||
* Constructor for a binary string backed byte buffer.
|
||
*
|
||
* @param [b] the bytes to wrap (either encoded as string, one byte per
|
||
* character, or as an ArrayBuffer or Typed Array).
|
||
*/
|
||
function ByteStringBuffer(b) {
|
||
// TODO: update to match DataBuffer API
|
||
|
||
// the data in this buffer
|
||
this.data = '';
|
||
// the pointer for reading from this buffer
|
||
this.read = 0;
|
||
|
||
if(typeof b === 'string') {
|
||
this.data = b;
|
||
} else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) {
|
||
// convert native buffer to forge buffer
|
||
// FIXME: support native buffers internally instead
|
||
var arr = new Uint8Array(b);
|
||
try {
|
||
this.data = String.fromCharCode.apply(null, arr);
|
||
} catch(e) {
|
||
for(var i = 0; i < arr.length; ++i) {
|
||
this.putByte(arr[i]);
|
||
}
|
||
}
|
||
} else if(b instanceof ByteStringBuffer ||
|
||
(typeof b === 'object' && typeof b.data === 'string' &&
|
||
typeof b.read === 'number')) {
|
||
// copy existing buffer
|
||
this.data = b.data;
|
||
this.read = b.read;
|
||
}
|
||
|
||
// used for v8 optimization
|
||
this._constructedStringLength = 0;
|
||
}
|
||
util.ByteStringBuffer = ByteStringBuffer;
|
||
|
||
/* Note: This is an optimization for V8-based browsers. When V8 concatenates
|
||
a string, the strings are only joined logically using a "cons string" or
|
||
"constructed/concatenated string". These containers keep references to one
|
||
another and can result in very large memory usage. For example, if a 2MB
|
||
string is constructed by concatenating 4 bytes together at a time, the
|
||
memory usage will be ~44MB; so ~22x increase. The strings are only joined
|
||
together when an operation requiring their joining takes place, such as
|
||
substr(). This function is called when adding data to this buffer to ensure
|
||
these types of strings are periodically joined to reduce the memory
|
||
footprint. */
|
||
var _MAX_CONSTRUCTED_STRING_LENGTH = 4096;
|
||
util.ByteStringBuffer.prototype._optimizeConstructedString = function(x) {
|
||
this._constructedStringLength += x;
|
||
if(this._constructedStringLength > _MAX_CONSTRUCTED_STRING_LENGTH) {
|
||
// this substr() should cause the constructed string to join
|
||
this.data.substr(0, 1);
|
||
this._constructedStringLength = 0;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Gets the number of bytes in this buffer.
|
||
*
|
||
* @return the number of bytes in this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.length = function() {
|
||
return this.data.length - this.read;
|
||
};
|
||
|
||
/**
|
||
* Gets whether or not this buffer is empty.
|
||
*
|
||
* @return true if this buffer is empty, false if not.
|
||
*/
|
||
util.ByteStringBuffer.prototype.isEmpty = function() {
|
||
return this.length() <= 0;
|
||
};
|
||
|
||
/**
|
||
* Puts a byte in this buffer.
|
||
*
|
||
* @param b the byte to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putByte = function(b) {
|
||
return this.putBytes(String.fromCharCode(b));
|
||
};
|
||
|
||
/**
|
||
* Puts a byte in this buffer N times.
|
||
*
|
||
* @param b the byte to put.
|
||
* @param n the number of bytes of value b to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.fillWithByte = function(b, n) {
|
||
b = String.fromCharCode(b);
|
||
var d = this.data;
|
||
while(n > 0) {
|
||
if(n & 1) {
|
||
d += b;
|
||
}
|
||
n >>>= 1;
|
||
if(n > 0) {
|
||
b += b;
|
||
}
|
||
}
|
||
this.data = d;
|
||
this._optimizeConstructedString(n);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts bytes in this buffer.
|
||
*
|
||
* @param bytes the bytes (as a UTF-8 encoded string) to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putBytes = function(bytes) {
|
||
this.data += bytes;
|
||
this._optimizeConstructedString(bytes.length);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a UTF-16 encoded string into this buffer.
|
||
*
|
||
* @param str the string to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putString = function(str) {
|
||
return this.putBytes(util.encodeUtf8(str));
|
||
};
|
||
|
||
/**
|
||
* Puts a 16-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 16-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt16 = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts a 24-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 24-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt24 = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i >> 16 & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts a 32-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 32-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt32 = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i >> 24 & 0xFF) +
|
||
String.fromCharCode(i >> 16 & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts a 16-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 16-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt16Le = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts a 24-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 24-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt24Le = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i >> 16 & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts a 32-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 32-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt32Le = function(i) {
|
||
return this.putBytes(
|
||
String.fromCharCode(i & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i >> 16 & 0xFF) +
|
||
String.fromCharCode(i >> 24 & 0xFF));
|
||
};
|
||
|
||
/**
|
||
* Puts an n-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the n-bit integer.
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putInt = function(i, n) {
|
||
var bytes = '';
|
||
do {
|
||
n -= 8;
|
||
bytes += String.fromCharCode((i >> n) & 0xFF);
|
||
} while(n > 0);
|
||
return this.putBytes(bytes);
|
||
};
|
||
|
||
/**
|
||
* Puts a signed n-bit integer in this buffer in big-endian order. Two's
|
||
* complement representation is used.
|
||
*
|
||
* @param i the n-bit integer.
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putSignedInt = function(i, n) {
|
||
if(i < 0) {
|
||
i += 2 << (n - 1);
|
||
}
|
||
return this.putInt(i, n);
|
||
};
|
||
|
||
/**
|
||
* Puts the given buffer into this buffer.
|
||
*
|
||
* @param buffer the buffer to put into this one.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.putBuffer = function(buffer) {
|
||
return this.putBytes(buffer.getBytes());
|
||
};
|
||
|
||
/**
|
||
* Gets a byte from this buffer and advances the read pointer by 1.
|
||
*
|
||
* @return the byte.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getByte = function() {
|
||
return this.data.charCodeAt(this.read++);
|
||
};
|
||
|
||
/**
|
||
* Gets a uint16 from this buffer in big-endian order and advances the read
|
||
* pointer by 2.
|
||
*
|
||
* @return the uint16.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt16 = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) << 8 ^
|
||
this.data.charCodeAt(this.read + 1));
|
||
this.read += 2;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint24 from this buffer in big-endian order and advances the read
|
||
* pointer by 3.
|
||
*
|
||
* @return the uint24.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt24 = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) << 16 ^
|
||
this.data.charCodeAt(this.read + 1) << 8 ^
|
||
this.data.charCodeAt(this.read + 2));
|
||
this.read += 3;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint32 from this buffer in big-endian order and advances the read
|
||
* pointer by 4.
|
||
*
|
||
* @return the word.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt32 = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) << 24 ^
|
||
this.data.charCodeAt(this.read + 1) << 16 ^
|
||
this.data.charCodeAt(this.read + 2) << 8 ^
|
||
this.data.charCodeAt(this.read + 3));
|
||
this.read += 4;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint16 from this buffer in little-endian order and advances the read
|
||
* pointer by 2.
|
||
*
|
||
* @return the uint16.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt16Le = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) ^
|
||
this.data.charCodeAt(this.read + 1) << 8);
|
||
this.read += 2;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint24 from this buffer in little-endian order and advances the read
|
||
* pointer by 3.
|
||
*
|
||
* @return the uint24.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt24Le = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) ^
|
||
this.data.charCodeAt(this.read + 1) << 8 ^
|
||
this.data.charCodeAt(this.read + 2) << 16);
|
||
this.read += 3;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint32 from this buffer in little-endian order and advances the read
|
||
* pointer by 4.
|
||
*
|
||
* @return the word.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt32Le = function() {
|
||
var rval = (
|
||
this.data.charCodeAt(this.read) ^
|
||
this.data.charCodeAt(this.read + 1) << 8 ^
|
||
this.data.charCodeAt(this.read + 2) << 16 ^
|
||
this.data.charCodeAt(this.read + 3) << 24);
|
||
this.read += 4;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets an n-bit integer from this buffer in big-endian order and advances the
|
||
* read pointer by n/8.
|
||
*
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return the integer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getInt = function(n) {
|
||
var rval = 0;
|
||
do {
|
||
rval = (rval << 8) + this.data.charCodeAt(this.read++);
|
||
n -= 8;
|
||
} while(n > 0);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a signed n-bit integer from this buffer in big-endian order, using
|
||
* two's complement, and advances the read pointer by n/8.
|
||
*
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return the integer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getSignedInt = function(n) {
|
||
var x = this.getInt(n);
|
||
var max = 2 << (n - 2);
|
||
if(x >= max) {
|
||
x -= max << 1;
|
||
}
|
||
return x;
|
||
};
|
||
|
||
/**
|
||
* Reads bytes out into a UTF-8 string and clears them from the buffer.
|
||
*
|
||
* @param count the number of bytes to read, undefined or null for all.
|
||
*
|
||
* @return a UTF-8 string of bytes.
|
||
*/
|
||
util.ByteStringBuffer.prototype.getBytes = function(count) {
|
||
var rval;
|
||
if(count) {
|
||
// read count bytes
|
||
count = Math.min(this.length(), count);
|
||
rval = this.data.slice(this.read, this.read + count);
|
||
this.read += count;
|
||
} else if(count === 0) {
|
||
rval = '';
|
||
} else {
|
||
// read all bytes, optimize to only copy when needed
|
||
rval = (this.read === 0) ? this.data : this.data.slice(this.read);
|
||
this.clear();
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a UTF-8 encoded string of the bytes from this buffer without modifying
|
||
* the read pointer.
|
||
*
|
||
* @param count the number of bytes to get, omit to get all.
|
||
*
|
||
* @return a string full of UTF-8 encoded characters.
|
||
*/
|
||
util.ByteStringBuffer.prototype.bytes = function(count) {
|
||
return (typeof(count) === 'undefined' ?
|
||
this.data.slice(this.read) :
|
||
this.data.slice(this.read, this.read + count));
|
||
};
|
||
|
||
/**
|
||
* Gets a byte at the given index without modifying the read pointer.
|
||
*
|
||
* @param i the byte index.
|
||
*
|
||
* @return the byte.
|
||
*/
|
||
util.ByteStringBuffer.prototype.at = function(i) {
|
||
return this.data.charCodeAt(this.read + i);
|
||
};
|
||
|
||
/**
|
||
* Puts a byte at the given index without modifying the read pointer.
|
||
*
|
||
* @param i the byte index.
|
||
* @param b the byte to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.setAt = function(i, b) {
|
||
this.data = this.data.substr(0, this.read + i) +
|
||
String.fromCharCode(b) +
|
||
this.data.substr(this.read + i + 1);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Gets the last byte without modifying the read pointer.
|
||
*
|
||
* @return the last byte.
|
||
*/
|
||
util.ByteStringBuffer.prototype.last = function() {
|
||
return this.data.charCodeAt(this.data.length - 1);
|
||
};
|
||
|
||
/**
|
||
* Creates a copy of this buffer.
|
||
*
|
||
* @return the copy.
|
||
*/
|
||
util.ByteStringBuffer.prototype.copy = function() {
|
||
var c = util.createBuffer(this.data);
|
||
c.read = this.read;
|
||
return c;
|
||
};
|
||
|
||
/**
|
||
* Compacts this buffer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.compact = function() {
|
||
if(this.read > 0) {
|
||
this.data = this.data.slice(this.read);
|
||
this.read = 0;
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Clears this buffer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.clear = function() {
|
||
this.data = '';
|
||
this.read = 0;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Shortens this buffer by triming bytes off of the end of this buffer.
|
||
*
|
||
* @param count the number of bytes to trim off.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.ByteStringBuffer.prototype.truncate = function(count) {
|
||
var len = Math.max(0, this.length() - count);
|
||
this.data = this.data.substr(this.read, len);
|
||
this.read = 0;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Converts this buffer to a hexadecimal string.
|
||
*
|
||
* @return a hexadecimal string.
|
||
*/
|
||
util.ByteStringBuffer.prototype.toHex = function() {
|
||
var rval = '';
|
||
for(var i = this.read; i < this.data.length; ++i) {
|
||
var b = this.data.charCodeAt(i);
|
||
if(b < 16) {
|
||
rval += '0';
|
||
}
|
||
rval += b.toString(16);
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts this buffer to a UTF-16 string (standard JavaScript string).
|
||
*
|
||
* @return a UTF-16 string.
|
||
*/
|
||
util.ByteStringBuffer.prototype.toString = function() {
|
||
return util.decodeUtf8(this.bytes());
|
||
};
|
||
|
||
/** End Buffer w/BinaryString backing */
|
||
|
||
|
||
/** Buffer w/UInt8Array backing */
|
||
|
||
/**
|
||
* FIXME: Experimental. Do not use yet.
|
||
*
|
||
* Constructor for an ArrayBuffer-backed byte buffer.
|
||
*
|
||
* The buffer may be constructed from a string, an ArrayBuffer, DataView, or a
|
||
* TypedArray.
|
||
*
|
||
* If a string is given, its encoding should be provided as an option,
|
||
* otherwise it will default to 'binary'. A 'binary' string is encoded such
|
||
* that each character is one byte in length and size.
|
||
*
|
||
* If an ArrayBuffer, DataView, or TypedArray is given, it will be used
|
||
* *directly* without any copying. Note that, if a write to the buffer requires
|
||
* more space, the buffer will allocate a new backing ArrayBuffer to
|
||
* accommodate. The starting read and write offsets for the buffer may be
|
||
* given as options.
|
||
*
|
||
* @param [b] the initial bytes for this buffer.
|
||
* @param options the options to use:
|
||
* [readOffset] the starting read offset to use (default: 0).
|
||
* [writeOffset] the starting write offset to use (default: the
|
||
* length of the first parameter).
|
||
* [growSize] the minimum amount, in bytes, to grow the buffer by to
|
||
* accommodate writes (default: 1024).
|
||
* [encoding] the encoding ('binary', 'utf8', 'utf16', 'hex') for the
|
||
* first parameter, if it is a string (default: 'binary').
|
||
*/
|
||
function DataBuffer(b, options) {
|
||
// default options
|
||
options = options || {};
|
||
|
||
// pointers for read from/write to buffer
|
||
this.read = options.readOffset || 0;
|
||
this.growSize = options.growSize || 1024;
|
||
|
||
var isArrayBuffer = util.isArrayBuffer(b);
|
||
var isArrayBufferView = util.isArrayBufferView(b);
|
||
if(isArrayBuffer || isArrayBufferView) {
|
||
// use ArrayBuffer directly
|
||
if(isArrayBuffer) {
|
||
this.data = new DataView(b);
|
||
} else {
|
||
// TODO: adjust read/write offset based on the type of view
|
||
// or specify that this must be done in the options ... that the
|
||
// offsets are byte-based
|
||
this.data = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
||
}
|
||
this.write = ('writeOffset' in options ?
|
||
options.writeOffset : this.data.byteLength);
|
||
return;
|
||
}
|
||
|
||
// initialize to empty array buffer and add any given bytes using putBytes
|
||
this.data = new DataView(new ArrayBuffer(0));
|
||
this.write = 0;
|
||
|
||
if(b !== null && b !== undefined) {
|
||
this.putBytes(b);
|
||
}
|
||
|
||
if('writeOffset' in options) {
|
||
this.write = options.writeOffset;
|
||
}
|
||
}
|
||
util.DataBuffer = DataBuffer;
|
||
|
||
/**
|
||
* Gets the number of bytes in this buffer.
|
||
*
|
||
* @return the number of bytes in this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.length = function() {
|
||
return this.write - this.read;
|
||
};
|
||
|
||
/**
|
||
* Gets whether or not this buffer is empty.
|
||
*
|
||
* @return true if this buffer is empty, false if not.
|
||
*/
|
||
util.DataBuffer.prototype.isEmpty = function() {
|
||
return this.length() <= 0;
|
||
};
|
||
|
||
/**
|
||
* Ensures this buffer has enough empty space to accommodate the given number
|
||
* of bytes. An optional parameter may be given that indicates a minimum
|
||
* amount to grow the buffer if necessary. If the parameter is not given,
|
||
* the buffer will be grown by some previously-specified default amount
|
||
* or heuristic.
|
||
*
|
||
* @param amount the number of bytes to accommodate.
|
||
* @param [growSize] the minimum amount, in bytes, to grow the buffer by if
|
||
* necessary.
|
||
*/
|
||
util.DataBuffer.prototype.accommodate = function(amount, growSize) {
|
||
if(this.length() >= amount) {
|
||
return this;
|
||
}
|
||
growSize = Math.max(growSize || this.growSize, amount);
|
||
|
||
// grow buffer
|
||
var src = new Uint8Array(
|
||
this.data.buffer, this.data.byteOffset, this.data.byteLength);
|
||
var dst = new Uint8Array(this.length() + growSize);
|
||
dst.set(src);
|
||
this.data = new DataView(dst.buffer);
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a byte in this buffer.
|
||
*
|
||
* @param b the byte to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putByte = function(b) {
|
||
this.accommodate(1);
|
||
this.data.setUint8(this.write++, b);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a byte in this buffer N times.
|
||
*
|
||
* @param b the byte to put.
|
||
* @param n the number of bytes of value b to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.fillWithByte = function(b, n) {
|
||
this.accommodate(n);
|
||
for(var i = 0; i < n; ++i) {
|
||
this.data.setUint8(b);
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts bytes in this buffer. The bytes may be given as a string, an
|
||
* ArrayBuffer, a DataView, or a TypedArray.
|
||
*
|
||
* @param bytes the bytes to put.
|
||
* @param [encoding] the encoding for the first parameter ('binary', 'utf8',
|
||
* 'utf16', 'hex'), if it is a string (default: 'binary').
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putBytes = function(bytes, encoding) {
|
||
if(util.isArrayBufferView(bytes)) {
|
||
var src = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||
var len = src.byteLength - src.byteOffset;
|
||
this.accommodate(len);
|
||
var dst = new Uint8Array(this.data.buffer, this.write);
|
||
dst.set(src);
|
||
this.write += len;
|
||
return this;
|
||
}
|
||
|
||
if(util.isArrayBuffer(bytes)) {
|
||
var src = new Uint8Array(bytes);
|
||
this.accommodate(src.byteLength);
|
||
var dst = new Uint8Array(this.data.buffer);
|
||
dst.set(src, this.write);
|
||
this.write += src.byteLength;
|
||
return this;
|
||
}
|
||
|
||
// bytes is a util.DataBuffer or equivalent
|
||
if(bytes instanceof util.DataBuffer ||
|
||
(typeof bytes === 'object' &&
|
||
typeof bytes.read === 'number' && typeof bytes.write === 'number' &&
|
||
util.isArrayBufferView(bytes.data))) {
|
||
var src = new Uint8Array(bytes.data.byteLength, bytes.read, bytes.length());
|
||
this.accommodate(src.byteLength);
|
||
var dst = new Uint8Array(bytes.data.byteLength, this.write);
|
||
dst.set(src);
|
||
this.write += src.byteLength;
|
||
return this;
|
||
}
|
||
|
||
if(bytes instanceof util.ByteStringBuffer) {
|
||
// copy binary string and process as the same as a string parameter below
|
||
bytes = bytes.data;
|
||
encoding = 'binary';
|
||
}
|
||
|
||
// string conversion
|
||
encoding = encoding || 'binary';
|
||
if(typeof bytes === 'string') {
|
||
var view;
|
||
|
||
// decode from string
|
||
if(encoding === 'hex') {
|
||
this.accommodate(Math.ceil(bytes.length / 2));
|
||
view = new Uint8Array(this.data.buffer, this.write);
|
||
this.write += util.binary.hex.decode(bytes, view, this.write);
|
||
return this;
|
||
}
|
||
if(encoding === 'base64') {
|
||
this.accommodate(Math.ceil(bytes.length / 4) * 3);
|
||
view = new Uint8Array(this.data.buffer, this.write);
|
||
this.write += util.binary.base64.decode(bytes, view, this.write);
|
||
return this;
|
||
}
|
||
|
||
// encode text as UTF-8 bytes
|
||
if(encoding === 'utf8') {
|
||
// encode as UTF-8 then decode string as raw binary
|
||
bytes = util.encodeUtf8(bytes);
|
||
encoding = 'binary';
|
||
}
|
||
|
||
// decode string as raw binary
|
||
if(encoding === 'binary' || encoding === 'raw') {
|
||
// one byte per character
|
||
this.accommodate(bytes.length);
|
||
view = new Uint8Array(this.data.buffer, this.write);
|
||
this.write += util.binary.raw.decode(view);
|
||
return this;
|
||
}
|
||
|
||
// encode text as UTF-16 bytes
|
||
if(encoding === 'utf16') {
|
||
// two bytes per character
|
||
this.accommodate(bytes.length * 2);
|
||
view = new Uint16Array(this.data.buffer, this.write);
|
||
this.write += util.text.utf16.encode(view);
|
||
return this;
|
||
}
|
||
|
||
throw new Error('Invalid encoding: ' + encoding);
|
||
}
|
||
|
||
throw Error('Invalid parameter: ' + bytes);
|
||
};
|
||
|
||
/**
|
||
* Puts the given buffer into this buffer.
|
||
*
|
||
* @param buffer the buffer to put into this one.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putBuffer = function(buffer) {
|
||
this.putBytes(buffer);
|
||
buffer.clear();
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a string into this buffer.
|
||
*
|
||
* @param str the string to put.
|
||
* @param [encoding] the encoding for the string (default: 'utf16').
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putString = function(str) {
|
||
return this.putBytes(str, 'utf16');
|
||
};
|
||
|
||
/**
|
||
* Puts a 16-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 16-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt16 = function(i) {
|
||
this.accommodate(2);
|
||
this.data.setInt16(this.write, i);
|
||
this.write += 2;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a 24-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 24-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt24 = function(i) {
|
||
this.accommodate(3);
|
||
this.data.setInt16(this.write, i >> 8 & 0xFFFF);
|
||
this.data.setInt8(this.write, i >> 16 & 0xFF);
|
||
this.write += 3;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a 32-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the 32-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt32 = function(i) {
|
||
this.accommodate(4);
|
||
this.data.setInt32(this.write, i);
|
||
this.write += 4;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a 16-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 16-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt16Le = function(i) {
|
||
this.accommodate(2);
|
||
this.data.setInt16(this.write, i, true);
|
||
this.write += 2;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a 24-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 24-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt24Le = function(i) {
|
||
this.accommodate(3);
|
||
this.data.setInt8(this.write, i >> 16 & 0xFF);
|
||
this.data.setInt16(this.write, i >> 8 & 0xFFFF, true);
|
||
this.write += 3;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a 32-bit integer in this buffer in little-endian order.
|
||
*
|
||
* @param i the 32-bit integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt32Le = function(i) {
|
||
this.accommodate(4);
|
||
this.data.setInt32(this.write, i, true);
|
||
this.write += 4;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts an n-bit integer in this buffer in big-endian order.
|
||
*
|
||
* @param i the n-bit integer.
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putInt = function(i, n) {
|
||
this.accommodate(n / 8);
|
||
do {
|
||
n -= 8;
|
||
this.data.setInt8(this.write++, (i >> n) & 0xFF);
|
||
} while(n > 0);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Puts a signed n-bit integer in this buffer in big-endian order. Two's
|
||
* complement representation is used.
|
||
*
|
||
* @param i the n-bit integer.
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.putSignedInt = function(i, n) {
|
||
this.accommodate(n / 8);
|
||
if(i < 0) {
|
||
i += 2 << (n - 1);
|
||
}
|
||
return this.putInt(i, n);
|
||
};
|
||
|
||
/**
|
||
* Gets a byte from this buffer and advances the read pointer by 1.
|
||
*
|
||
* @return the byte.
|
||
*/
|
||
util.DataBuffer.prototype.getByte = function() {
|
||
return this.data.getInt8(this.read++);
|
||
};
|
||
|
||
/**
|
||
* Gets a uint16 from this buffer in big-endian order and advances the read
|
||
* pointer by 2.
|
||
*
|
||
* @return the uint16.
|
||
*/
|
||
util.DataBuffer.prototype.getInt16 = function() {
|
||
var rval = this.data.getInt16(this.read);
|
||
this.read += 2;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint24 from this buffer in big-endian order and advances the read
|
||
* pointer by 3.
|
||
*
|
||
* @return the uint24.
|
||
*/
|
||
util.DataBuffer.prototype.getInt24 = function() {
|
||
var rval = (
|
||
this.data.getInt16(this.read) << 8 ^
|
||
this.data.getInt8(this.read + 2));
|
||
this.read += 3;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint32 from this buffer in big-endian order and advances the read
|
||
* pointer by 4.
|
||
*
|
||
* @return the word.
|
||
*/
|
||
util.DataBuffer.prototype.getInt32 = function() {
|
||
var rval = this.data.getInt32(this.read);
|
||
this.read += 4;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint16 from this buffer in little-endian order and advances the read
|
||
* pointer by 2.
|
||
*
|
||
* @return the uint16.
|
||
*/
|
||
util.DataBuffer.prototype.getInt16Le = function() {
|
||
var rval = this.data.getInt16(this.read, true);
|
||
this.read += 2;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint24 from this buffer in little-endian order and advances the read
|
||
* pointer by 3.
|
||
*
|
||
* @return the uint24.
|
||
*/
|
||
util.DataBuffer.prototype.getInt24Le = function() {
|
||
var rval = (
|
||
this.data.getInt8(this.read) ^
|
||
this.data.getInt16(this.read + 1, true) << 8);
|
||
this.read += 3;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a uint32 from this buffer in little-endian order and advances the read
|
||
* pointer by 4.
|
||
*
|
||
* @return the word.
|
||
*/
|
||
util.DataBuffer.prototype.getInt32Le = function() {
|
||
var rval = this.data.getInt32(this.read, true);
|
||
this.read += 4;
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets an n-bit integer from this buffer in big-endian order and advances the
|
||
* read pointer by n/8.
|
||
*
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return the integer.
|
||
*/
|
||
util.DataBuffer.prototype.getInt = function(n) {
|
||
var rval = 0;
|
||
do {
|
||
rval = (rval << 8) + this.data.getInt8(this.read++);
|
||
n -= 8;
|
||
} while(n > 0);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a signed n-bit integer from this buffer in big-endian order, using
|
||
* two's complement, and advances the read pointer by n/8.
|
||
*
|
||
* @param n the number of bits in the integer.
|
||
*
|
||
* @return the integer.
|
||
*/
|
||
util.DataBuffer.prototype.getSignedInt = function(n) {
|
||
var x = this.getInt(n);
|
||
var max = 2 << (n - 2);
|
||
if(x >= max) {
|
||
x -= max << 1;
|
||
}
|
||
return x;
|
||
};
|
||
|
||
/**
|
||
* Reads bytes out into a UTF-8 string and clears them from the buffer.
|
||
*
|
||
* @param count the number of bytes to read, undefined or null for all.
|
||
*
|
||
* @return a UTF-8 string of bytes.
|
||
*/
|
||
util.DataBuffer.prototype.getBytes = function(count) {
|
||
// TODO: deprecate this method, it is poorly named and
|
||
// this.toString('binary') replaces it
|
||
// add a toTypedArray()/toArrayBuffer() function
|
||
var rval;
|
||
if(count) {
|
||
// read count bytes
|
||
count = Math.min(this.length(), count);
|
||
rval = this.data.slice(this.read, this.read + count);
|
||
this.read += count;
|
||
} else if(count === 0) {
|
||
rval = '';
|
||
} else {
|
||
// read all bytes, optimize to only copy when needed
|
||
rval = (this.read === 0) ? this.data : this.data.slice(this.read);
|
||
this.clear();
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets a UTF-8 encoded string of the bytes from this buffer without modifying
|
||
* the read pointer.
|
||
*
|
||
* @param count the number of bytes to get, omit to get all.
|
||
*
|
||
* @return a string full of UTF-8 encoded characters.
|
||
*/
|
||
util.DataBuffer.prototype.bytes = function(count) {
|
||
// TODO: deprecate this method, it is poorly named, add "getString()"
|
||
return (typeof(count) === 'undefined' ?
|
||
this.data.slice(this.read) :
|
||
this.data.slice(this.read, this.read + count));
|
||
};
|
||
|
||
/**
|
||
* Gets a byte at the given index without modifying the read pointer.
|
||
*
|
||
* @param i the byte index.
|
||
*
|
||
* @return the byte.
|
||
*/
|
||
util.DataBuffer.prototype.at = function(i) {
|
||
return this.data.getUint8(this.read + i);
|
||
};
|
||
|
||
/**
|
||
* Puts a byte at the given index without modifying the read pointer.
|
||
*
|
||
* @param i the byte index.
|
||
* @param b the byte to put.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.setAt = function(i, b) {
|
||
this.data.setUint8(i, b);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Gets the last byte without modifying the read pointer.
|
||
*
|
||
* @return the last byte.
|
||
*/
|
||
util.DataBuffer.prototype.last = function() {
|
||
return this.data.getUint8(this.write - 1);
|
||
};
|
||
|
||
/**
|
||
* Creates a copy of this buffer.
|
||
*
|
||
* @return the copy.
|
||
*/
|
||
util.DataBuffer.prototype.copy = function() {
|
||
return new util.DataBuffer(this);
|
||
};
|
||
|
||
/**
|
||
* Compacts this buffer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.compact = function() {
|
||
if(this.read > 0) {
|
||
var src = new Uint8Array(this.data.buffer, this.read);
|
||
var dst = new Uint8Array(src.byteLength);
|
||
dst.set(src);
|
||
this.data = new DataView(dst);
|
||
this.write -= this.read;
|
||
this.read = 0;
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Clears this buffer.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.clear = function() {
|
||
this.data = new DataView(new ArrayBuffer(0));
|
||
this.read = this.write = 0;
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Shortens this buffer by triming bytes off of the end of this buffer.
|
||
*
|
||
* @param count the number of bytes to trim off.
|
||
*
|
||
* @return this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.truncate = function(count) {
|
||
this.write = Math.max(0, this.length() - count);
|
||
this.read = Math.min(this.read, this.write);
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Converts this buffer to a hexadecimal string.
|
||
*
|
||
* @return a hexadecimal string.
|
||
*/
|
||
util.DataBuffer.prototype.toHex = function() {
|
||
var rval = '';
|
||
for(var i = this.read; i < this.data.byteLength; ++i) {
|
||
var b = this.data.getUint8(i);
|
||
if(b < 16) {
|
||
rval += '0';
|
||
}
|
||
rval += b.toString(16);
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts this buffer to a string, using the given encoding. If no
|
||
* encoding is given, 'utf8' (UTF-8) is used.
|
||
*
|
||
* @param [encoding] the encoding to use: 'binary', 'utf8', 'utf16', 'hex',
|
||
* 'base64' (default: 'utf8').
|
||
*
|
||
* @return a string representation of the bytes in this buffer.
|
||
*/
|
||
util.DataBuffer.prototype.toString = function(encoding) {
|
||
var view = new Uint8Array(this.data, this.read, this.length());
|
||
encoding = encoding || 'utf8';
|
||
|
||
// encode to string
|
||
if(encoding === 'binary' || encoding === 'raw') {
|
||
return util.binary.raw.encode(view);
|
||
}
|
||
if(encoding === 'hex') {
|
||
return util.binary.hex.encode(view);
|
||
}
|
||
if(encoding === 'base64') {
|
||
return util.binary.base64.encode(view);
|
||
}
|
||
|
||
// decode to text
|
||
if(encoding === 'utf8') {
|
||
return util.text.utf8.decode(view);
|
||
}
|
||
if(encoding === 'utf16') {
|
||
return util.text.utf16.decode(view);
|
||
}
|
||
|
||
throw new Error('Invalid encoding: ' + encoding);
|
||
};
|
||
|
||
/** End Buffer w/UInt8Array backing */
|
||
|
||
|
||
/**
|
||
* Creates a buffer that stores bytes. A value may be given to put into the
|
||
* buffer that is either a string of bytes or a UTF-16 string that will
|
||
* be encoded using UTF-8 (to do the latter, specify 'utf8' as the encoding).
|
||
*
|
||
* @param [input] the bytes to wrap (as a string) or a UTF-16 string to encode
|
||
* as UTF-8.
|
||
* @param [encoding] (default: 'raw', other: 'utf8').
|
||
*/
|
||
util.createBuffer = function(input, encoding) {
|
||
// TODO: deprecate, use new ByteBuffer() instead
|
||
encoding = encoding || 'raw';
|
||
if(input !== undefined && encoding === 'utf8') {
|
||
input = util.encodeUtf8(input);
|
||
}
|
||
return new util.ByteBuffer(input);
|
||
};
|
||
|
||
/**
|
||
* Fills a string with a particular value. If you want the string to be a byte
|
||
* string, pass in String.fromCharCode(theByte).
|
||
*
|
||
* @param c the character to fill the string with, use String.fromCharCode
|
||
* to fill the string with a byte value.
|
||
* @param n the number of characters of value c to fill with.
|
||
*
|
||
* @return the filled string.
|
||
*/
|
||
util.fillString = function(c, n) {
|
||
var s = '';
|
||
while(n > 0) {
|
||
if(n & 1) {
|
||
s += c;
|
||
}
|
||
n >>>= 1;
|
||
if(n > 0) {
|
||
c += c;
|
||
}
|
||
}
|
||
return s;
|
||
};
|
||
|
||
/**
|
||
* Performs a per byte XOR between two byte strings and returns the result as a
|
||
* string of bytes.
|
||
*
|
||
* @param s1 first string of bytes.
|
||
* @param s2 second string of bytes.
|
||
* @param n the number of bytes to XOR.
|
||
*
|
||
* @return the XOR'd result.
|
||
*/
|
||
util.xorBytes = function(s1, s2, n) {
|
||
var s3 = '';
|
||
var b = '';
|
||
var t = '';
|
||
var i = 0;
|
||
var c = 0;
|
||
for(; n > 0; --n, ++i) {
|
||
b = s1.charCodeAt(i) ^ s2.charCodeAt(i);
|
||
if(c >= 10) {
|
||
s3 += t;
|
||
t = '';
|
||
c = 0;
|
||
}
|
||
t += String.fromCharCode(b);
|
||
++c;
|
||
}
|
||
s3 += t;
|
||
return s3;
|
||
};
|
||
|
||
/**
|
||
* Converts a hex string into a 'binary' encoded string of bytes.
|
||
*
|
||
* @param hex the hexadecimal string to convert.
|
||
*
|
||
* @return the binary-encoded string of bytes.
|
||
*/
|
||
util.hexToBytes = function(hex) {
|
||
// TODO: deprecate: "Deprecated. Use util.binary.hex.decode instead."
|
||
var rval = '';
|
||
var i = 0;
|
||
if(hex.length & 1 == 1) {
|
||
// odd number of characters, convert first character alone
|
||
i = 1;
|
||
rval += String.fromCharCode(parseInt(hex[0], 16));
|
||
}
|
||
// convert 2 characters (1 byte) at a time
|
||
for(; i < hex.length; i += 2) {
|
||
rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts a 'binary' encoded string of bytes to hex.
|
||
*
|
||
* @param bytes the byte string to convert.
|
||
*
|
||
* @return the string of hexadecimal characters.
|
||
*/
|
||
util.bytesToHex = function(bytes) {
|
||
// TODO: deprecate: "Deprecated. Use util.binary.hex.encode instead."
|
||
return util.createBuffer(bytes).toHex();
|
||
};
|
||
|
||
/**
|
||
* Converts an 32-bit integer to 4-big-endian byte string.
|
||
*
|
||
* @param i the integer.
|
||
*
|
||
* @return the byte string.
|
||
*/
|
||
util.int32ToBytes = function(i) {
|
||
return (
|
||
String.fromCharCode(i >> 24 & 0xFF) +
|
||
String.fromCharCode(i >> 16 & 0xFF) +
|
||
String.fromCharCode(i >> 8 & 0xFF) +
|
||
String.fromCharCode(i & 0xFF));
|
||
};
|
||
|
||
// base64 characters, reverse mapping
|
||
var _base64 =
|
||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||
var _base64Idx = [
|
||
/*43 -43 = 0*/
|
||
/*'+', 1, 2, 3,'/' */
|
||
62, -1, -1, -1, 63,
|
||
|
||
/*'0','1','2','3','4','5','6','7','8','9' */
|
||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
||
|
||
/*15, 16, 17,'=', 19, 20, 21 */
|
||
-1, -1, -1, 64, -1, -1, -1,
|
||
|
||
/*65 - 43 = 22*/
|
||
/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
|
||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||
|
||
/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
|
||
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
||
|
||
/*91 - 43 = 48 */
|
||
/*48, 49, 50, 51, 52, 53 */
|
||
-1, -1, -1, -1, -1, -1,
|
||
|
||
/*97 - 43 = 54*/
|
||
/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
|
||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||
|
||
/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
|
||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||
];
|
||
|
||
/**
|
||
* Base64 encodes a 'binary' encoded string of bytes.
|
||
*
|
||
* @param input the binary encoded string of bytes to base64-encode.
|
||
* @param maxline the maximum number of encoded characters per line to use,
|
||
* defaults to none.
|
||
*
|
||
* @return the base64-encoded output.
|
||
*/
|
||
util.encode64 = function(input, maxline) {
|
||
// TODO: deprecate: "Deprecated. Use util.binary.base64.encode instead."
|
||
var line = '';
|
||
var output = '';
|
||
var chr1, chr2, chr3;
|
||
var i = 0;
|
||
while(i < input.length) {
|
||
chr1 = input.charCodeAt(i++);
|
||
chr2 = input.charCodeAt(i++);
|
||
chr3 = input.charCodeAt(i++);
|
||
|
||
// encode 4 character group
|
||
line += _base64.charAt(chr1 >> 2);
|
||
line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
|
||
if(isNaN(chr2)) {
|
||
line += '==';
|
||
} else {
|
||
line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
|
||
line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
|
||
}
|
||
|
||
if(maxline && line.length > maxline) {
|
||
output += line.substr(0, maxline) + '\r\n';
|
||
line = line.substr(maxline);
|
||
}
|
||
}
|
||
output += line;
|
||
return output;
|
||
};
|
||
|
||
/**
|
||
* Base64 decodes a string into a 'binary' encoded string of bytes.
|
||
*
|
||
* @param input the base64-encoded input.
|
||
*
|
||
* @return the binary encoded string.
|
||
*/
|
||
util.decode64 = function(input) {
|
||
// TODO: deprecate: "Deprecated. Use util.binary.base64.decode instead."
|
||
|
||
// remove all non-base64 characters
|
||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
|
||
|
||
var output = '';
|
||
var enc1, enc2, enc3, enc4;
|
||
var i = 0;
|
||
|
||
while(i < input.length) {
|
||
enc1 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc2 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc3 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc4 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
|
||
output += String.fromCharCode((enc1 << 2) | (enc2 >> 4));
|
||
if(enc3 !== 64) {
|
||
// decoded at least 2 bytes
|
||
output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2));
|
||
if(enc4 !== 64) {
|
||
// decoded 3 bytes
|
||
output += String.fromCharCode(((enc3 & 3) << 6) | enc4);
|
||
}
|
||
}
|
||
}
|
||
|
||
return output;
|
||
};
|
||
|
||
/**
|
||
* UTF-8 encodes the given UTF-16 encoded string (a standard JavaScript
|
||
* string). Non-ASCII characters will be encoded as multiple bytes according
|
||
* to UTF-8.
|
||
*
|
||
* @param str the string to encode.
|
||
*
|
||
* @return the UTF-8 encoded string.
|
||
*/
|
||
util.encodeUtf8 = function(str) {
|
||
return unescape(encodeURIComponent(str));
|
||
};
|
||
|
||
/**
|
||
* Decodes a UTF-8 encoded string into a UTF-16 string.
|
||
*
|
||
* @param str the string to decode.
|
||
*
|
||
* @return the UTF-16 encoded string (standard JavaScript string).
|
||
*/
|
||
util.decodeUtf8 = function(str) {
|
||
return decodeURIComponent(escape(str));
|
||
};
|
||
|
||
// binary encoding/decoding tools
|
||
// FIXME: Experimental. Do not use yet.
|
||
util.binary = {
|
||
raw: {},
|
||
hex: {},
|
||
base64: {}
|
||
};
|
||
|
||
/**
|
||
* Encodes a Uint8Array as a binary-encoded string. This encoding uses
|
||
* a value between 0 and 255 for each character.
|
||
*
|
||
* @param bytes the Uint8Array to encode.
|
||
*
|
||
* @return the binary-encoded string.
|
||
*/
|
||
util.binary.raw.encode = function(bytes) {
|
||
return String.fromCharCode.apply(null, bytes);
|
||
};
|
||
|
||
/**
|
||
* Decodes a binary-encoded string to a Uint8Array. This encoding uses
|
||
* a value between 0 and 255 for each character.
|
||
*
|
||
* @param str the binary-encoded string to decode.
|
||
* @param [output] an optional Uint8Array to write the output to; if it
|
||
* is too small, an exception will be thrown.
|
||
* @param [offset] the start offset for writing to the output (default: 0).
|
||
*
|
||
* @return the Uint8Array or the number of bytes written if output was given.
|
||
*/
|
||
util.binary.raw.decode = function(str, output, offset) {
|
||
var out = output;
|
||
if(!out) {
|
||
out = new Uint8Array(str.length);
|
||
}
|
||
offset = offset || 0;
|
||
var j = offset;
|
||
for(var i = 0; i < str.length; ++i) {
|
||
out[j++] = str.charCodeAt(i);
|
||
}
|
||
return output ? (j - offset) : out;
|
||
};
|
||
|
||
/**
|
||
* Encodes a 'binary' string, ArrayBuffer, DataView, TypedArray, or
|
||
* ByteBuffer as a string of hexadecimal characters.
|
||
*
|
||
* @param bytes the bytes to convert.
|
||
*
|
||
* @return the string of hexadecimal characters.
|
||
*/
|
||
util.binary.hex.encode = util.bytesToHex;
|
||
|
||
/**
|
||
* Decodes a hex-encoded string to a Uint8Array.
|
||
*
|
||
* @param hex the hexadecimal string to convert.
|
||
* @param [output] an optional Uint8Array to write the output to; if it
|
||
* is too small, an exception will be thrown.
|
||
* @param [offset] the start offset for writing to the output (default: 0).
|
||
*
|
||
* @return the Uint8Array or the number of bytes written if output was given.
|
||
*/
|
||
util.binary.hex.decode = function(hex, output, offset) {
|
||
var out = output;
|
||
if(!out) {
|
||
out = new Uint8Array(Math.ceil(hex.length / 2));
|
||
}
|
||
offset = offset || 0;
|
||
var i = 0, j = offset;
|
||
if(hex.length & 1) {
|
||
// odd number of characters, convert first character alone
|
||
i = 1;
|
||
out[j++] = parseInt(hex[0], 16);
|
||
}
|
||
// convert 2 characters (1 byte) at a time
|
||
for(; i < hex.length; i += 2) {
|
||
out[j++] = parseInt(hex.substr(i, 2), 16);
|
||
}
|
||
return output ? (j - offset) : out;
|
||
};
|
||
|
||
/**
|
||
* Base64-encodes a Uint8Array.
|
||
*
|
||
* @param input the Uint8Array to encode.
|
||
* @param maxline the maximum number of encoded characters per line to use,
|
||
* defaults to none.
|
||
*
|
||
* @return the base64-encoded output string.
|
||
*/
|
||
util.binary.base64.encode = function(input, maxline) {
|
||
var line = '';
|
||
var output = '';
|
||
var chr1, chr2, chr3;
|
||
var i = 0;
|
||
while(i < input.byteLength) {
|
||
chr1 = input[i++];
|
||
chr2 = input[i++];
|
||
chr3 = input[i++];
|
||
|
||
// encode 4 character group
|
||
line += _base64.charAt(chr1 >> 2);
|
||
line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
|
||
if(isNaN(chr2)) {
|
||
line += '==';
|
||
} else {
|
||
line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
|
||
line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
|
||
}
|
||
|
||
if(maxline && line.length > maxline) {
|
||
output += line.substr(0, maxline) + '\r\n';
|
||
line = line.substr(maxline);
|
||
}
|
||
}
|
||
output += line;
|
||
return output;
|
||
};
|
||
|
||
/**
|
||
* Decodes a base64-encoded string to a Uint8Array.
|
||
*
|
||
* @param input the base64-encoded input string.
|
||
* @param [output] an optional Uint8Array to write the output to; if it
|
||
* is too small, an exception will be thrown.
|
||
* @param [offset] the start offset for writing to the output (default: 0).
|
||
*
|
||
* @return the Uint8Array or the number of bytes written if output was given.
|
||
*/
|
||
util.binary.base64.decode = function(input, output, offset) {
|
||
var out = output;
|
||
if(!out) {
|
||
out = new Uint8Array(Math.ceil(input.length / 4) * 3);
|
||
}
|
||
|
||
// remove all non-base64 characters
|
||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
|
||
|
||
offset = offset || 0;
|
||
var enc1, enc2, enc3, enc4;
|
||
var i = 0, j = offset;
|
||
|
||
while(i < input.length) {
|
||
enc1 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc2 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc3 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
enc4 = _base64Idx[input.charCodeAt(i++) - 43];
|
||
|
||
out[j++] = (enc1 << 2) | (enc2 >> 4);
|
||
if(enc3 !== 64) {
|
||
// decoded at least 2 bytes
|
||
out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||
if(enc4 !== 64) {
|
||
// decoded 3 bytes
|
||
out[j++] = ((enc3 & 3) << 6) | enc4;
|
||
}
|
||
}
|
||
}
|
||
|
||
// make sure result is the exact decoded length
|
||
return output ?
|
||
(j - offset) :
|
||
out.subarray(0, j);
|
||
};
|
||
|
||
// text encoding/decoding tools
|
||
// FIXME: Experimental. Do not use yet.
|
||
util.text = {
|
||
utf8: {},
|
||
utf16: {}
|
||
};
|
||
|
||
/**
|
||
* Encodes the given string as UTF-8 in a Uint8Array.
|
||
*
|
||
* @param str the string to encode.
|
||
* @param [output] an optional Uint8Array to write the output to; if it
|
||
* is too small, an exception will be thrown.
|
||
* @param [offset] the start offset for writing to the output (default: 0).
|
||
*
|
||
* @return the Uint8Array or the number of bytes written if output was given.
|
||
*/
|
||
util.text.utf8.encode = function(str, output, offset) {
|
||
str = util.encodeUtf8(str);
|
||
var out = output;
|
||
if(!out) {
|
||
out = new Uint8Array(str.length);
|
||
}
|
||
offset = offset || 0;
|
||
var j = offset;
|
||
for(var i = 0; i < str.length; ++i) {
|
||
out[j++] = str.charCodeAt(i);
|
||
}
|
||
return output ? (j - offset) : out;
|
||
};
|
||
|
||
/**
|
||
* Decodes the UTF-8 contents from a Uint8Array.
|
||
*
|
||
* @param bytes the Uint8Array to decode.
|
||
*
|
||
* @return the resulting string.
|
||
*/
|
||
util.text.utf8.decode = function(bytes) {
|
||
return util.decodeUtf8(String.fromCharCode.apply(null, bytes));
|
||
};
|
||
|
||
/**
|
||
* Encodes the given string as UTF-16 in a Uint8Array.
|
||
*
|
||
* @param str the string to encode.
|
||
* @param [output] an optional Uint8Array to write the output to; if it
|
||
* is too small, an exception will be thrown.
|
||
* @param [offset] the start offset for writing to the output (default: 0).
|
||
*
|
||
* @return the Uint8Array or the number of bytes written if output was given.
|
||
*/
|
||
util.text.utf16.encode = function(str, output, offset) {
|
||
var out = output;
|
||
if(!out) {
|
||
out = new Uint8Array(str.length * 2);
|
||
}
|
||
var view = new Uint16Array(out.buffer);
|
||
offset = offset || 0;
|
||
var j = offset;
|
||
var k = offset;
|
||
for(var i = 0; i < str.length; ++i) {
|
||
view[k++] = str.charCodeAt(i);
|
||
j += 2;
|
||
}
|
||
return output ? (j - offset) : out;
|
||
};
|
||
|
||
/**
|
||
* Decodes the UTF-16 contents from a Uint8Array.
|
||
*
|
||
* @param bytes the Uint8Array to decode.
|
||
*
|
||
* @return the resulting string.
|
||
*/
|
||
util.text.utf16.decode = function(bytes) {
|
||
return String.fromCharCode.apply(null, new Uint16Array(bytes.buffer));
|
||
};
|
||
|
||
/**
|
||
* Deflates the given data using a flash interface.
|
||
*
|
||
* @param api the flash interface.
|
||
* @param bytes the data.
|
||
* @param raw true to return only raw deflate data, false to include zlib
|
||
* header and trailer.
|
||
*
|
||
* @return the deflated data as a string.
|
||
*/
|
||
util.deflate = function(api, bytes, raw) {
|
||
bytes = util.decode64(api.deflate(util.encode64(bytes)).rval);
|
||
|
||
// strip zlib header and trailer if necessary
|
||
if(raw) {
|
||
// zlib header is 2 bytes (CMF,FLG) where FLG indicates that
|
||
// there is a 4-byte DICT (alder-32) block before the data if
|
||
// its 5th bit is set
|
||
var start = 2;
|
||
var flg = bytes.charCodeAt(1);
|
||
if(flg & 0x20) {
|
||
start = 6;
|
||
}
|
||
// zlib trailer is 4 bytes of adler-32
|
||
bytes = bytes.substring(start, bytes.length - 4);
|
||
}
|
||
|
||
return bytes;
|
||
};
|
||
|
||
/**
|
||
* Inflates the given data using a flash interface.
|
||
*
|
||
* @param api the flash interface.
|
||
* @param bytes the data.
|
||
* @param raw true if the incoming data has no zlib header or trailer and is
|
||
* raw DEFLATE data.
|
||
*
|
||
* @return the inflated data as a string, null on error.
|
||
*/
|
||
util.inflate = function(api, bytes, raw) {
|
||
// TODO: add zlib header and trailer if necessary/possible
|
||
var rval = api.inflate(util.encode64(bytes)).rval;
|
||
return (rval === null) ? null : util.decode64(rval);
|
||
};
|
||
|
||
/**
|
||
* Sets a storage object.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
* @param obj the storage object, null to remove.
|
||
*/
|
||
var _setStorageObject = function(api, id, obj) {
|
||
if(!api) {
|
||
throw new Error('WebStorage not available.');
|
||
}
|
||
|
||
var rval;
|
||
if(obj === null) {
|
||
rval = api.removeItem(id);
|
||
} else {
|
||
// json-encode and base64-encode object
|
||
obj = util.encode64(JSON.stringify(obj));
|
||
rval = api.setItem(id, obj);
|
||
}
|
||
|
||
// handle potential flash error
|
||
if(typeof(rval) !== 'undefined' && rval.rval !== true) {
|
||
var error = new Error(rval.error.message);
|
||
error.id = rval.error.id;
|
||
error.name = rval.error.name;
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Gets a storage object.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
*
|
||
* @return the storage object entry or null if none exists.
|
||
*/
|
||
var _getStorageObject = function(api, id) {
|
||
if(!api) {
|
||
throw new Error('WebStorage not available.');
|
||
}
|
||
|
||
// get the existing entry
|
||
var rval = api.getItem(id);
|
||
|
||
/* Note: We check api.init because we can't do (api == localStorage)
|
||
on IE because of "Class doesn't support Automation" exception. Only
|
||
the flash api has an init method so this works too, but we need a
|
||
better solution in the future. */
|
||
|
||
// flash returns item wrapped in an object, handle special case
|
||
if(api.init) {
|
||
if(rval.rval === null) {
|
||
if(rval.error) {
|
||
var error = new Error(rval.error.message);
|
||
error.id = rval.error.id;
|
||
error.name = rval.error.name;
|
||
throw error;
|
||
}
|
||
// no error, but also no item
|
||
rval = null;
|
||
} else {
|
||
rval = rval.rval;
|
||
}
|
||
}
|
||
|
||
// handle decoding
|
||
if(rval !== null) {
|
||
// base64-decode and json-decode data
|
||
rval = JSON.parse(util.decode64(rval));
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Stores an item in local storage.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
* @param data the data for the item (any javascript object/primitive).
|
||
*/
|
||
var _setItem = function(api, id, key, data) {
|
||
// get storage object
|
||
var obj = _getStorageObject(api, id);
|
||
if(obj === null) {
|
||
// create a new storage object
|
||
obj = {};
|
||
}
|
||
// update key
|
||
obj[key] = data;
|
||
|
||
// set storage object
|
||
_setStorageObject(api, id, obj);
|
||
};
|
||
|
||
/**
|
||
* Gets an item from local storage.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
*
|
||
* @return the item.
|
||
*/
|
||
var _getItem = function(api, id, key) {
|
||
// get storage object
|
||
var rval = _getStorageObject(api, id);
|
||
if(rval !== null) {
|
||
// return data at key
|
||
rval = (key in rval) ? rval[key] : null;
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Removes an item from local storage.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
*/
|
||
var _removeItem = function(api, id, key) {
|
||
// get storage object
|
||
var obj = _getStorageObject(api, id);
|
||
if(obj !== null && key in obj) {
|
||
// remove key
|
||
delete obj[key];
|
||
|
||
// see if entry has no keys remaining
|
||
var empty = true;
|
||
for(var prop in obj) {
|
||
empty = false;
|
||
break;
|
||
}
|
||
if(empty) {
|
||
// remove entry entirely if no keys are left
|
||
obj = null;
|
||
}
|
||
|
||
// set storage object
|
||
_setStorageObject(api, id, obj);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Clears the local disk storage identified by the given ID.
|
||
*
|
||
* @param api the storage interface.
|
||
* @param id the storage ID to use.
|
||
*/
|
||
var _clearItems = function(api, id) {
|
||
_setStorageObject(api, id, null);
|
||
};
|
||
|
||
/**
|
||
* Calls a storage function.
|
||
*
|
||
* @param func the function to call.
|
||
* @param args the arguments for the function.
|
||
* @param location the location argument.
|
||
*
|
||
* @return the return value from the function.
|
||
*/
|
||
var _callStorageFunction = function(func, args, location) {
|
||
var rval = null;
|
||
|
||
// default storage types
|
||
if(typeof(location) === 'undefined') {
|
||
location = ['web', 'flash'];
|
||
}
|
||
|
||
// apply storage types in order of preference
|
||
var type;
|
||
var done = false;
|
||
var exception = null;
|
||
for(var idx in location) {
|
||
type = location[idx];
|
||
try {
|
||
if(type === 'flash' || type === 'both') {
|
||
if(args[0] === null) {
|
||
throw new Error('Flash local storage not available.');
|
||
}
|
||
rval = func.apply(this, args);
|
||
done = (type === 'flash');
|
||
}
|
||
if(type === 'web' || type === 'both') {
|
||
args[0] = localStorage;
|
||
rval = func.apply(this, args);
|
||
done = true;
|
||
}
|
||
} catch(ex) {
|
||
exception = ex;
|
||
}
|
||
if(done) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(!done) {
|
||
throw exception;
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Stores an item on local disk.
|
||
*
|
||
* The available types of local storage include 'flash', 'web', and 'both'.
|
||
*
|
||
* The type 'flash' refers to flash local storage (SharedObject). In order
|
||
* to use flash local storage, the 'api' parameter must be valid. The type
|
||
* 'web' refers to WebStorage, if supported by the browser. The type 'both'
|
||
* refers to storing using both 'flash' and 'web', not just one or the
|
||
* other.
|
||
*
|
||
* The location array should list the storage types to use in order of
|
||
* preference:
|
||
*
|
||
* ['flash']: flash only storage
|
||
* ['web']: web only storage
|
||
* ['both']: try to store in both
|
||
* ['flash','web']: store in flash first, but if not available, 'web'
|
||
* ['web','flash']: store in web first, but if not available, 'flash'
|
||
*
|
||
* The location array defaults to: ['web', 'flash']
|
||
*
|
||
* @param api the flash interface, null to use only WebStorage.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
* @param data the data for the item (any javascript object/primitive).
|
||
* @param location an array with the preferred types of storage to use.
|
||
*/
|
||
util.setItem = function(api, id, key, data, location) {
|
||
_callStorageFunction(_setItem, arguments, location);
|
||
};
|
||
|
||
/**
|
||
* Gets an item on local disk.
|
||
*
|
||
* Set setItem() for details on storage types.
|
||
*
|
||
* @param api the flash interface, null to use only WebStorage.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
* @param location an array with the preferred types of storage to use.
|
||
*
|
||
* @return the item.
|
||
*/
|
||
util.getItem = function(api, id, key, location) {
|
||
return _callStorageFunction(_getItem, arguments, location);
|
||
};
|
||
|
||
/**
|
||
* Removes an item on local disk.
|
||
*
|
||
* Set setItem() for details on storage types.
|
||
*
|
||
* @param api the flash interface.
|
||
* @param id the storage ID to use.
|
||
* @param key the key for the item.
|
||
* @param location an array with the preferred types of storage to use.
|
||
*/
|
||
util.removeItem = function(api, id, key, location) {
|
||
_callStorageFunction(_removeItem, arguments, location);
|
||
};
|
||
|
||
/**
|
||
* Clears the local disk storage identified by the given ID.
|
||
*
|
||
* Set setItem() for details on storage types.
|
||
*
|
||
* @param api the flash interface if flash is available.
|
||
* @param id the storage ID to use.
|
||
* @param location an array with the preferred types of storage to use.
|
||
*/
|
||
util.clearItems = function(api, id, location) {
|
||
_callStorageFunction(_clearItems, arguments, location);
|
||
};
|
||
|
||
/**
|
||
* Parses the scheme, host, and port from an http(s) url.
|
||
*
|
||
* @param str the url string.
|
||
*
|
||
* @return the parsed url object or null if the url is invalid.
|
||
*/
|
||
util.parseUrl = function(str) {
|
||
// FIXME: this regex looks a bit broken
|
||
var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;
|
||
regex.lastIndex = 0;
|
||
var m = regex.exec(str);
|
||
var url = (m === null) ? null : {
|
||
full: str,
|
||
scheme: m[1],
|
||
host: m[2],
|
||
port: m[3],
|
||
path: m[4]
|
||
};
|
||
if(url) {
|
||
url.fullHost = url.host;
|
||
if(url.port) {
|
||
if(url.port !== 80 && url.scheme === 'http') {
|
||
url.fullHost += ':' + url.port;
|
||
} else if(url.port !== 443 && url.scheme === 'https') {
|
||
url.fullHost += ':' + url.port;
|
||
}
|
||
} else if(url.scheme === 'http') {
|
||
url.port = 80;
|
||
} else if(url.scheme === 'https') {
|
||
url.port = 443;
|
||
}
|
||
url.full = url.scheme + '://' + url.fullHost;
|
||
}
|
||
return url;
|
||
};
|
||
|
||
/* Storage for query variables */
|
||
var _queryVariables = null;
|
||
|
||
/**
|
||
* Returns the window location query variables. Query is parsed on the first
|
||
* call and the same object is returned on subsequent calls. The mapping
|
||
* is from keys to an array of values. Parameters without values will have
|
||
* an object key set but no value added to the value array. Values are
|
||
* unescaped.
|
||
*
|
||
* ...?k1=v1&k2=v2:
|
||
* {
|
||
* "k1": ["v1"],
|
||
* "k2": ["v2"]
|
||
* }
|
||
*
|
||
* ...?k1=v1&k1=v2:
|
||
* {
|
||
* "k1": ["v1", "v2"]
|
||
* }
|
||
*
|
||
* ...?k1=v1&k2:
|
||
* {
|
||
* "k1": ["v1"],
|
||
* "k2": []
|
||
* }
|
||
*
|
||
* ...?k1=v1&k1:
|
||
* {
|
||
* "k1": ["v1"]
|
||
* }
|
||
*
|
||
* ...?k1&k1:
|
||
* {
|
||
* "k1": []
|
||
* }
|
||
*
|
||
* @param query the query string to parse (optional, default to cached
|
||
* results from parsing window location search query).
|
||
*
|
||
* @return object mapping keys to variables.
|
||
*/
|
||
util.getQueryVariables = function(query) {
|
||
var parse = function(q) {
|
||
var rval = {};
|
||
var kvpairs = q.split('&');
|
||
for(var i = 0; i < kvpairs.length; i++) {
|
||
var pos = kvpairs[i].indexOf('=');
|
||
var key;
|
||
var val;
|
||
if(pos > 0) {
|
||
key = kvpairs[i].substring(0, pos);
|
||
val = kvpairs[i].substring(pos + 1);
|
||
} else {
|
||
key = kvpairs[i];
|
||
val = null;
|
||
}
|
||
if(!(key in rval)) {
|
||
rval[key] = [];
|
||
}
|
||
// disallow overriding object prototype keys
|
||
if(!(key in Object.prototype) && val !== null) {
|
||
rval[key].push(unescape(val));
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
var rval;
|
||
if(typeof(query) === 'undefined') {
|
||
// set cached variables if needed
|
||
if(_queryVariables === null) {
|
||
if(typeof(window) !== 'undefined' && window.location && window.location.search) {
|
||
// parse window search query
|
||
_queryVariables = parse(window.location.search.substring(1));
|
||
} else {
|
||
// no query variables available
|
||
_queryVariables = {};
|
||
}
|
||
}
|
||
rval = _queryVariables;
|
||
} else {
|
||
// parse given query
|
||
rval = parse(query);
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Parses a fragment into a path and query. This method will take a URI
|
||
* fragment and break it up as if it were the main URI. For example:
|
||
* /bar/baz?a=1&b=2
|
||
* results in:
|
||
* {
|
||
* path: ["bar", "baz"],
|
||
* query: {"k1": ["v1"], "k2": ["v2"]}
|
||
* }
|
||
*
|
||
* @return object with a path array and query object.
|
||
*/
|
||
util.parseFragment = function(fragment) {
|
||
// default to whole fragment
|
||
var fp = fragment;
|
||
var fq = '';
|
||
// split into path and query if possible at the first '?'
|
||
var pos = fragment.indexOf('?');
|
||
if(pos > 0) {
|
||
fp = fragment.substring(0, pos);
|
||
fq = fragment.substring(pos + 1);
|
||
}
|
||
// split path based on '/' and ignore first element if empty
|
||
var path = fp.split('/');
|
||
if(path.length > 0 && path[0] === '') {
|
||
path.shift();
|
||
}
|
||
// convert query into object
|
||
var query = (fq === '') ? {} : util.getQueryVariables(fq);
|
||
|
||
return {
|
||
pathString: fp,
|
||
queryString: fq,
|
||
path: path,
|
||
query: query
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Makes a request out of a URI-like request string. This is intended to
|
||
* be used where a fragment id (after a URI '#') is parsed as a URI with
|
||
* path and query parts. The string should have a path beginning and
|
||
* delimited by '/' and optional query parameters following a '?'. The
|
||
* query should be a standard URL set of key value pairs delimited by
|
||
* '&'. For backwards compatibility the initial '/' on the path is not
|
||
* required. The request object has the following API, (fully described
|
||
* in the method code):
|
||
* {
|
||
* path: <the path string part>.
|
||
* query: <the query string part>,
|
||
* getPath(i): get part or all of the split path array,
|
||
* getQuery(k, i): get part or all of a query key array,
|
||
* getQueryLast(k, _default): get last element of a query key array.
|
||
* }
|
||
*
|
||
* @return object with request parameters.
|
||
*/
|
||
util.makeRequest = function(reqString) {
|
||
var frag = util.parseFragment(reqString);
|
||
var req = {
|
||
// full path string
|
||
path: frag.pathString,
|
||
// full query string
|
||
query: frag.queryString,
|
||
/**
|
||
* Get path or element in path.
|
||
*
|
||
* @param i optional path index.
|
||
*
|
||
* @return path or part of path if i provided.
|
||
*/
|
||
getPath: function(i) {
|
||
return (typeof(i) === 'undefined') ? frag.path : frag.path[i];
|
||
},
|
||
/**
|
||
* Get query, values for a key, or value for a key index.
|
||
*
|
||
* @param k optional query key.
|
||
* @param i optional query key index.
|
||
*
|
||
* @return query, values for a key, or value for a key index.
|
||
*/
|
||
getQuery: function(k, i) {
|
||
var rval;
|
||
if(typeof(k) === 'undefined') {
|
||
rval = frag.query;
|
||
} else {
|
||
rval = frag.query[k];
|
||
if(rval && typeof(i) !== 'undefined') {
|
||
rval = rval[i];
|
||
}
|
||
}
|
||
return rval;
|
||
},
|
||
getQueryLast: function(k, _default) {
|
||
var rval;
|
||
var vals = req.getQuery(k);
|
||
if(vals) {
|
||
rval = vals[vals.length - 1];
|
||
} else {
|
||
rval = _default;
|
||
}
|
||
return rval;
|
||
}
|
||
};
|
||
return req;
|
||
};
|
||
|
||
/**
|
||
* Makes a URI out of a path, an object with query parameters, and a
|
||
* fragment. Uses jQuery.param() internally for query string creation.
|
||
* If the path is an array, it will be joined with '/'.
|
||
*
|
||
* @param path string path or array of strings.
|
||
* @param query object with query parameters. (optional)
|
||
* @param fragment fragment string. (optional)
|
||
*
|
||
* @return string object with request parameters.
|
||
*/
|
||
util.makeLink = function(path, query, fragment) {
|
||
// join path parts if needed
|
||
path = jQuery.isArray(path) ? path.join('/') : path;
|
||
|
||
var qstr = jQuery.param(query || {});
|
||
fragment = fragment || '';
|
||
return path +
|
||
((qstr.length > 0) ? ('?' + qstr) : '') +
|
||
((fragment.length > 0) ? ('#' + fragment) : '');
|
||
};
|
||
|
||
/**
|
||
* Follows a path of keys deep into an object hierarchy and set a value.
|
||
* If a key does not exist or it's value is not an object, create an
|
||
* object in it's place. This can be destructive to a object tree if
|
||
* leaf nodes are given as non-final path keys.
|
||
* Used to avoid exceptions from missing parts of the path.
|
||
*
|
||
* @param object the starting object.
|
||
* @param keys an array of string keys.
|
||
* @param value the value to set.
|
||
*/
|
||
util.setPath = function(object, keys, value) {
|
||
// need to start at an object
|
||
if(typeof(object) === 'object' && object !== null) {
|
||
var i = 0;
|
||
var len = keys.length;
|
||
while(i < len) {
|
||
var next = keys[i++];
|
||
if(i == len) {
|
||
// last
|
||
object[next] = value;
|
||
} else {
|
||
// more
|
||
var hasNext = (next in object);
|
||
if(!hasNext ||
|
||
(hasNext && typeof(object[next]) !== 'object') ||
|
||
(hasNext && object[next] === null)) {
|
||
object[next] = {};
|
||
}
|
||
object = object[next];
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Follows a path of keys deep into an object hierarchy and return a value.
|
||
* If a key does not exist, create an object in it's place.
|
||
* Used to avoid exceptions from missing parts of the path.
|
||
*
|
||
* @param object the starting object.
|
||
* @param keys an array of string keys.
|
||
* @param _default value to return if path not found.
|
||
*
|
||
* @return the value at the path if found, else default if given, else
|
||
* undefined.
|
||
*/
|
||
util.getPath = function(object, keys, _default) {
|
||
var i = 0;
|
||
var len = keys.length;
|
||
var hasNext = true;
|
||
while(hasNext && i < len &&
|
||
typeof(object) === 'object' && object !== null) {
|
||
var next = keys[i++];
|
||
hasNext = next in object;
|
||
if(hasNext) {
|
||
object = object[next];
|
||
}
|
||
}
|
||
return (hasNext ? object : _default);
|
||
};
|
||
|
||
/**
|
||
* Follow a path of keys deep into an object hierarchy and delete the
|
||
* last one. If a key does not exist, do nothing.
|
||
* Used to avoid exceptions from missing parts of the path.
|
||
*
|
||
* @param object the starting object.
|
||
* @param keys an array of string keys.
|
||
*/
|
||
util.deletePath = function(object, keys) {
|
||
// need to start at an object
|
||
if(typeof(object) === 'object' && object !== null) {
|
||
var i = 0;
|
||
var len = keys.length;
|
||
while(i < len) {
|
||
var next = keys[i++];
|
||
if(i == len) {
|
||
// last
|
||
delete object[next];
|
||
} else {
|
||
// more
|
||
if(!(next in object) ||
|
||
(typeof(object[next]) !== 'object') ||
|
||
(object[next] === null)) {
|
||
break;
|
||
}
|
||
object = object[next];
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Check if an object is empty.
|
||
*
|
||
* Taken from:
|
||
* http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json/679937#679937
|
||
*
|
||
* @param object the object to check.
|
||
*/
|
||
util.isEmpty = function(obj) {
|
||
for(var prop in obj) {
|
||
if(obj.hasOwnProperty(prop)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* Format with simple printf-style interpolation.
|
||
*
|
||
* %%: literal '%'
|
||
* %s,%o: convert next argument into a string.
|
||
*
|
||
* @param format the string to format.
|
||
* @param ... arguments to interpolate into the format string.
|
||
*/
|
||
util.format = function(format) {
|
||
var re = /%./g;
|
||
// current match
|
||
var match;
|
||
// current part
|
||
var part;
|
||
// current arg index
|
||
var argi = 0;
|
||
// collected parts to recombine later
|
||
var parts = [];
|
||
// last index found
|
||
var last = 0;
|
||
// loop while matches remain
|
||
while((match = re.exec(format))) {
|
||
part = format.substring(last, re.lastIndex - 2);
|
||
// don't add empty strings (ie, parts between %s%s)
|
||
if(part.length > 0) {
|
||
parts.push(part);
|
||
}
|
||
last = re.lastIndex;
|
||
// switch on % code
|
||
var code = match[0][1];
|
||
switch(code) {
|
||
case 's':
|
||
case 'o':
|
||
// check if enough arguments were given
|
||
if(argi < arguments.length) {
|
||
parts.push(arguments[argi++ + 1]);
|
||
} else {
|
||
parts.push('<?>');
|
||
}
|
||
break;
|
||
// FIXME: do proper formating for numbers, etc
|
||
//case 'f':
|
||
//case 'd':
|
||
case '%':
|
||
parts.push('%');
|
||
break;
|
||
default:
|
||
//parts.push('<' + '%' + code + '?>');
|
||
parts.push('<\u0023' + code + '?>'); // Need to escape this line for ASP.net
|
||
}
|
||
}
|
||
// add trailing part of format string
|
||
parts.push(format.substring(last));
|
||
return parts.join('');
|
||
};
|
||
|
||
/**
|
||
* Formats a number.
|
||
*
|
||
* http://snipplr.com/view/5945/javascript-numberformat--ported-from-php/
|
||
*/
|
||
util.formatNumber = function(number, decimals, dec_point, thousands_sep) {
|
||
// http://kevin.vanzonneveld.net
|
||
// + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
|
||
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||
// + bugfix by: Michael White (http://crestidg.com)
|
||
// + bugfix by: Benjamin Lupton
|
||
// + bugfix by: Allan Jensen (http://www.winternet.no)
|
||
// + revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
|
||
// * example 1: number_format(1234.5678, 2, '.', '');
|
||
// * returns 1: 1234.57
|
||
|
||
var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
|
||
var d = dec_point === undefined ? ',' : dec_point;
|
||
var t = thousands_sep === undefined ?
|
||
'.' : thousands_sep, s = n < 0 ? '-' : '';
|
||
var i = parseInt((n = Math.abs(+n || 0).toFixed(c)), 10) + '';
|
||
var j = (i.length > 3) ? i.length % 3 : 0;
|
||
return s + (j ? i.substr(0, j) + t : '') +
|
||
i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
|
||
(c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
|
||
};
|
||
|
||
/**
|
||
* Formats a byte size.
|
||
*
|
||
* http://snipplr.com/view/5949/format-humanize-file-byte-size-presentation-in-javascript/
|
||
*/
|
||
util.formatSize = function(size) {
|
||
if(size >= 1073741824) {
|
||
size = util.formatNumber(size / 1073741824, 2, '.', '') + ' GiB';
|
||
} else if(size >= 1048576) {
|
||
size = util.formatNumber(size / 1048576, 2, '.', '') + ' MiB';
|
||
} else if(size >= 1024) {
|
||
size = util.formatNumber(size / 1024, 0) + ' KiB';
|
||
} else {
|
||
size = util.formatNumber(size, 0) + ' bytes';
|
||
}
|
||
return size;
|
||
};
|
||
|
||
/**
|
||
* Converts an IPv4 or IPv6 string representation into bytes (in network order).
|
||
*
|
||
* @param ip the IPv4 or IPv6 address to convert.
|
||
*
|
||
* @return the 4-byte IPv6 or 16-byte IPv6 address or null if the address can't
|
||
* be parsed.
|
||
*/
|
||
util.bytesFromIP = function(ip) {
|
||
if(ip.indexOf('.') !== -1) {
|
||
return util.bytesFromIPv4(ip);
|
||
}
|
||
if(ip.indexOf(':') !== -1) {
|
||
return util.bytesFromIPv6(ip);
|
||
}
|
||
return null;
|
||
};
|
||
|
||
/**
|
||
* Converts an IPv4 string representation into bytes (in network order).
|
||
*
|
||
* @param ip the IPv4 address to convert.
|
||
*
|
||
* @return the 4-byte address or null if the address can't be parsed.
|
||
*/
|
||
util.bytesFromIPv4 = function(ip) {
|
||
ip = ip.split('.');
|
||
if(ip.length !== 4) {
|
||
return null;
|
||
}
|
||
var b = util.createBuffer();
|
||
for(var i = 0; i < ip.length; ++i) {
|
||
var num = parseInt(ip[i], 10);
|
||
if(isNaN(num)) {
|
||
return null;
|
||
}
|
||
b.putByte(num);
|
||
}
|
||
return b.getBytes();
|
||
};
|
||
|
||
/**
|
||
* Converts an IPv6 string representation into bytes (in network order).
|
||
*
|
||
* @param ip the IPv6 address to convert.
|
||
*
|
||
* @return the 16-byte address or null if the address can't be parsed.
|
||
*/
|
||
util.bytesFromIPv6 = function(ip) {
|
||
var blanks = 0;
|
||
ip = ip.split(':').filter(function(e) {
|
||
if(e.length === 0) ++blanks;
|
||
return true;
|
||
});
|
||
var zeros = (8 - ip.length + blanks) * 2;
|
||
var b = util.createBuffer();
|
||
for(var i = 0; i < 8; ++i) {
|
||
if(!ip[i] || ip[i].length === 0) {
|
||
b.fillWithByte(0, zeros);
|
||
zeros = 0;
|
||
continue;
|
||
}
|
||
var bytes = util.hexToBytes(ip[i]);
|
||
if(bytes.length < 2) {
|
||
b.putByte(0);
|
||
}
|
||
b.putBytes(bytes);
|
||
}
|
||
return b.getBytes();
|
||
};
|
||
|
||
/**
|
||
* Converts 4-bytes into an IPv4 string representation or 16-bytes into
|
||
* an IPv6 string representation. The bytes must be in network order.
|
||
*
|
||
* @param bytes the bytes to convert.
|
||
*
|
||
* @return the IPv4 or IPv6 string representation if 4 or 16 bytes,
|
||
* respectively, are given, otherwise null.
|
||
*/
|
||
util.bytesToIP = function(bytes) {
|
||
if(bytes.length === 4) {
|
||
return util.bytesToIPv4(bytes);
|
||
}
|
||
if(bytes.length === 16) {
|
||
return util.bytesToIPv6(bytes);
|
||
}
|
||
return null;
|
||
};
|
||
|
||
/**
|
||
* Converts 4-bytes into an IPv4 string representation. The bytes must be
|
||
* in network order.
|
||
*
|
||
* @param bytes the bytes to convert.
|
||
*
|
||
* @return the IPv4 string representation or null for an invalid # of bytes.
|
||
*/
|
||
util.bytesToIPv4 = function(bytes) {
|
||
if(bytes.length !== 4) {
|
||
return null;
|
||
}
|
||
var ip = [];
|
||
for(var i = 0; i < bytes.length; ++i) {
|
||
ip.push(bytes.charCodeAt(i));
|
||
}
|
||
return ip.join('.');
|
||
};
|
||
|
||
/**
|
||
* Converts 16-bytes into an IPv16 string representation. The bytes must be
|
||
* in network order.
|
||
*
|
||
* @param bytes the bytes to convert.
|
||
*
|
||
* @return the IPv16 string representation or null for an invalid # of bytes.
|
||
*/
|
||
util.bytesToIPv6 = function(bytes) {
|
||
if(bytes.length !== 16) {
|
||
return null;
|
||
}
|
||
var ip = [];
|
||
var zeroGroups = [];
|
||
var zeroMaxGroup = 0;
|
||
for(var i = 0; i < bytes.length; i += 2) {
|
||
var hex = util.bytesToHex(bytes[i] + bytes[i + 1]);
|
||
// canonicalize zero representation
|
||
while(hex[0] === '0' && hex !== '0') {
|
||
hex = hex.substr(1);
|
||
}
|
||
if(hex === '0') {
|
||
var last = zeroGroups[zeroGroups.length - 1];
|
||
var idx = ip.length;
|
||
if(!last || idx !== last.end + 1) {
|
||
zeroGroups.push({start: idx, end: idx});
|
||
} else {
|
||
last.end = idx;
|
||
if((last.end - last.start) >
|
||
(zeroGroups[zeroMaxGroup].end - zeroGroups[zeroMaxGroup].start)) {
|
||
zeroMaxGroup = zeroGroups.length - 1;
|
||
}
|
||
}
|
||
}
|
||
ip.push(hex);
|
||
}
|
||
if(zeroGroups.length > 0) {
|
||
var group = zeroGroups[zeroMaxGroup];
|
||
// only shorten group of length > 0
|
||
if(group.end - group.start > 0) {
|
||
ip.splice(group.start, group.end - group.start + 1, '');
|
||
if(group.start === 0) {
|
||
ip.unshift('');
|
||
}
|
||
if(group.end === 7) {
|
||
ip.push('');
|
||
}
|
||
}
|
||
}
|
||
return ip.join(':');
|
||
};
|
||
|
||
/**
|
||
* Estimates the number of processes that can be run concurrently. If
|
||
* creating Web Workers, keep in mind that the main JavaScript process needs
|
||
* its own core.
|
||
*
|
||
* @param options the options to use:
|
||
* update true to force an update (not use the cached value).
|
||
* @param callback(err, max) called once the operation completes.
|
||
*/
|
||
util.estimateCores = function(options, callback) {
|
||
if(typeof options === 'function') {
|
||
callback = options;
|
||
options = {};
|
||
}
|
||
options = options || {};
|
||
if('cores' in util && !options.update) {
|
||
return callback(null, util.cores);
|
||
}
|
||
if(typeof navigator !== 'undefined' &&
|
||
'hardwareConcurrency' in navigator &&
|
||
navigator.hardwareConcurrency > 0) {
|
||
util.cores = navigator.hardwareConcurrency;
|
||
return callback(null, util.cores);
|
||
}
|
||
if(typeof Worker === 'undefined') {
|
||
// workers not available
|
||
util.cores = 1;
|
||
return callback(null, util.cores);
|
||
}
|
||
if(typeof Blob === 'undefined') {
|
||
// can't estimate, default to 2
|
||
util.cores = 2;
|
||
return callback(null, util.cores);
|
||
}
|
||
|
||
// create worker concurrency estimation code as blob
|
||
var blobUrl = URL.createObjectURL(new Blob(['(',
|
||
function() {
|
||
self.addEventListener('message', function(e) {
|
||
// run worker for 4 ms
|
||
var st = Date.now();
|
||
var et = st + 4;
|
||
while(Date.now() < et);
|
||
self.postMessage({st: st, et: et});
|
||
});
|
||
}.toString(),
|
||
')()'], {type: 'application/javascript'}));
|
||
|
||
// take 5 samples using 16 workers
|
||
sample([], 5, 16);
|
||
|
||
function sample(max, samples, numWorkers) {
|
||
if(samples === 0) {
|
||
// get overlap average
|
||
var avg = Math.floor(max.reduce(function(avg, x) {
|
||
return avg + x;
|
||
}, 0) / max.length);
|
||
util.cores = Math.max(1, avg);
|
||
URL.revokeObjectURL(blobUrl);
|
||
return callback(null, util.cores);
|
||
}
|
||
map(numWorkers, function(err, results) {
|
||
max.push(reduce(numWorkers, results));
|
||
sample(max, samples - 1, numWorkers);
|
||
});
|
||
}
|
||
|
||
function map(numWorkers, callback) {
|
||
var workers = [];
|
||
var results = [];
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
var worker = new Worker(blobUrl);
|
||
worker.addEventListener('message', function(e) {
|
||
results.push(e.data);
|
||
if(results.length === numWorkers) {
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
workers[i].terminate();
|
||
}
|
||
callback(null, results);
|
||
}
|
||
});
|
||
workers.push(worker);
|
||
}
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
workers[i].postMessage(i);
|
||
}
|
||
}
|
||
|
||
function reduce(numWorkers, results) {
|
||
// find overlapping time windows
|
||
var overlaps = [];
|
||
for(var n = 0; n < numWorkers; ++n) {
|
||
var r1 = results[n];
|
||
var overlap = overlaps[n] = [];
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
if(n === i) {
|
||
continue;
|
||
}
|
||
var r2 = results[i];
|
||
if((r1.st > r2.st && r1.st < r2.et) ||
|
||
(r2.st > r1.st && r2.st < r1.et)) {
|
||
overlap.push(i);
|
||
}
|
||
}
|
||
}
|
||
// get maximum overlaps ... don't include overlapping worker itself
|
||
// as the main JS process was also being scheduled during the work and
|
||
// would have to be subtracted from the estimate anyway
|
||
return overlaps.reduce(function(max, overlap) {
|
||
return Math.max(max, overlap.length);
|
||
}, 0);
|
||
}
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'util';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/util',['require', 'module'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Cipher base API.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.cipher = forge.cipher || {};
|
||
|
||
// registered algorithms
|
||
forge.cipher.algorithms = forge.cipher.algorithms || {};
|
||
|
||
/**
|
||
* Creates a cipher object that can be used to encrypt data using the given
|
||
* algorithm and key. The algorithm may be provided as a string value for a
|
||
* previously registered algorithm or it may be given as a cipher algorithm
|
||
* API object.
|
||
*
|
||
* @param algorithm the algorithm to use, either a string or an algorithm API
|
||
* object.
|
||
* @param key the key to use, as a binary-encoded string of bytes or a
|
||
* byte buffer.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.cipher.createCipher = function(algorithm, key) {
|
||
var api = algorithm;
|
||
if(typeof api === 'string') {
|
||
api = forge.cipher.getAlgorithm(api);
|
||
if(api) {
|
||
api = api();
|
||
}
|
||
}
|
||
if(!api) {
|
||
throw new Error('Unsupported algorithm: ' + algorithm);
|
||
}
|
||
|
||
// assume block cipher
|
||
return new forge.cipher.BlockCipher({
|
||
algorithm: api,
|
||
key: key,
|
||
decrypt: false
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Creates a decipher object that can be used to decrypt data using the given
|
||
* algorithm and key. The algorithm may be provided as a string value for a
|
||
* previously registered algorithm or it may be given as a cipher algorithm
|
||
* API object.
|
||
*
|
||
* @param algorithm the algorithm to use, either a string or an algorithm API
|
||
* object.
|
||
* @param key the key to use, as a binary-encoded string of bytes or a
|
||
* byte buffer.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.cipher.createDecipher = function(algorithm, key) {
|
||
var api = algorithm;
|
||
if(typeof api === 'string') {
|
||
api = forge.cipher.getAlgorithm(api);
|
||
if(api) {
|
||
api = api();
|
||
}
|
||
}
|
||
if(!api) {
|
||
throw new Error('Unsupported algorithm: ' + algorithm);
|
||
}
|
||
|
||
// assume block cipher
|
||
return new forge.cipher.BlockCipher({
|
||
algorithm: api,
|
||
key: key,
|
||
decrypt: true
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Registers an algorithm by name. If the name was already registered, the
|
||
* algorithm API object will be overwritten.
|
||
*
|
||
* @param name the name of the algorithm.
|
||
* @param algorithm the algorithm API object.
|
||
*/
|
||
forge.cipher.registerAlgorithm = function(name, algorithm) {
|
||
name = name.toUpperCase();
|
||
forge.cipher.algorithms[name] = algorithm;
|
||
};
|
||
|
||
/**
|
||
* Gets a registered algorithm by name.
|
||
*
|
||
* @param name the name of the algorithm.
|
||
*
|
||
* @return the algorithm, if found, null if not.
|
||
*/
|
||
forge.cipher.getAlgorithm = function(name) {
|
||
name = name.toUpperCase();
|
||
if(name in forge.cipher.algorithms) {
|
||
return forge.cipher.algorithms[name];
|
||
}
|
||
return null;
|
||
};
|
||
|
||
var BlockCipher = forge.cipher.BlockCipher = function(options) {
|
||
this.algorithm = options.algorithm;
|
||
this.mode = this.algorithm.mode;
|
||
this.blockSize = this.mode.blockSize;
|
||
this._finish = false;
|
||
this._input = null;
|
||
this.output = null;
|
||
this._op = options.decrypt ? this.mode.decrypt : this.mode.encrypt;
|
||
this._decrypt = options.decrypt;
|
||
this.algorithm.initialize(options);
|
||
};
|
||
|
||
/**
|
||
* Starts or restarts the encryption or decryption process, whichever
|
||
* was previously configured.
|
||
*
|
||
* For non-GCM mode, the IV may be a binary-encoded string of bytes, an array
|
||
* of bytes, a byte buffer, or an array of 32-bit integers. If the IV is in
|
||
* bytes, then it must be Nb (16) bytes in length. If the IV is given in as
|
||
* 32-bit integers, then it must be 4 integers long.
|
||
*
|
||
* Note: an IV is not required or used in ECB mode.
|
||
*
|
||
* For GCM-mode, the IV must be given as a binary-encoded string of bytes or
|
||
* a byte buffer. The number of bytes should be 12 (96 bits) as recommended
|
||
* by NIST SP-800-38D but another length may be given.
|
||
*
|
||
* @param options the options to use:
|
||
* iv the initialization vector to use as a binary-encoded string of
|
||
* bytes, null to reuse the last ciphered block from a previous
|
||
* update() (this "residue" method is for legacy support only).
|
||
* additionalData additional authentication data as a binary-encoded
|
||
* string of bytes, for 'GCM' mode, (default: none).
|
||
* tagLength desired length of authentication tag, in bits, for
|
||
* 'GCM' mode (0-128, default: 128).
|
||
* tag the authentication tag to check if decrypting, as a
|
||
* binary-encoded string of bytes.
|
||
* output the output the buffer to write to, null to create one.
|
||
*/
|
||
BlockCipher.prototype.start = function(options) {
|
||
options = options || {};
|
||
var opts = {};
|
||
for(var key in options) {
|
||
opts[key] = options[key];
|
||
}
|
||
opts.decrypt = this._decrypt;
|
||
this._finish = false;
|
||
this._input = forge.util.createBuffer();
|
||
this.output = options.output || forge.util.createBuffer();
|
||
this.mode.start(opts);
|
||
};
|
||
|
||
/**
|
||
* Updates the next block according to the cipher mode.
|
||
*
|
||
* @param input the buffer to read from.
|
||
*/
|
||
BlockCipher.prototype.update = function(input) {
|
||
if(input) {
|
||
// input given, so empty it into the input buffer
|
||
this._input.putBuffer(input);
|
||
}
|
||
|
||
// do cipher operation until it needs more input and not finished
|
||
while(!this._op.call(this.mode, this._input, this.output, this._finish) &&
|
||
!this._finish) {}
|
||
|
||
// free consumed memory from input buffer
|
||
this._input.compact();
|
||
};
|
||
|
||
/**
|
||
* Finishes encrypting or decrypting.
|
||
*
|
||
* @param pad a padding function to use in CBC mode, null for default,
|
||
* signature(blockSize, buffer, decrypt).
|
||
*
|
||
* @return true if successful, false on error.
|
||
*/
|
||
BlockCipher.prototype.finish = function(pad) {
|
||
// backwards-compatibility w/deprecated padding API
|
||
// Note: will overwrite padding functions even after another start() call
|
||
if(pad && (this.mode.name === 'ECB' || this.mode.name === 'CBC')) {
|
||
this.mode.pad = function(input) {
|
||
return pad(this.blockSize, input, false);
|
||
};
|
||
this.mode.unpad = function(output) {
|
||
return pad(this.blockSize, output, true);
|
||
};
|
||
}
|
||
|
||
// build options for padding and afterFinish functions
|
||
var options = {};
|
||
options.decrypt = this._decrypt;
|
||
|
||
// get # of bytes that won't fill a block
|
||
options.overflow = this._input.length() % this.blockSize;
|
||
|
||
if(!this._decrypt && this.mode.pad) {
|
||
if(!this.mode.pad(this._input, options)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// do final update
|
||
this._finish = true;
|
||
this.update();
|
||
|
||
if(this._decrypt && this.mode.unpad) {
|
||
if(!this.mode.unpad(this.output, options)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if(this.mode.afterFinish) {
|
||
if(!this.mode.afterFinish(this.output, options)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'cipher';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/cipher',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Supported cipher modes.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.cipher = forge.cipher || {};
|
||
|
||
// supported cipher modes
|
||
var modes = forge.cipher.modes = forge.cipher.modes || {};
|
||
|
||
|
||
/** Electronic codebook (ECB) (Don't use this; it's not secure) **/
|
||
|
||
modes.ecb = function(options) {
|
||
options = options || {};
|
||
this.name = 'ECB';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = new Array(this._ints);
|
||
this._outBlock = new Array(this._ints);
|
||
};
|
||
|
||
modes.ecb.prototype.start = function(options) {};
|
||
|
||
modes.ecb.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
if(input.length() < this.blockSize && !(finish && input.length() > 0)) {
|
||
return true;
|
||
}
|
||
|
||
// get next block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = input.getInt32();
|
||
}
|
||
|
||
// encrypt block
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// write output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._outBlock[i]);
|
||
}
|
||
};
|
||
|
||
modes.ecb.prototype.decrypt = function(input, output, finish) {
|
||
// not enough input to decrypt
|
||
if(input.length() < this.blockSize && !(finish && input.length() > 0)) {
|
||
return true;
|
||
}
|
||
|
||
// get next block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = input.getInt32();
|
||
}
|
||
|
||
// decrypt block
|
||
this.cipher.decrypt(this._inBlock, this._outBlock);
|
||
|
||
// write output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._outBlock[i]);
|
||
}
|
||
};
|
||
|
||
modes.ecb.prototype.pad = function(input, options) {
|
||
// add PKCS#7 padding to block (each pad byte is the
|
||
// value of the number of pad bytes)
|
||
var padding = (input.length() === this.blockSize ?
|
||
this.blockSize : (this.blockSize - input.length()));
|
||
input.fillWithByte(padding, padding);
|
||
return true;
|
||
};
|
||
|
||
modes.ecb.prototype.unpad = function(output, options) {
|
||
// check for error: input data not a multiple of blockSize
|
||
if(options.overflow > 0) {
|
||
return false;
|
||
}
|
||
|
||
// ensure padding byte count is valid
|
||
var len = output.length();
|
||
var count = output.at(len - 1);
|
||
if(count > (this.blockSize << 2)) {
|
||
return false;
|
||
}
|
||
|
||
// trim off padding bytes
|
||
output.truncate(count);
|
||
return true;
|
||
};
|
||
|
||
|
||
/** Cipher-block Chaining (CBC) **/
|
||
|
||
modes.cbc = function(options) {
|
||
options = options || {};
|
||
this.name = 'CBC';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = new Array(this._ints);
|
||
this._outBlock = new Array(this._ints);
|
||
};
|
||
|
||
modes.cbc.prototype.start = function(options) {
|
||
// Note: legacy support for using IV residue (has security flaws)
|
||
// if IV is null, reuse block from previous processing
|
||
if(options.iv === null) {
|
||
// must have a previous block
|
||
if(!this._prev) {
|
||
throw new Error('Invalid IV parameter.');
|
||
}
|
||
this._iv = this._prev.slice(0);
|
||
} else if(!('iv' in options)) {
|
||
throw new Error('Invalid IV parameter.');
|
||
} else {
|
||
// save IV as "previous" block
|
||
this._iv = transformIV(options.iv);
|
||
this._prev = this._iv.slice(0);
|
||
}
|
||
};
|
||
|
||
modes.cbc.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
if(input.length() < this.blockSize && !(finish && input.length() > 0)) {
|
||
return true;
|
||
}
|
||
|
||
// get next block
|
||
// CBC XOR's IV (or previous block) with plaintext
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = this._prev[i] ^ input.getInt32();
|
||
}
|
||
|
||
// encrypt block
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// write output, save previous block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._outBlock[i]);
|
||
}
|
||
this._prev = this._outBlock;
|
||
};
|
||
|
||
modes.cbc.prototype.decrypt = function(input, output, finish) {
|
||
// not enough input to decrypt
|
||
if(input.length() < this.blockSize && !(finish && input.length() > 0)) {
|
||
return true;
|
||
}
|
||
|
||
// get next block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = input.getInt32();
|
||
}
|
||
|
||
// decrypt block
|
||
this.cipher.decrypt(this._inBlock, this._outBlock);
|
||
|
||
// write output, save previous ciphered block
|
||
// CBC XOR's IV (or previous block) with ciphertext
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._prev[i] ^ this._outBlock[i]);
|
||
}
|
||
this._prev = this._inBlock.slice(0);
|
||
};
|
||
|
||
modes.cbc.prototype.pad = function(input, options) {
|
||
// add PKCS#7 padding to block (each pad byte is the
|
||
// value of the number of pad bytes)
|
||
var padding = (input.length() === this.blockSize ?
|
||
this.blockSize : (this.blockSize - input.length()));
|
||
input.fillWithByte(padding, padding);
|
||
return true;
|
||
};
|
||
|
||
modes.cbc.prototype.unpad = function(output, options) {
|
||
// check for error: input data not a multiple of blockSize
|
||
if(options.overflow > 0) {
|
||
return false;
|
||
}
|
||
|
||
// ensure padding byte count is valid
|
||
var len = output.length();
|
||
var count = output.at(len - 1);
|
||
if(count > (this.blockSize << 2)) {
|
||
return false;
|
||
}
|
||
|
||
// trim off padding bytes
|
||
output.truncate(count);
|
||
return true;
|
||
};
|
||
|
||
|
||
/** Cipher feedback (CFB) **/
|
||
|
||
modes.cfb = function(options) {
|
||
options = options || {};
|
||
this.name = 'CFB';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = null;
|
||
this._outBlock = new Array(this._ints);
|
||
this._partialBlock = new Array(this._ints);
|
||
this._partialOutput = forge.util.createBuffer();
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.cfb.prototype.start = function(options) {
|
||
if(!('iv' in options)) {
|
||
throw new Error('Invalid IV parameter.');
|
||
}
|
||
// use IV as first input
|
||
this._iv = transformIV(options.iv);
|
||
this._inBlock = this._iv.slice(0);
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.cfb.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
var inputLength = input.length();
|
||
if(inputLength === 0) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// handle full block
|
||
if(this._partialBytes === 0 && inputLength >= this.blockSize) {
|
||
// XOR input with output, write input as output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = input.getInt32() ^ this._outBlock[i];
|
||
output.putInt32(this._inBlock[i]);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// handle partial block
|
||
var partialBytes = (this.blockSize - inputLength) % this.blockSize;
|
||
if(partialBytes > 0) {
|
||
partialBytes = this.blockSize - partialBytes;
|
||
}
|
||
|
||
// XOR input with output, write input as partial output
|
||
this._partialOutput.clear();
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._partialBlock[i] = input.getInt32() ^ this._outBlock[i];
|
||
this._partialOutput.putInt32(this._partialBlock[i]);
|
||
}
|
||
|
||
if(partialBytes > 0) {
|
||
// block still incomplete, restore input buffer
|
||
input.read -= this.blockSize;
|
||
} else {
|
||
// block complete, update input block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = this._partialBlock[i];
|
||
}
|
||
}
|
||
|
||
// skip any previous partial bytes
|
||
if(this._partialBytes > 0) {
|
||
this._partialOutput.getBytes(this._partialBytes);
|
||
}
|
||
|
||
if(partialBytes > 0 && !finish) {
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
partialBytes - this._partialBytes));
|
||
this._partialBytes = partialBytes;
|
||
return true;
|
||
}
|
||
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
inputLength - this._partialBytes));
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.cfb.prototype.decrypt = function(input, output, finish) {
|
||
// not enough input to decrypt
|
||
var inputLength = input.length();
|
||
if(inputLength === 0) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block (CFB always uses encryption mode)
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// handle full block
|
||
if(this._partialBytes === 0 && inputLength >= this.blockSize) {
|
||
// XOR input with output, write input as output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = input.getInt32();
|
||
output.putInt32(this._inBlock[i] ^ this._outBlock[i]);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// handle partial block
|
||
var partialBytes = (this.blockSize - inputLength) % this.blockSize;
|
||
if(partialBytes > 0) {
|
||
partialBytes = this.blockSize - partialBytes;
|
||
}
|
||
|
||
// XOR input with output, write input as partial output
|
||
this._partialOutput.clear();
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._partialBlock[i] = input.getInt32();
|
||
this._partialOutput.putInt32(this._partialBlock[i] ^ this._outBlock[i]);
|
||
}
|
||
|
||
if(partialBytes > 0) {
|
||
// block still incomplete, restore input buffer
|
||
input.read -= this.blockSize;
|
||
} else {
|
||
// block complete, update input block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = this._partialBlock[i];
|
||
}
|
||
}
|
||
|
||
// skip any previous partial bytes
|
||
if(this._partialBytes > 0) {
|
||
this._partialOutput.getBytes(this._partialBytes);
|
||
}
|
||
|
||
if(partialBytes > 0 && !finish) {
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
partialBytes - this._partialBytes));
|
||
this._partialBytes = partialBytes;
|
||
return true;
|
||
}
|
||
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
inputLength - this._partialBytes));
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
/** Output feedback (OFB) **/
|
||
|
||
modes.ofb = function(options) {
|
||
options = options || {};
|
||
this.name = 'OFB';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = null;
|
||
this._outBlock = new Array(this._ints);
|
||
this._partialOutput = forge.util.createBuffer();
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.ofb.prototype.start = function(options) {
|
||
if(!('iv' in options)) {
|
||
throw new Error('Invalid IV parameter.');
|
||
}
|
||
// use IV as first input
|
||
this._iv = transformIV(options.iv);
|
||
this._inBlock = this._iv.slice(0);
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.ofb.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
var inputLength = input.length();
|
||
if(input.length() === 0) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block (OFB always uses encryption mode)
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// handle full block
|
||
if(this._partialBytes === 0 && inputLength >= this.blockSize) {
|
||
// XOR input with output and update next input
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(input.getInt32() ^ this._outBlock[i]);
|
||
this._inBlock[i] = this._outBlock[i];
|
||
}
|
||
return;
|
||
}
|
||
|
||
// handle partial block
|
||
var partialBytes = (this.blockSize - inputLength) % this.blockSize;
|
||
if(partialBytes > 0) {
|
||
partialBytes = this.blockSize - partialBytes;
|
||
}
|
||
|
||
// XOR input with output
|
||
this._partialOutput.clear();
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._partialOutput.putInt32(input.getInt32() ^ this._outBlock[i]);
|
||
}
|
||
|
||
if(partialBytes > 0) {
|
||
// block still incomplete, restore input buffer
|
||
input.read -= this.blockSize;
|
||
} else {
|
||
// block complete, update input block
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._inBlock[i] = this._outBlock[i];
|
||
}
|
||
}
|
||
|
||
// skip any previous partial bytes
|
||
if(this._partialBytes > 0) {
|
||
this._partialOutput.getBytes(this._partialBytes);
|
||
}
|
||
|
||
if(partialBytes > 0 && !finish) {
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
partialBytes - this._partialBytes));
|
||
this._partialBytes = partialBytes;
|
||
return true;
|
||
}
|
||
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
inputLength - this._partialBytes));
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.ofb.prototype.decrypt = modes.ofb.prototype.encrypt;
|
||
|
||
|
||
/** Counter (CTR) **/
|
||
|
||
modes.ctr = function(options) {
|
||
options = options || {};
|
||
this.name = 'CTR';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = null;
|
||
this._outBlock = new Array(this._ints);
|
||
this._partialOutput = forge.util.createBuffer();
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.ctr.prototype.start = function(options) {
|
||
if(!('iv' in options)) {
|
||
throw new Error('Invalid IV parameter.');
|
||
}
|
||
// use IV as first input
|
||
this._iv = transformIV(options.iv);
|
||
this._inBlock = this._iv.slice(0);
|
||
this._partialBytes = 0;
|
||
};
|
||
|
||
modes.ctr.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
var inputLength = input.length();
|
||
if(inputLength === 0) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block (CTR always uses encryption mode)
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// handle full block
|
||
if(this._partialBytes === 0 && inputLength >= this.blockSize) {
|
||
// XOR input with output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(input.getInt32() ^ this._outBlock[i]);
|
||
}
|
||
} else {
|
||
// handle partial block
|
||
var partialBytes = (this.blockSize - inputLength) % this.blockSize;
|
||
if(partialBytes > 0) {
|
||
partialBytes = this.blockSize - partialBytes;
|
||
}
|
||
|
||
// XOR input with output
|
||
this._partialOutput.clear();
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._partialOutput.putInt32(input.getInt32() ^ this._outBlock[i]);
|
||
}
|
||
|
||
if(partialBytes > 0) {
|
||
// block still incomplete, restore input buffer
|
||
input.read -= this.blockSize;
|
||
}
|
||
|
||
// skip any previous partial bytes
|
||
if(this._partialBytes > 0) {
|
||
this._partialOutput.getBytes(this._partialBytes);
|
||
}
|
||
|
||
if(partialBytes > 0 && !finish) {
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
partialBytes - this._partialBytes));
|
||
this._partialBytes = partialBytes;
|
||
return true;
|
||
}
|
||
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
inputLength - this._partialBytes));
|
||
this._partialBytes = 0;
|
||
}
|
||
|
||
// block complete, increment counter (input block)
|
||
inc32(this._inBlock);
|
||
};
|
||
|
||
modes.ctr.prototype.decrypt = modes.ctr.prototype.encrypt;
|
||
|
||
|
||
/** Galois/Counter Mode (GCM) **/
|
||
|
||
modes.gcm = function(options) {
|
||
options = options || {};
|
||
this.name = 'GCM';
|
||
this.cipher = options.cipher;
|
||
this.blockSize = options.blockSize || 16;
|
||
this._ints = this.blockSize / 4;
|
||
this._inBlock = new Array(this._ints);
|
||
this._outBlock = new Array(this._ints);
|
||
this._partialOutput = forge.util.createBuffer();
|
||
this._partialBytes = 0;
|
||
|
||
// R is actually this value concatenated with 120 more zero bits, but
|
||
// we only XOR against R so the other zeros have no effect -- we just
|
||
// apply this value to the first integer in a block
|
||
this._R = 0xE1000000;
|
||
};
|
||
|
||
modes.gcm.prototype.start = function(options) {
|
||
if(!('iv' in options)) {
|
||
throw new Error('Invalid IV parameter.');
|
||
}
|
||
// ensure IV is a byte buffer
|
||
var iv = forge.util.createBuffer(options.iv);
|
||
|
||
// no ciphered data processed yet
|
||
this._cipherLength = 0;
|
||
|
||
// default additional data is none
|
||
var additionalData;
|
||
if('additionalData' in options) {
|
||
additionalData = forge.util.createBuffer(options.additionalData);
|
||
} else {
|
||
additionalData = forge.util.createBuffer();
|
||
}
|
||
|
||
// default tag length is 128 bits
|
||
if('tagLength' in options) {
|
||
this._tagLength = options.tagLength;
|
||
} else {
|
||
this._tagLength = 128;
|
||
}
|
||
|
||
// if tag is given, ensure tag matches tag length
|
||
this._tag = null;
|
||
if(options.decrypt) {
|
||
// save tag to check later
|
||
this._tag = forge.util.createBuffer(options.tag).getBytes();
|
||
if(this._tag.length !== (this._tagLength / 8)) {
|
||
throw new Error('Authentication tag does not match tag length.');
|
||
}
|
||
}
|
||
|
||
// create tmp storage for hash calculation
|
||
this._hashBlock = new Array(this._ints);
|
||
|
||
// no tag generated yet
|
||
this.tag = null;
|
||
|
||
// generate hash subkey
|
||
// (apply block cipher to "zero" block)
|
||
this._hashSubkey = new Array(this._ints);
|
||
this.cipher.encrypt([0, 0, 0, 0], this._hashSubkey);
|
||
|
||
// generate table M
|
||
// use 4-bit tables (32 component decomposition of a 16 byte value)
|
||
// 8-bit tables take more space and are known to have security
|
||
// vulnerabilities (in native implementations)
|
||
this.componentBits = 4;
|
||
this._m = this.generateHashTable(this._hashSubkey, this.componentBits);
|
||
|
||
// Note: support IV length different from 96 bits? (only supporting
|
||
// 96 bits is recommended by NIST SP-800-38D)
|
||
// generate J_0
|
||
var ivLength = iv.length();
|
||
if(ivLength === 12) {
|
||
// 96-bit IV
|
||
this._j0 = [iv.getInt32(), iv.getInt32(), iv.getInt32(), 1];
|
||
} else {
|
||
// IV is NOT 96-bits
|
||
this._j0 = [0, 0, 0, 0];
|
||
while(iv.length() > 0) {
|
||
this._j0 = this.ghash(
|
||
this._hashSubkey, this._j0,
|
||
[iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()]);
|
||
}
|
||
this._j0 = this.ghash(
|
||
this._hashSubkey, this._j0, [0, 0].concat(from64To32(ivLength * 8)));
|
||
}
|
||
|
||
// generate ICB (initial counter block)
|
||
this._inBlock = this._j0.slice(0);
|
||
inc32(this._inBlock);
|
||
this._partialBytes = 0;
|
||
|
||
// consume authentication data
|
||
additionalData = forge.util.createBuffer(additionalData);
|
||
// save additional data length as a BE 64-bit number
|
||
this._aDataLength = from64To32(additionalData.length() * 8);
|
||
// pad additional data to 128 bit (16 byte) block size
|
||
var overflow = additionalData.length() % this.blockSize;
|
||
if(overflow) {
|
||
additionalData.fillWithByte(0, this.blockSize - overflow);
|
||
}
|
||
this._s = [0, 0, 0, 0];
|
||
while(additionalData.length() > 0) {
|
||
this._s = this.ghash(this._hashSubkey, this._s, [
|
||
additionalData.getInt32(),
|
||
additionalData.getInt32(),
|
||
additionalData.getInt32(),
|
||
additionalData.getInt32()
|
||
]);
|
||
}
|
||
};
|
||
|
||
modes.gcm.prototype.encrypt = function(input, output, finish) {
|
||
// not enough input to encrypt
|
||
var inputLength = input.length();
|
||
if(inputLength === 0) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// handle full block
|
||
if(this._partialBytes === 0 && inputLength >= this.blockSize) {
|
||
// XOR input with output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._outBlock[i] ^= input.getInt32());
|
||
}
|
||
this._cipherLength += this.blockSize;
|
||
} else {
|
||
// handle partial block
|
||
var partialBytes = (this.blockSize - inputLength) % this.blockSize;
|
||
if(partialBytes > 0) {
|
||
partialBytes = this.blockSize - partialBytes;
|
||
}
|
||
|
||
// XOR input with output
|
||
this._partialOutput.clear();
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._partialOutput.putInt32(input.getInt32() ^ this._outBlock[i]);
|
||
}
|
||
|
||
if(partialBytes === 0 || finish) {
|
||
// handle overflow prior to hashing
|
||
if(finish) {
|
||
// get block overflow
|
||
var overflow = inputLength % this.blockSize;
|
||
this._cipherLength += overflow;
|
||
// truncate for hash function
|
||
this._partialOutput.truncate(this.blockSize - overflow);
|
||
} else {
|
||
this._cipherLength += this.blockSize;
|
||
}
|
||
|
||
// get output block for hashing
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this._outBlock[i] = this._partialOutput.getInt32();
|
||
}
|
||
this._partialOutput.read -= this.blockSize;
|
||
}
|
||
|
||
// skip any previous partial bytes
|
||
if(this._partialBytes > 0) {
|
||
this._partialOutput.getBytes(this._partialBytes);
|
||
}
|
||
|
||
if(partialBytes > 0 && !finish) {
|
||
// block still incomplete, restore input buffer, get partial output,
|
||
// and return early
|
||
input.read -= this.blockSize;
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
partialBytes - this._partialBytes));
|
||
this._partialBytes = partialBytes;
|
||
return true;
|
||
}
|
||
|
||
output.putBytes(this._partialOutput.getBytes(
|
||
inputLength - this._partialBytes));
|
||
this._partialBytes = 0;
|
||
}
|
||
|
||
// update hash block S
|
||
this._s = this.ghash(this._hashSubkey, this._s, this._outBlock);
|
||
|
||
// increment counter (input block)
|
||
inc32(this._inBlock);
|
||
};
|
||
|
||
modes.gcm.prototype.decrypt = function(input, output, finish) {
|
||
// not enough input to decrypt
|
||
var inputLength = input.length();
|
||
if(inputLength < this.blockSize && !(finish && inputLength > 0)) {
|
||
return true;
|
||
}
|
||
|
||
// encrypt block (GCM always uses encryption mode)
|
||
this.cipher.encrypt(this._inBlock, this._outBlock);
|
||
|
||
// increment counter (input block)
|
||
inc32(this._inBlock);
|
||
|
||
// update hash block S
|
||
this._hashBlock[0] = input.getInt32();
|
||
this._hashBlock[1] = input.getInt32();
|
||
this._hashBlock[2] = input.getInt32();
|
||
this._hashBlock[3] = input.getInt32();
|
||
this._s = this.ghash(this._hashSubkey, this._s, this._hashBlock);
|
||
|
||
// XOR hash input with output
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
output.putInt32(this._outBlock[i] ^ this._hashBlock[i]);
|
||
}
|
||
|
||
// increment cipher data length
|
||
if(inputLength < this.blockSize) {
|
||
this._cipherLength += inputLength % this.blockSize;
|
||
} else {
|
||
this._cipherLength += this.blockSize;
|
||
}
|
||
};
|
||
|
||
modes.gcm.prototype.afterFinish = function(output, options) {
|
||
var rval = true;
|
||
|
||
// handle overflow
|
||
if(options.decrypt && options.overflow) {
|
||
output.truncate(this.blockSize - options.overflow);
|
||
}
|
||
|
||
// handle authentication tag
|
||
this.tag = forge.util.createBuffer();
|
||
|
||
// concatenate additional data length with cipher length
|
||
var lengths = this._aDataLength.concat(from64To32(this._cipherLength * 8));
|
||
|
||
// include lengths in hash
|
||
this._s = this.ghash(this._hashSubkey, this._s, lengths);
|
||
|
||
// do GCTR(J_0, S)
|
||
var tag = [];
|
||
this.cipher.encrypt(this._j0, tag);
|
||
for(var i = 0; i < this._ints; ++i) {
|
||
this.tag.putInt32(this._s[i] ^ tag[i]);
|
||
}
|
||
|
||
// trim tag to length
|
||
this.tag.truncate(this.tag.length() % (this._tagLength / 8));
|
||
|
||
// check authentication tag
|
||
if(options.decrypt && this.tag.bytes() !== this._tag) {
|
||
rval = false;
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* See NIST SP-800-38D 6.3 (Algorithm 1). This function performs Galois
|
||
* field multiplication. The field, GF(2^128), is defined by the polynomial:
|
||
*
|
||
* x^128 + x^7 + x^2 + x + 1
|
||
*
|
||
* Which is represented in little-endian binary form as: 11100001 (0xe1). When
|
||
* the value of a coefficient is 1, a bit is set. The value R, is the
|
||
* concatenation of this value and 120 zero bits, yielding a 128-bit value
|
||
* which matches the block size.
|
||
*
|
||
* This function will multiply two elements (vectors of bytes), X and Y, in
|
||
* the field GF(2^128). The result is initialized to zero. For each bit of
|
||
* X (out of 128), x_i, if x_i is set, then the result is multiplied (XOR'd)
|
||
* by the current value of Y. For each bit, the value of Y will be raised by
|
||
* a power of x (multiplied by the polynomial x). This can be achieved by
|
||
* shifting Y once to the right. If the current value of Y, prior to being
|
||
* multiplied by x, has 0 as its LSB, then it is a 127th degree polynomial.
|
||
* Otherwise, we must divide by R after shifting to find the remainder.
|
||
*
|
||
* @param x the first block to multiply by the second.
|
||
* @param y the second block to multiply by the first.
|
||
*
|
||
* @return the block result of the multiplication.
|
||
*/
|
||
modes.gcm.prototype.multiply = function(x, y) {
|
||
var z_i = [0, 0, 0, 0];
|
||
var v_i = y.slice(0);
|
||
|
||
// calculate Z_128 (block has 128 bits)
|
||
for(var i = 0; i < 128; ++i) {
|
||
// if x_i is 0, Z_{i+1} = Z_i (unchanged)
|
||
// else Z_{i+1} = Z_i ^ V_i
|
||
// get x_i by finding 32-bit int position, then left shift 1 by remainder
|
||
var x_i = x[(i / 32) | 0] & (1 << (31 - i % 32));
|
||
if(x_i) {
|
||
z_i[0] ^= v_i[0];
|
||
z_i[1] ^= v_i[1];
|
||
z_i[2] ^= v_i[2];
|
||
z_i[3] ^= v_i[3];
|
||
}
|
||
|
||
// if LSB(V_i) is 1, V_i = V_i >> 1
|
||
// else V_i = (V_i >> 1) ^ R
|
||
this.pow(v_i, v_i);
|
||
}
|
||
|
||
return z_i;
|
||
};
|
||
|
||
modes.gcm.prototype.pow = function(x, out) {
|
||
// if LSB(x) is 1, x = x >>> 1
|
||
// else x = (x >>> 1) ^ R
|
||
var lsb = x[3] & 1;
|
||
|
||
// always do x >>> 1:
|
||
// starting with the rightmost integer, shift each integer to the right
|
||
// one bit, pulling in the bit from the integer to the left as its top
|
||
// most bit (do this for the last 3 integers)
|
||
for(var i = 3; i > 0; --i) {
|
||
out[i] = (x[i] >>> 1) | ((x[i - 1] & 1) << 31);
|
||
}
|
||
// shift the first integer normally
|
||
out[0] = x[0] >>> 1;
|
||
|
||
// if lsb was not set, then polynomial had a degree of 127 and doesn't
|
||
// need to divided; otherwise, XOR with R to find the remainder; we only
|
||
// need to XOR the first integer since R technically ends w/120 zero bits
|
||
if(lsb) {
|
||
out[0] ^= this._R;
|
||
}
|
||
};
|
||
|
||
modes.gcm.prototype.tableMultiply = function(x) {
|
||
// assumes 4-bit tables are used
|
||
var z = [0, 0, 0, 0];
|
||
for(var i = 0; i < 32; ++i) {
|
||
var idx = (i / 8) | 0;
|
||
var x_i = (x[idx] >>> ((7 - (i % 8)) * 4)) & 0xF;
|
||
var ah = this._m[i][x_i];
|
||
z[0] ^= ah[0];
|
||
z[1] ^= ah[1];
|
||
z[2] ^= ah[2];
|
||
z[3] ^= ah[3];
|
||
}
|
||
return z;
|
||
};
|
||
|
||
/**
|
||
* A continuing version of the GHASH algorithm that operates on a single
|
||
* block. The hash block, last hash value (Ym) and the new block to hash
|
||
* are given.
|
||
*
|
||
* @param h the hash block.
|
||
* @param y the previous value for Ym, use [0, 0, 0, 0] for a new hash.
|
||
* @param x the block to hash.
|
||
*
|
||
* @return the hashed value (Ym).
|
||
*/
|
||
modes.gcm.prototype.ghash = function(h, y, x) {
|
||
y[0] ^= x[0];
|
||
y[1] ^= x[1];
|
||
y[2] ^= x[2];
|
||
y[3] ^= x[3];
|
||
return this.tableMultiply(y);
|
||
//return this.multiply(y, h);
|
||
};
|
||
|
||
/**
|
||
* Precomputes a table for multiplying against the hash subkey. This
|
||
* mechanism provides a substantial speed increase over multiplication
|
||
* performed without a table. The table-based multiplication this table is
|
||
* for solves X * H by multiplying each component of X by H and then
|
||
* composing the results together using XOR.
|
||
*
|
||
* This function can be used to generate tables with different bit sizes
|
||
* for the components, however, this implementation assumes there are
|
||
* 32 components of X (which is a 16 byte vector), therefore each component
|
||
* takes 4-bits (so the table is constructed with bits=4).
|
||
*
|
||
* @param h the hash subkey.
|
||
* @param bits the bit size for a component.
|
||
*/
|
||
modes.gcm.prototype.generateHashTable = function(h, bits) {
|
||
// TODO: There are further optimizations that would use only the
|
||
// first table M_0 (or some variant) along with a remainder table;
|
||
// this can be explored in the future
|
||
var multiplier = 8 / bits;
|
||
var perInt = 4 * multiplier;
|
||
var size = 16 * multiplier;
|
||
var m = new Array(size);
|
||
for(var i = 0; i < size; ++i) {
|
||
var tmp = [0, 0, 0, 0];
|
||
var idx = (i / perInt) | 0;
|
||
var shft = ((perInt - 1 - (i % perInt)) * bits);
|
||
tmp[idx] = (1 << (bits - 1)) << shft;
|
||
m[i] = this.generateSubHashTable(this.multiply(tmp, h), bits);
|
||
}
|
||
return m;
|
||
};
|
||
|
||
/**
|
||
* Generates a table for multiplying against the hash subkey for one
|
||
* particular component (out of all possible component values).
|
||
*
|
||
* @param mid the pre-multiplied value for the middle key of the table.
|
||
* @param bits the bit size for a component.
|
||
*/
|
||
modes.gcm.prototype.generateSubHashTable = function(mid, bits) {
|
||
// compute the table quickly by minimizing the number of
|
||
// POW operations -- they only need to be performed for powers of 2,
|
||
// all other entries can be composed from those powers using XOR
|
||
var size = 1 << bits;
|
||
var half = size >>> 1;
|
||
var m = new Array(size);
|
||
m[half] = mid.slice(0);
|
||
var i = half >>> 1;
|
||
while(i > 0) {
|
||
// raise m0[2 * i] and store in m0[i]
|
||
this.pow(m[2 * i], m[i] = []);
|
||
i >>= 1;
|
||
}
|
||
i = 2;
|
||
while(i < half) {
|
||
for(var j = 1; j < i; ++j) {
|
||
var m_i = m[i];
|
||
var m_j = m[j];
|
||
m[i + j] = [
|
||
m_i[0] ^ m_j[0],
|
||
m_i[1] ^ m_j[1],
|
||
m_i[2] ^ m_j[2],
|
||
m_i[3] ^ m_j[3]
|
||
];
|
||
}
|
||
i *= 2;
|
||
}
|
||
m[0] = [0, 0, 0, 0];
|
||
/* Note: We could avoid storing these by doing composition during multiply
|
||
calculate top half using composition by speed is preferred. */
|
||
for(i = half + 1; i < size; ++i) {
|
||
var c = m[i ^ half];
|
||
m[i] = [mid[0] ^ c[0], mid[1] ^ c[1], mid[2] ^ c[2], mid[3] ^ c[3]];
|
||
}
|
||
return m;
|
||
};
|
||
|
||
|
||
/** Utility functions */
|
||
|
||
function transformIV(iv) {
|
||
if(typeof iv === 'string') {
|
||
// convert iv string into byte buffer
|
||
iv = forge.util.createBuffer(iv);
|
||
}
|
||
|
||
if(forge.util.isArray(iv) && iv.length > 4) {
|
||
// convert iv byte array into byte buffer
|
||
var tmp = iv;
|
||
iv = forge.util.createBuffer();
|
||
for(var i = 0; i < tmp.length; ++i) {
|
||
iv.putByte(tmp[i]);
|
||
}
|
||
}
|
||
if(!forge.util.isArray(iv)) {
|
||
// convert iv byte buffer into 32-bit integer array
|
||
iv = [iv.getInt32(), iv.getInt32(), iv.getInt32(), iv.getInt32()];
|
||
}
|
||
|
||
return iv;
|
||
}
|
||
|
||
function inc32(block) {
|
||
// increment last 32 bits of block only
|
||
block[block.length - 1] = (block[block.length - 1] + 1) & 0xFFFFFFFF;
|
||
}
|
||
|
||
function from64To32(num) {
|
||
// convert 64-bit number to two BE Int32s
|
||
return [(num / 0x100000000) | 0, num & 0xFFFFFFFF];
|
||
}
|
||
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'cipherModes';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/cipherModes',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Advanced Encryption Standard (AES) implementation.
|
||
*
|
||
* This implementation is based on the public domain library 'jscrypto' which
|
||
* was written by:
|
||
*
|
||
* Emily Stark (estark@stanford.edu)
|
||
* Mike Hamburg (mhamburg@stanford.edu)
|
||
* Dan Boneh (dabo@cs.stanford.edu)
|
||
*
|
||
* Parts of this code are based on the OpenSSL implementation of AES:
|
||
* http://www.openssl.org
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* AES API */
|
||
forge.aes = forge.aes || {};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var cipher = forge.cipher.createCipher('AES-<mode>', key);
|
||
* cipher.start({iv: iv});
|
||
*
|
||
* Creates an AES cipher object to encrypt data using the given symmetric key.
|
||
* The output will be stored in the 'output' member of the returned cipher.
|
||
*
|
||
* The key and iv may be given as a string of bytes, an array of bytes,
|
||
* a byte buffer, or an array of 32-bit words.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.aes.startEncrypting = function(key, iv, output, mode) {
|
||
var cipher = _createCipher({
|
||
key: key,
|
||
output: output,
|
||
decrypt: false,
|
||
mode: mode
|
||
});
|
||
cipher.start(iv);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var cipher = forge.cipher.createCipher('AES-<mode>', key);
|
||
*
|
||
* Creates an AES cipher object to encrypt data using the given symmetric key.
|
||
*
|
||
* The key may be given as a string of bytes, an array of bytes, a
|
||
* byte buffer, or an array of 32-bit words.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.aes.createEncryptionCipher = function(key, mode) {
|
||
return _createCipher({
|
||
key: key,
|
||
output: null,
|
||
decrypt: false,
|
||
mode: mode
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var decipher = forge.cipher.createDecipher('AES-<mode>', key);
|
||
* decipher.start({iv: iv});
|
||
*
|
||
* Creates an AES cipher object to decrypt data using the given symmetric key.
|
||
* The output will be stored in the 'output' member of the returned cipher.
|
||
*
|
||
* The key and iv may be given as a string of bytes, an array of bytes,
|
||
* a byte buffer, or an array of 32-bit words.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.aes.startDecrypting = function(key, iv, output, mode) {
|
||
var cipher = _createCipher({
|
||
key: key,
|
||
output: output,
|
||
decrypt: true,
|
||
mode: mode
|
||
});
|
||
cipher.start(iv);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var decipher = forge.cipher.createDecipher('AES-<mode>', key);
|
||
*
|
||
* Creates an AES cipher object to decrypt data using the given symmetric key.
|
||
*
|
||
* The key may be given as a string of bytes, an array of bytes, a
|
||
* byte buffer, or an array of 32-bit words.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.aes.createDecryptionCipher = function(key, mode) {
|
||
return _createCipher({
|
||
key: key,
|
||
output: null,
|
||
decrypt: true,
|
||
mode: mode
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Creates a new AES cipher algorithm object.
|
||
*
|
||
* @param name the name of the algorithm.
|
||
* @param mode the mode factory function.
|
||
*
|
||
* @return the AES algorithm object.
|
||
*/
|
||
forge.aes.Algorithm = function(name, mode) {
|
||
if(!init) {
|
||
initialize();
|
||
}
|
||
var self = this;
|
||
self.name = name;
|
||
self.mode = new mode({
|
||
blockSize: 16,
|
||
cipher: {
|
||
encrypt: function(inBlock, outBlock) {
|
||
return _updateBlock(self._w, inBlock, outBlock, false);
|
||
},
|
||
decrypt: function(inBlock, outBlock) {
|
||
return _updateBlock(self._w, inBlock, outBlock, true);
|
||
}
|
||
}
|
||
});
|
||
self._init = false;
|
||
};
|
||
|
||
/**
|
||
* Initializes this AES algorithm by expanding its key.
|
||
*
|
||
* @param options the options to use.
|
||
* key the key to use with this algorithm.
|
||
* decrypt true if the algorithm should be initialized for decryption,
|
||
* false for encryption.
|
||
*/
|
||
forge.aes.Algorithm.prototype.initialize = function(options) {
|
||
if(this._init) {
|
||
return;
|
||
}
|
||
|
||
var key = options.key;
|
||
var tmp;
|
||
|
||
/* Note: The key may be a string of bytes, an array of bytes, a byte
|
||
buffer, or an array of 32-bit integers. If the key is in bytes, then
|
||
it must be 16, 24, or 32 bytes in length. If it is in 32-bit
|
||
integers, it must be 4, 6, or 8 integers long. */
|
||
|
||
if(typeof key === 'string' &&
|
||
(key.length === 16 || key.length === 24 || key.length === 32)) {
|
||
// convert key string into byte buffer
|
||
key = forge.util.createBuffer(key);
|
||
} else if(forge.util.isArray(key) &&
|
||
(key.length === 16 || key.length === 24 || key.length === 32)) {
|
||
// convert key integer array into byte buffer
|
||
tmp = key;
|
||
key = forge.util.createBuffer();
|
||
for(var i = 0; i < tmp.length; ++i) {
|
||
key.putByte(tmp[i]);
|
||
}
|
||
}
|
||
|
||
// convert key byte buffer into 32-bit integer array
|
||
if(!forge.util.isArray(key)) {
|
||
tmp = key;
|
||
key = [];
|
||
|
||
// key lengths of 16, 24, 32 bytes allowed
|
||
var len = tmp.length();
|
||
if(len === 16 || len === 24 || len === 32) {
|
||
len = len >>> 2;
|
||
for(var i = 0; i < len; ++i) {
|
||
key.push(tmp.getInt32());
|
||
}
|
||
}
|
||
}
|
||
|
||
// key must be an array of 32-bit integers by now
|
||
if(!forge.util.isArray(key) ||
|
||
!(key.length === 4 || key.length === 6 || key.length === 8)) {
|
||
throw new Error('Invalid key parameter.');
|
||
}
|
||
|
||
// encryption operation is always used for these modes
|
||
var mode = this.mode.name;
|
||
var encryptOp = (['CFB', 'OFB', 'CTR', 'GCM'].indexOf(mode) !== -1);
|
||
|
||
// do key expansion
|
||
this._w = _expandKey(key, options.decrypt && !encryptOp);
|
||
this._init = true;
|
||
};
|
||
|
||
/**
|
||
* Expands a key. Typically only used for testing.
|
||
*
|
||
* @param key the symmetric key to expand, as an array of 32-bit words.
|
||
* @param decrypt true to expand for decryption, false for encryption.
|
||
*
|
||
* @return the expanded key.
|
||
*/
|
||
forge.aes._expandKey = function(key, decrypt) {
|
||
if(!init) {
|
||
initialize();
|
||
}
|
||
return _expandKey(key, decrypt);
|
||
};
|
||
|
||
/**
|
||
* Updates a single block. Typically only used for testing.
|
||
*
|
||
* @param w the expanded key to use.
|
||
* @param input an array of block-size 32-bit words.
|
||
* @param output an array of block-size 32-bit words.
|
||
* @param decrypt true to decrypt, false to encrypt.
|
||
*/
|
||
forge.aes._updateBlock = _updateBlock;
|
||
|
||
|
||
/** Register AES algorithms **/
|
||
|
||
registerAlgorithm('AES-ECB', forge.cipher.modes.ecb);
|
||
registerAlgorithm('AES-CBC', forge.cipher.modes.cbc);
|
||
registerAlgorithm('AES-CFB', forge.cipher.modes.cfb);
|
||
registerAlgorithm('AES-OFB', forge.cipher.modes.ofb);
|
||
registerAlgorithm('AES-CTR', forge.cipher.modes.ctr);
|
||
registerAlgorithm('AES-GCM', forge.cipher.modes.gcm);
|
||
|
||
function registerAlgorithm(name, mode) {
|
||
var factory = function() {
|
||
return new forge.aes.Algorithm(name, mode);
|
||
};
|
||
forge.cipher.registerAlgorithm(name, factory);
|
||
}
|
||
|
||
|
||
/** AES implementation **/
|
||
|
||
var init = false; // not yet initialized
|
||
var Nb = 4; // number of words comprising the state (AES = 4)
|
||
var sbox; // non-linear substitution table used in key expansion
|
||
var isbox; // inversion of sbox
|
||
var rcon; // round constant word array
|
||
var mix; // mix-columns table
|
||
var imix; // inverse mix-columns table
|
||
|
||
/**
|
||
* Performs initialization, ie: precomputes tables to optimize for speed.
|
||
*
|
||
* One way to understand how AES works is to imagine that 'addition' and
|
||
* 'multiplication' are interfaces that require certain mathematical
|
||
* properties to hold true (ie: they are associative) but they might have
|
||
* different implementations and produce different kinds of results ...
|
||
* provided that their mathematical properties remain true. AES defines
|
||
* its own methods of addition and multiplication but keeps some important
|
||
* properties the same, ie: associativity and distributivity. The
|
||
* explanation below tries to shed some light on how AES defines addition
|
||
* and multiplication of bytes and 32-bit words in order to perform its
|
||
* encryption and decryption algorithms.
|
||
*
|
||
* The basics:
|
||
*
|
||
* The AES algorithm views bytes as binary representations of polynomials
|
||
* that have either 1 or 0 as the coefficients. It defines the addition
|
||
* or subtraction of two bytes as the XOR operation. It also defines the
|
||
* multiplication of two bytes as a finite field referred to as GF(2^8)
|
||
* (Note: 'GF' means "Galois Field" which is a field that contains a finite
|
||
* number of elements so GF(2^8) has 256 elements).
|
||
*
|
||
* This means that any two bytes can be represented as binary polynomials;
|
||
* when they multiplied together and modularly reduced by an irreducible
|
||
* polynomial of the 8th degree, the results are the field GF(2^8). The
|
||
* specific irreducible polynomial that AES uses in hexadecimal is 0x11b.
|
||
* This multiplication is associative with 0x01 as the identity:
|
||
*
|
||
* (b * 0x01 = GF(b, 0x01) = b).
|
||
*
|
||
* The operation GF(b, 0x02) can be performed at the byte level by left
|
||
* shifting b once and then XOR'ing it (to perform the modular reduction)
|
||
* with 0x11b if b is >= 128. Repeated application of the multiplication
|
||
* of 0x02 can be used to implement the multiplication of any two bytes.
|
||
*
|
||
* For instance, multiplying 0x57 and 0x13, denoted as GF(0x57, 0x13), can
|
||
* be performed by factoring 0x13 into 0x01, 0x02, and 0x10. Then these
|
||
* factors can each be multiplied by 0x57 and then added together. To do
|
||
* the multiplication, values for 0x57 multiplied by each of these 3 factors
|
||
* can be precomputed and stored in a table. To add them, the values from
|
||
* the table are XOR'd together.
|
||
*
|
||
* AES also defines addition and multiplication of words, that is 4-byte
|
||
* numbers represented as polynomials of 3 degrees where the coefficients
|
||
* are the values of the bytes.
|
||
*
|
||
* The word [a0, a1, a2, a3] is a polynomial a3x^3 + a2x^2 + a1x + a0.
|
||
*
|
||
* Addition is performed by XOR'ing like powers of x. Multiplication
|
||
* is performed in two steps, the first is an algebriac expansion as
|
||
* you would do normally (where addition is XOR). But the result is
|
||
* a polynomial larger than 3 degrees and thus it cannot fit in a word. So
|
||
* next the result is modularly reduced by an AES-specific polynomial of
|
||
* degree 4 which will always produce a polynomial of less than 4 degrees
|
||
* such that it will fit in a word. In AES, this polynomial is x^4 + 1.
|
||
*
|
||
* The modular product of two polynomials 'a' and 'b' is thus:
|
||
*
|
||
* d(x) = d3x^3 + d2x^2 + d1x + d0
|
||
* with
|
||
* d0 = GF(a0, b0) ^ GF(a3, b1) ^ GF(a2, b2) ^ GF(a1, b3)
|
||
* d1 = GF(a1, b0) ^ GF(a0, b1) ^ GF(a3, b2) ^ GF(a2, b3)
|
||
* d2 = GF(a2, b0) ^ GF(a1, b1) ^ GF(a0, b2) ^ GF(a3, b3)
|
||
* d3 = GF(a3, b0) ^ GF(a2, b1) ^ GF(a1, b2) ^ GF(a0, b3)
|
||
*
|
||
* As a matrix:
|
||
*
|
||
* [d0] = [a0 a3 a2 a1][b0]
|
||
* [d1] [a1 a0 a3 a2][b1]
|
||
* [d2] [a2 a1 a0 a3][b2]
|
||
* [d3] [a3 a2 a1 a0][b3]
|
||
*
|
||
* Special polynomials defined by AES (0x02 == {02}):
|
||
* a(x) = {03}x^3 + {01}x^2 + {01}x + {02}
|
||
* a^-1(x) = {0b}x^3 + {0d}x^2 + {09}x + {0e}.
|
||
*
|
||
* These polynomials are used in the MixColumns() and InverseMixColumns()
|
||
* operations, respectively, to cause each element in the state to affect
|
||
* the output (referred to as diffusing).
|
||
*
|
||
* RotWord() uses: a0 = a1 = a2 = {00} and a3 = {01}, which is the
|
||
* polynomial x3.
|
||
*
|
||
* The ShiftRows() method modifies the last 3 rows in the state (where
|
||
* the state is 4 words with 4 bytes per word) by shifting bytes cyclically.
|
||
* The 1st byte in the second row is moved to the end of the row. The 1st
|
||
* and 2nd bytes in the third row are moved to the end of the row. The 1st,
|
||
* 2nd, and 3rd bytes are moved in the fourth row.
|
||
*
|
||
* More details on how AES arithmetic works:
|
||
*
|
||
* In the polynomial representation of binary numbers, XOR performs addition
|
||
* and subtraction and multiplication in GF(2^8) denoted as GF(a, b)
|
||
* corresponds with the multiplication of polynomials modulo an irreducible
|
||
* polynomial of degree 8. In other words, for AES, GF(a, b) will multiply
|
||
* polynomial 'a' with polynomial 'b' and then do a modular reduction by
|
||
* an AES-specific irreducible polynomial of degree 8.
|
||
*
|
||
* A polynomial is irreducible if its only divisors are one and itself. For
|
||
* the AES algorithm, this irreducible polynomial is:
|
||
*
|
||
* m(x) = x^8 + x^4 + x^3 + x + 1,
|
||
*
|
||
* or {01}{1b} in hexadecimal notation, where each coefficient is a bit:
|
||
* 100011011 = 283 = 0x11b.
|
||
*
|
||
* For example, GF(0x57, 0x83) = 0xc1 because
|
||
*
|
||
* 0x57 = 87 = 01010111 = x^6 + x^4 + x^2 + x + 1
|
||
* 0x85 = 131 = 10000101 = x^7 + x + 1
|
||
*
|
||
* (x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1)
|
||
* = x^13 + x^11 + x^9 + x^8 + x^7 +
|
||
* x^7 + x^5 + x^3 + x^2 + x +
|
||
* x^6 + x^4 + x^2 + x + 1
|
||
* = x^13 + x^11 + x^9 + x^8 + x^6 + x^5 + x^4 + x^3 + 1 = y
|
||
* y modulo (x^8 + x^4 + x^3 + x + 1)
|
||
* = x^7 + x^6 + 1.
|
||
*
|
||
* The modular reduction by m(x) guarantees the result will be a binary
|
||
* polynomial of less than degree 8, so that it can fit in a byte.
|
||
*
|
||
* The operation to multiply a binary polynomial b with x (the polynomial
|
||
* x in binary representation is 00000010) is:
|
||
*
|
||
* b_7x^8 + b_6x^7 + b_5x^6 + b_4x^5 + b_3x^4 + b_2x^3 + b_1x^2 + b_0x^1
|
||
*
|
||
* To get GF(b, x) we must reduce that by m(x). If b_7 is 0 (that is the
|
||
* most significant bit is 0 in b) then the result is already reduced. If
|
||
* it is 1, then we can reduce it by subtracting m(x) via an XOR.
|
||
*
|
||
* It follows that multiplication by x (00000010 or 0x02) can be implemented
|
||
* by performing a left shift followed by a conditional bitwise XOR with
|
||
* 0x1b. This operation on bytes is denoted by xtime(). Multiplication by
|
||
* higher powers of x can be implemented by repeated application of xtime().
|
||
*
|
||
* By adding intermediate results, multiplication by any constant can be
|
||
* implemented. For instance:
|
||
*
|
||
* GF(0x57, 0x13) = 0xfe because:
|
||
*
|
||
* xtime(b) = (b & 128) ? (b << 1 ^ 0x11b) : (b << 1)
|
||
*
|
||
* Note: We XOR with 0x11b instead of 0x1b because in javascript our
|
||
* datatype for b can be larger than 1 byte, so a left shift will not
|
||
* automatically eliminate bits that overflow a byte ... by XOR'ing the
|
||
* overflow bit with 1 (the extra one from 0x11b) we zero it out.
|
||
*
|
||
* GF(0x57, 0x02) = xtime(0x57) = 0xae
|
||
* GF(0x57, 0x04) = xtime(0xae) = 0x47
|
||
* GF(0x57, 0x08) = xtime(0x47) = 0x8e
|
||
* GF(0x57, 0x10) = xtime(0x8e) = 0x07
|
||
*
|
||
* GF(0x57, 0x13) = GF(0x57, (0x01 ^ 0x02 ^ 0x10))
|
||
*
|
||
* And by the distributive property (since XOR is addition and GF() is
|
||
* multiplication):
|
||
*
|
||
* = GF(0x57, 0x01) ^ GF(0x57, 0x02) ^ GF(0x57, 0x10)
|
||
* = 0x57 ^ 0xae ^ 0x07
|
||
* = 0xfe.
|
||
*/
|
||
function initialize() {
|
||
init = true;
|
||
|
||
/* Populate the Rcon table. These are the values given by
|
||
[x^(i-1),{00},{00},{00}] where x^(i-1) are powers of x (and x = 0x02)
|
||
in the field of GF(2^8), where i starts at 1.
|
||
|
||
rcon[0] = [0x00, 0x00, 0x00, 0x00]
|
||
rcon[1] = [0x01, 0x00, 0x00, 0x00] 2^(1-1) = 2^0 = 1
|
||
rcon[2] = [0x02, 0x00, 0x00, 0x00] 2^(2-1) = 2^1 = 2
|
||
...
|
||
rcon[9] = [0x1B, 0x00, 0x00, 0x00] 2^(9-1) = 2^8 = 0x1B
|
||
rcon[10] = [0x36, 0x00, 0x00, 0x00] 2^(10-1) = 2^9 = 0x36
|
||
|
||
We only store the first byte because it is the only one used.
|
||
*/
|
||
rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
|
||
|
||
// compute xtime table which maps i onto GF(i, 0x02)
|
||
var xtime = new Array(256);
|
||
for(var i = 0; i < 128; ++i) {
|
||
xtime[i] = i << 1;
|
||
xtime[i + 128] = (i + 128) << 1 ^ 0x11B;
|
||
}
|
||
|
||
// compute all other tables
|
||
sbox = new Array(256);
|
||
isbox = new Array(256);
|
||
mix = new Array(4);
|
||
imix = new Array(4);
|
||
for(var i = 0; i < 4; ++i) {
|
||
mix[i] = new Array(256);
|
||
imix[i] = new Array(256);
|
||
}
|
||
var e = 0, ei = 0, e2, e4, e8, sx, sx2, me, ime;
|
||
for(var i = 0; i < 256; ++i) {
|
||
/* We need to generate the SubBytes() sbox and isbox tables so that
|
||
we can perform byte substitutions. This requires us to traverse
|
||
all of the elements in GF, find their multiplicative inverses,
|
||
and apply to each the following affine transformation:
|
||
|
||
bi' = bi ^ b(i + 4) mod 8 ^ b(i + 5) mod 8 ^ b(i + 6) mod 8 ^
|
||
b(i + 7) mod 8 ^ ci
|
||
for 0 <= i < 8, where bi is the ith bit of the byte, and ci is the
|
||
ith bit of a byte c with the value {63} or {01100011}.
|
||
|
||
It is possible to traverse every possible value in a Galois field
|
||
using what is referred to as a 'generator'. There are many
|
||
generators (128 out of 256): 3,5,6,9,11,82 to name a few. To fully
|
||
traverse GF we iterate 255 times, multiplying by our generator
|
||
each time.
|
||
|
||
On each iteration we can determine the multiplicative inverse for
|
||
the current element.
|
||
|
||
Suppose there is an element in GF 'e'. For a given generator 'g',
|
||
e = g^x. The multiplicative inverse of e is g^(255 - x). It turns
|
||
out that if use the inverse of a generator as another generator
|
||
it will produce all of the corresponding multiplicative inverses
|
||
at the same time. For this reason, we choose 5 as our inverse
|
||
generator because it only requires 2 multiplies and 1 add and its
|
||
inverse, 82, requires relatively few operations as well.
|
||
|
||
In order to apply the affine transformation, the multiplicative
|
||
inverse 'ei' of 'e' can be repeatedly XOR'd (4 times) with a
|
||
bit-cycling of 'ei'. To do this 'ei' is first stored in 's' and
|
||
'x'. Then 's' is left shifted and the high bit of 's' is made the
|
||
low bit. The resulting value is stored in 's'. Then 'x' is XOR'd
|
||
with 's' and stored in 'x'. On each subsequent iteration the same
|
||
operation is performed. When 4 iterations are complete, 'x' is
|
||
XOR'd with 'c' (0x63) and the transformed value is stored in 'x'.
|
||
For example:
|
||
|
||
s = 01000001
|
||
x = 01000001
|
||
|
||
iteration 1: s = 10000010, x ^= s
|
||
iteration 2: s = 00000101, x ^= s
|
||
iteration 3: s = 00001010, x ^= s
|
||
iteration 4: s = 00010100, x ^= s
|
||
x ^= 0x63
|
||
|
||
This can be done with a loop where s = (s << 1) | (s >> 7). However,
|
||
it can also be done by using a single 16-bit (in this case 32-bit)
|
||
number 'sx'. Since XOR is an associative operation, we can set 'sx'
|
||
to 'ei' and then XOR it with 'sx' left-shifted 1,2,3, and 4 times.
|
||
The most significant bits will flow into the high 8 bit positions
|
||
and be correctly XOR'd with one another. All that remains will be
|
||
to cycle the high 8 bits by XOR'ing them all with the lower 8 bits
|
||
afterwards.
|
||
|
||
At the same time we're populating sbox and isbox we can precompute
|
||
the multiplication we'll need to do to do MixColumns() later.
|
||
*/
|
||
|
||
// apply affine transformation
|
||
sx = ei ^ (ei << 1) ^ (ei << 2) ^ (ei << 3) ^ (ei << 4);
|
||
sx = (sx >> 8) ^ (sx & 255) ^ 0x63;
|
||
|
||
// update tables
|
||
sbox[e] = sx;
|
||
isbox[sx] = e;
|
||
|
||
/* Mixing columns is done using matrix multiplication. The columns
|
||
that are to be mixed are each a single word in the current state.
|
||
The state has Nb columns (4 columns). Therefore each column is a
|
||
4 byte word. So to mix the columns in a single column 'c' where
|
||
its rows are r0, r1, r2, and r3, we use the following matrix
|
||
multiplication:
|
||
|
||
[2 3 1 1]*[r0,c]=[r'0,c]
|
||
[1 2 3 1] [r1,c] [r'1,c]
|
||
[1 1 2 3] [r2,c] [r'2,c]
|
||
[3 1 1 2] [r3,c] [r'3,c]
|
||
|
||
r0, r1, r2, and r3 are each 1 byte of one of the words in the
|
||
state (a column). To do matrix multiplication for each mixed
|
||
column c' we multiply the corresponding row from the left matrix
|
||
with the corresponding column from the right matrix. In total, we
|
||
get 4 equations:
|
||
|
||
r0,c' = 2*r0,c + 3*r1,c + 1*r2,c + 1*r3,c
|
||
r1,c' = 1*r0,c + 2*r1,c + 3*r2,c + 1*r3,c
|
||
r2,c' = 1*r0,c + 1*r1,c + 2*r2,c + 3*r3,c
|
||
r3,c' = 3*r0,c + 1*r1,c + 1*r2,c + 2*r3,c
|
||
|
||
As usual, the multiplication is as previously defined and the
|
||
addition is XOR. In order to optimize mixing columns we can store
|
||
the multiplication results in tables. If you think of the whole
|
||
column as a word (it might help to visualize by mentally rotating
|
||
the equations above by counterclockwise 90 degrees) then you can
|
||
see that it would be useful to map the multiplications performed on
|
||
each byte (r0, r1, r2, r3) onto a word as well. For instance, we
|
||
could map 2*r0,1*r0,1*r0,3*r0 onto a word by storing 2*r0 in the
|
||
highest 8 bits and 3*r0 in the lowest 8 bits (with the other two
|
||
respectively in the middle). This means that a table can be
|
||
constructed that uses r0 as an index to the word. We can do the
|
||
same with r1, r2, and r3, creating a total of 4 tables.
|
||
|
||
To construct a full c', we can just look up each byte of c in
|
||
their respective tables and XOR the results together.
|
||
|
||
Also, to build each table we only have to calculate the word
|
||
for 2,1,1,3 for every byte ... which we can do on each iteration
|
||
of this loop since we will iterate over every byte. After we have
|
||
calculated 2,1,1,3 we can get the results for the other tables
|
||
by cycling the byte at the end to the beginning. For instance
|
||
we can take the result of table 2,1,1,3 and produce table 3,2,1,1
|
||
by moving the right most byte to the left most position just like
|
||
how you can imagine the 3 moved out of 2,1,1,3 and to the front
|
||
to produce 3,2,1,1.
|
||
|
||
There is another optimization in that the same multiples of
|
||
the current element we need in order to advance our generator
|
||
to the next iteration can be reused in performing the 2,1,1,3
|
||
calculation. We also calculate the inverse mix column tables,
|
||
with e,9,d,b being the inverse of 2,1,1,3.
|
||
|
||
When we're done, and we need to actually mix columns, the first
|
||
byte of each state word should be put through mix[0] (2,1,1,3),
|
||
the second through mix[1] (3,2,1,1) and so forth. Then they should
|
||
be XOR'd together to produce the fully mixed column.
|
||
*/
|
||
|
||
// calculate mix and imix table values
|
||
sx2 = xtime[sx];
|
||
e2 = xtime[e];
|
||
e4 = xtime[e2];
|
||
e8 = xtime[e4];
|
||
me =
|
||
(sx2 << 24) ^ // 2
|
||
(sx << 16) ^ // 1
|
||
(sx << 8) ^ // 1
|
||
(sx ^ sx2); // 3
|
||
ime =
|
||
(e2 ^ e4 ^ e8) << 24 ^ // E (14)
|
||
(e ^ e8) << 16 ^ // 9
|
||
(e ^ e4 ^ e8) << 8 ^ // D (13)
|
||
(e ^ e2 ^ e8); // B (11)
|
||
// produce each of the mix tables by rotating the 2,1,1,3 value
|
||
for(var n = 0; n < 4; ++n) {
|
||
mix[n][e] = me;
|
||
imix[n][sx] = ime;
|
||
// cycle the right most byte to the left most position
|
||
// ie: 2,1,1,3 becomes 3,2,1,1
|
||
me = me << 24 | me >>> 8;
|
||
ime = ime << 24 | ime >>> 8;
|
||
}
|
||
|
||
// get next element and inverse
|
||
if(e === 0) {
|
||
// 1 is the inverse of 1
|
||
e = ei = 1;
|
||
} else {
|
||
// e = 2e + 2*2*2*(10e)) = multiply e by 82 (chosen generator)
|
||
// ei = ei + 2*2*ei = multiply ei by 5 (inverse generator)
|
||
e = e2 ^ xtime[xtime[xtime[e2 ^ e8]]];
|
||
ei ^= xtime[xtime[ei]];
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generates a key schedule using the AES key expansion algorithm.
|
||
*
|
||
* The AES algorithm takes the Cipher Key, K, and performs a Key Expansion
|
||
* routine to generate a key schedule. The Key Expansion generates a total
|
||
* of Nb*(Nr + 1) words: the algorithm requires an initial set of Nb words,
|
||
* and each of the Nr rounds requires Nb words of key data. The resulting
|
||
* key schedule consists of a linear array of 4-byte words, denoted [wi ],
|
||
* with i in the range 0 ≤ i < Nb(Nr + 1).
|
||
*
|
||
* KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
|
||
* AES-128 (Nb=4, Nk=4, Nr=10)
|
||
* AES-192 (Nb=4, Nk=6, Nr=12)
|
||
* AES-256 (Nb=4, Nk=8, Nr=14)
|
||
* Note: Nr=Nk+6.
|
||
*
|
||
* Nb is the number of columns (32-bit words) comprising the State (or
|
||
* number of bytes in a block). For AES, Nb=4.
|
||
*
|
||
* @param key the key to schedule (as an array of 32-bit words).
|
||
* @param decrypt true to modify the key schedule to decrypt, false not to.
|
||
*
|
||
* @return the generated key schedule.
|
||
*/
|
||
function _expandKey(key, decrypt) {
|
||
// copy the key's words to initialize the key schedule
|
||
var w = key.slice(0);
|
||
|
||
/* RotWord() will rotate a word, moving the first byte to the last
|
||
byte's position (shifting the other bytes left).
|
||
|
||
We will be getting the value of Rcon at i / Nk. 'i' will iterate
|
||
from Nk to (Nb * Nr+1). Nk = 4 (4 byte key), Nb = 4 (4 words in
|
||
a block), Nr = Nk + 6 (10). Therefore 'i' will iterate from
|
||
4 to 44 (exclusive). Each time we iterate 4 times, i / Nk will
|
||
increase by 1. We use a counter iNk to keep track of this.
|
||
*/
|
||
|
||
// go through the rounds expanding the key
|
||
var temp, iNk = 1;
|
||
var Nk = w.length;
|
||
var Nr1 = Nk + 6 + 1;
|
||
var end = Nb * Nr1;
|
||
for(var i = Nk; i < end; ++i) {
|
||
temp = w[i - 1];
|
||
if(i % Nk === 0) {
|
||
// temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk]
|
||
temp =
|
||
sbox[temp >>> 16 & 255] << 24 ^
|
||
sbox[temp >>> 8 & 255] << 16 ^
|
||
sbox[temp & 255] << 8 ^
|
||
sbox[temp >>> 24] ^ (rcon[iNk] << 24);
|
||
iNk++;
|
||
} else if(Nk > 6 && (i % Nk === 4)) {
|
||
// temp = SubWord(temp)
|
||
temp =
|
||
sbox[temp >>> 24] << 24 ^
|
||
sbox[temp >>> 16 & 255] << 16 ^
|
||
sbox[temp >>> 8 & 255] << 8 ^
|
||
sbox[temp & 255];
|
||
}
|
||
w[i] = w[i - Nk] ^ temp;
|
||
}
|
||
|
||
/* When we are updating a cipher block we always use the code path for
|
||
encryption whether we are decrypting or not (to shorten code and
|
||
simplify the generation of look up tables). However, because there
|
||
are differences in the decryption algorithm, other than just swapping
|
||
in different look up tables, we must transform our key schedule to
|
||
account for these changes:
|
||
|
||
1. The decryption algorithm gets its key rounds in reverse order.
|
||
2. The decryption algorithm adds the round key before mixing columns
|
||
instead of afterwards.
|
||
|
||
We don't need to modify our key schedule to handle the first case,
|
||
we can just traverse the key schedule in reverse order when decrypting.
|
||
|
||
The second case requires a little work.
|
||
|
||
The tables we built for performing rounds will take an input and then
|
||
perform SubBytes() and MixColumns() or, for the decrypt version,
|
||
InvSubBytes() and InvMixColumns(). But the decrypt algorithm requires
|
||
us to AddRoundKey() before InvMixColumns(). This means we'll need to
|
||
apply some transformations to the round key to inverse-mix its columns
|
||
so they'll be correct for moving AddRoundKey() to after the state has
|
||
had its columns inverse-mixed.
|
||
|
||
To inverse-mix the columns of the state when we're decrypting we use a
|
||
lookup table that will apply InvSubBytes() and InvMixColumns() at the
|
||
same time. However, the round key's bytes are not inverse-substituted
|
||
in the decryption algorithm. To get around this problem, we can first
|
||
substitute the bytes in the round key so that when we apply the
|
||
transformation via the InvSubBytes()+InvMixColumns() table, it will
|
||
undo our substitution leaving us with the original value that we
|
||
want -- and then inverse-mix that value.
|
||
|
||
This change will correctly alter our key schedule so that we can XOR
|
||
each round key with our already transformed decryption state. This
|
||
allows us to use the same code path as the encryption algorithm.
|
||
|
||
We make one more change to the decryption key. Since the decryption
|
||
algorithm runs in reverse from the encryption algorithm, we reverse
|
||
the order of the round keys to avoid having to iterate over the key
|
||
schedule backwards when running the encryption algorithm later in
|
||
decryption mode. In addition to reversing the order of the round keys,
|
||
we also swap each round key's 2nd and 4th rows. See the comments
|
||
section where rounds are performed for more details about why this is
|
||
done. These changes are done inline with the other substitution
|
||
described above.
|
||
*/
|
||
if(decrypt) {
|
||
var tmp;
|
||
var m0 = imix[0];
|
||
var m1 = imix[1];
|
||
var m2 = imix[2];
|
||
var m3 = imix[3];
|
||
var wnew = w.slice(0);
|
||
end = w.length;
|
||
for(var i = 0, wi = end - Nb; i < end; i += Nb, wi -= Nb) {
|
||
// do not sub the first or last round key (round keys are Nb
|
||
// words) as no column mixing is performed before they are added,
|
||
// but do change the key order
|
||
if(i === 0 || i === (end - Nb)) {
|
||
wnew[i] = w[wi];
|
||
wnew[i + 1] = w[wi + 3];
|
||
wnew[i + 2] = w[wi + 2];
|
||
wnew[i + 3] = w[wi + 1];
|
||
} else {
|
||
// substitute each round key byte because the inverse-mix
|
||
// table will inverse-substitute it (effectively cancel the
|
||
// substitution because round key bytes aren't sub'd in
|
||
// decryption mode) and swap indexes 3 and 1
|
||
for(var n = 0; n < Nb; ++n) {
|
||
tmp = w[wi + n];
|
||
wnew[i + (3&-n)] =
|
||
m0[sbox[tmp >>> 24]] ^
|
||
m1[sbox[tmp >>> 16 & 255]] ^
|
||
m2[sbox[tmp >>> 8 & 255]] ^
|
||
m3[sbox[tmp & 255]];
|
||
}
|
||
}
|
||
}
|
||
w = wnew;
|
||
}
|
||
|
||
return w;
|
||
}
|
||
|
||
/**
|
||
* Updates a single block (16 bytes) using AES. The update will either
|
||
* encrypt or decrypt the block.
|
||
*
|
||
* @param w the key schedule.
|
||
* @param input the input block (an array of 32-bit words).
|
||
* @param output the updated output block.
|
||
* @param decrypt true to decrypt the block, false to encrypt it.
|
||
*/
|
||
function _updateBlock(w, input, output, decrypt) {
|
||
/*
|
||
Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
|
||
begin
|
||
byte state[4,Nb]
|
||
state = in
|
||
AddRoundKey(state, w[0, Nb-1])
|
||
for round = 1 step 1 to Nr–1
|
||
SubBytes(state)
|
||
ShiftRows(state)
|
||
MixColumns(state)
|
||
AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
|
||
end for
|
||
SubBytes(state)
|
||
ShiftRows(state)
|
||
AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
|
||
out = state
|
||
end
|
||
|
||
InvCipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
|
||
begin
|
||
byte state[4,Nb]
|
||
state = in
|
||
AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
|
||
for round = Nr-1 step -1 downto 1
|
||
InvShiftRows(state)
|
||
InvSubBytes(state)
|
||
AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
|
||
InvMixColumns(state)
|
||
end for
|
||
InvShiftRows(state)
|
||
InvSubBytes(state)
|
||
AddRoundKey(state, w[0, Nb-1])
|
||
out = state
|
||
end
|
||
*/
|
||
|
||
// Encrypt: AddRoundKey(state, w[0, Nb-1])
|
||
// Decrypt: AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
|
||
var Nr = w.length / 4 - 1;
|
||
var m0, m1, m2, m3, sub;
|
||
if(decrypt) {
|
||
m0 = imix[0];
|
||
m1 = imix[1];
|
||
m2 = imix[2];
|
||
m3 = imix[3];
|
||
sub = isbox;
|
||
} else {
|
||
m0 = mix[0];
|
||
m1 = mix[1];
|
||
m2 = mix[2];
|
||
m3 = mix[3];
|
||
sub = sbox;
|
||
}
|
||
var a, b, c, d, a2, b2, c2;
|
||
a = input[0] ^ w[0];
|
||
b = input[decrypt ? 3 : 1] ^ w[1];
|
||
c = input[2] ^ w[2];
|
||
d = input[decrypt ? 1 : 3] ^ w[3];
|
||
var i = 3;
|
||
|
||
/* In order to share code we follow the encryption algorithm when both
|
||
encrypting and decrypting. To account for the changes required in the
|
||
decryption algorithm, we use different lookup tables when decrypting
|
||
and use a modified key schedule to account for the difference in the
|
||
order of transformations applied when performing rounds. We also get
|
||
key rounds in reverse order (relative to encryption). */
|
||
for(var round = 1; round < Nr; ++round) {
|
||
/* As described above, we'll be using table lookups to perform the
|
||
column mixing. Each column is stored as a word in the state (the
|
||
array 'input' has one column as a word at each index). In order to
|
||
mix a column, we perform these transformations on each row in c,
|
||
which is 1 byte in each word. The new column for c0 is c'0:
|
||
|
||
m0 m1 m2 m3
|
||
r0,c'0 = 2*r0,c0 + 3*r1,c0 + 1*r2,c0 + 1*r3,c0
|
||
r1,c'0 = 1*r0,c0 + 2*r1,c0 + 3*r2,c0 + 1*r3,c0
|
||
r2,c'0 = 1*r0,c0 + 1*r1,c0 + 2*r2,c0 + 3*r3,c0
|
||
r3,c'0 = 3*r0,c0 + 1*r1,c0 + 1*r2,c0 + 2*r3,c0
|
||
|
||
So using mix tables where c0 is a word with r0 being its upper
|
||
8 bits and r3 being its lower 8 bits:
|
||
|
||
m0[c0 >> 24] will yield this word: [2*r0,1*r0,1*r0,3*r0]
|
||
...
|
||
m3[c0 & 255] will yield this word: [1*r3,1*r3,3*r3,2*r3]
|
||
|
||
Therefore to mix the columns in each word in the state we
|
||
do the following (& 255 omitted for brevity):
|
||
c'0,r0 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
|
||
c'0,r1 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
|
||
c'0,r2 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
|
||
c'0,r3 = m0[c0 >> 24] ^ m1[c1 >> 16] ^ m2[c2 >> 8] ^ m3[c3]
|
||
|
||
However, before mixing, the algorithm requires us to perform
|
||
ShiftRows(). The ShiftRows() transformation cyclically shifts the
|
||
last 3 rows of the state over different offsets. The first row
|
||
(r = 0) is not shifted.
|
||
|
||
s'_r,c = s_r,(c + shift(r, Nb) mod Nb
|
||
for 0 < r < 4 and 0 <= c < Nb and
|
||
shift(1, 4) = 1
|
||
shift(2, 4) = 2
|
||
shift(3, 4) = 3.
|
||
|
||
This causes the first byte in r = 1 to be moved to the end of
|
||
the row, the first 2 bytes in r = 2 to be moved to the end of
|
||
the row, the first 3 bytes in r = 3 to be moved to the end of
|
||
the row:
|
||
|
||
r1: [c0 c1 c2 c3] => [c1 c2 c3 c0]
|
||
r2: [c0 c1 c2 c3] [c2 c3 c0 c1]
|
||
r3: [c0 c1 c2 c3] [c3 c0 c1 c2]
|
||
|
||
We can make these substitutions inline with our column mixing to
|
||
generate an updated set of equations to produce each word in the
|
||
state (note the columns have changed positions):
|
||
|
||
c0 c1 c2 c3 => c0 c1 c2 c3
|
||
c0 c1 c2 c3 c1 c2 c3 c0 (cycled 1 byte)
|
||
c0 c1 c2 c3 c2 c3 c0 c1 (cycled 2 bytes)
|
||
c0 c1 c2 c3 c3 c0 c1 c2 (cycled 3 bytes)
|
||
|
||
Therefore:
|
||
|
||
c'0 = 2*r0,c0 + 3*r1,c1 + 1*r2,c2 + 1*r3,c3
|
||
c'0 = 1*r0,c0 + 2*r1,c1 + 3*r2,c2 + 1*r3,c3
|
||
c'0 = 1*r0,c0 + 1*r1,c1 + 2*r2,c2 + 3*r3,c3
|
||
c'0 = 3*r0,c0 + 1*r1,c1 + 1*r2,c2 + 2*r3,c3
|
||
|
||
c'1 = 2*r0,c1 + 3*r1,c2 + 1*r2,c3 + 1*r3,c0
|
||
c'1 = 1*r0,c1 + 2*r1,c2 + 3*r2,c3 + 1*r3,c0
|
||
c'1 = 1*r0,c1 + 1*r1,c2 + 2*r2,c3 + 3*r3,c0
|
||
c'1 = 3*r0,c1 + 1*r1,c2 + 1*r2,c3 + 2*r3,c0
|
||
|
||
... and so forth for c'2 and c'3. The important distinction is
|
||
that the columns are cycling, with c0 being used with the m0
|
||
map when calculating c0, but c1 being used with the m0 map when
|
||
calculating c1 ... and so forth.
|
||
|
||
When performing the inverse we transform the mirror image and
|
||
skip the bottom row, instead of the top one, and move upwards:
|
||
|
||
c3 c2 c1 c0 => c0 c3 c2 c1 (cycled 3 bytes) *same as encryption
|
||
c3 c2 c1 c0 c1 c0 c3 c2 (cycled 2 bytes)
|
||
c3 c2 c1 c0 c2 c1 c0 c3 (cycled 1 byte) *same as encryption
|
||
c3 c2 c1 c0 c3 c2 c1 c0
|
||
|
||
If you compare the resulting matrices for ShiftRows()+MixColumns()
|
||
and for InvShiftRows()+InvMixColumns() the 2nd and 4th columns are
|
||
different (in encrypt mode vs. decrypt mode). So in order to use
|
||
the same code to handle both encryption and decryption, we will
|
||
need to do some mapping.
|
||
|
||
If in encryption mode we let a=c0, b=c1, c=c2, d=c3, and r<N> be
|
||
a row number in the state, then the resulting matrix in encryption
|
||
mode for applying the above transformations would be:
|
||
|
||
r1: a b c d
|
||
r2: b c d a
|
||
r3: c d a b
|
||
r4: d a b c
|
||
|
||
If we did the same in decryption mode we would get:
|
||
|
||
r1: a d c b
|
||
r2: b a d c
|
||
r3: c b a d
|
||
r4: d c b a
|
||
|
||
If instead we swap d and b (set b=c3 and d=c1), then we get:
|
||
|
||
r1: a b c d
|
||
r2: d a b c
|
||
r3: c d a b
|
||
r4: b c d a
|
||
|
||
Now the 1st and 3rd rows are the same as the encryption matrix. All
|
||
we need to do then to make the mapping exactly the same is to swap
|
||
the 2nd and 4th rows when in decryption mode. To do this without
|
||
having to do it on each iteration, we swapped the 2nd and 4th rows
|
||
in the decryption key schedule. We also have to do the swap above
|
||
when we first pull in the input and when we set the final output. */
|
||
a2 =
|
||
m0[a >>> 24] ^
|
||
m1[b >>> 16 & 255] ^
|
||
m2[c >>> 8 & 255] ^
|
||
m3[d & 255] ^ w[++i];
|
||
b2 =
|
||
m0[b >>> 24] ^
|
||
m1[c >>> 16 & 255] ^
|
||
m2[d >>> 8 & 255] ^
|
||
m3[a & 255] ^ w[++i];
|
||
c2 =
|
||
m0[c >>> 24] ^
|
||
m1[d >>> 16 & 255] ^
|
||
m2[a >>> 8 & 255] ^
|
||
m3[b & 255] ^ w[++i];
|
||
d =
|
||
m0[d >>> 24] ^
|
||
m1[a >>> 16 & 255] ^
|
||
m2[b >>> 8 & 255] ^
|
||
m3[c & 255] ^ w[++i];
|
||
a = a2;
|
||
b = b2;
|
||
c = c2;
|
||
}
|
||
|
||
/*
|
||
Encrypt:
|
||
SubBytes(state)
|
||
ShiftRows(state)
|
||
AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
|
||
|
||
Decrypt:
|
||
InvShiftRows(state)
|
||
InvSubBytes(state)
|
||
AddRoundKey(state, w[0, Nb-1])
|
||
*/
|
||
// Note: rows are shifted inline
|
||
output[0] =
|
||
(sub[a >>> 24] << 24) ^
|
||
(sub[b >>> 16 & 255] << 16) ^
|
||
(sub[c >>> 8 & 255] << 8) ^
|
||
(sub[d & 255]) ^ w[++i];
|
||
output[decrypt ? 3 : 1] =
|
||
(sub[b >>> 24] << 24) ^
|
||
(sub[c >>> 16 & 255] << 16) ^
|
||
(sub[d >>> 8 & 255] << 8) ^
|
||
(sub[a & 255]) ^ w[++i];
|
||
output[2] =
|
||
(sub[c >>> 24] << 24) ^
|
||
(sub[d >>> 16 & 255] << 16) ^
|
||
(sub[a >>> 8 & 255] << 8) ^
|
||
(sub[b & 255]) ^ w[++i];
|
||
output[decrypt ? 1 : 3] =
|
||
(sub[d >>> 24] << 24) ^
|
||
(sub[a >>> 16 & 255] << 16) ^
|
||
(sub[b >>> 8 & 255] << 8) ^
|
||
(sub[c & 255]) ^ w[++i];
|
||
}
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* forge.cipher.createCipher('AES-<mode>', key);
|
||
* forge.cipher.createDecipher('AES-<mode>', key);
|
||
*
|
||
* Creates a deprecated AES cipher object. This object's mode will default to
|
||
* CBC (cipher-block-chaining).
|
||
*
|
||
* The key and iv may be given as a string of bytes, an array of bytes, a
|
||
* byte buffer, or an array of 32-bit words.
|
||
*
|
||
* @param options the options to use.
|
||
* key the symmetric key to use.
|
||
* output the buffer to write to.
|
||
* decrypt true for decryption, false for encryption.
|
||
* mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
function _createCipher(options) {
|
||
options = options || {};
|
||
var mode = (options.mode || 'CBC').toUpperCase();
|
||
var algorithm = 'AES-' + mode;
|
||
|
||
var cipher;
|
||
if(options.decrypt) {
|
||
cipher = forge.cipher.createDecipher(algorithm, options.key);
|
||
} else {
|
||
cipher = forge.cipher.createCipher(algorithm, options.key);
|
||
}
|
||
|
||
// backwards compatible start API
|
||
var start = cipher.start;
|
||
cipher.start = function(iv, options) {
|
||
// backwards compatibility: support second arg as output buffer
|
||
var output = null;
|
||
if(options instanceof forge.util.ByteBuffer) {
|
||
output = options;
|
||
options = {};
|
||
}
|
||
options = options || {};
|
||
options.output = output;
|
||
options.iv = iv;
|
||
start.call(cipher, options);
|
||
};
|
||
|
||
return cipher;
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'aes';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define(
|
||
'js/aes',['require', 'module', './cipher', './cipherModes', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Object IDs for ASN.1.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.pki = forge.pki || {};
|
||
var oids = forge.pki.oids = forge.oids = forge.oids || {};
|
||
|
||
// algorithm OIDs
|
||
oids['1.2.840.113549.1.1.1'] = 'rsaEncryption';
|
||
oids['rsaEncryption'] = '1.2.840.113549.1.1.1';
|
||
// Note: md2 & md4 not implemented
|
||
//oids['1.2.840.113549.1.1.2'] = 'md2WithRSAEncryption';
|
||
//oids['md2WithRSAEncryption'] = '1.2.840.113549.1.1.2';
|
||
//oids['1.2.840.113549.1.1.3'] = 'md4WithRSAEncryption';
|
||
//oids['md4WithRSAEncryption'] = '1.2.840.113549.1.1.3';
|
||
oids['1.2.840.113549.1.1.4'] = 'md5WithRSAEncryption';
|
||
oids['md5WithRSAEncryption'] = '1.2.840.113549.1.1.4';
|
||
oids['1.2.840.113549.1.1.5'] = 'sha1WithRSAEncryption';
|
||
oids['sha1WithRSAEncryption'] = '1.2.840.113549.1.1.5';
|
||
oids['1.2.840.113549.1.1.7'] = 'RSAES-OAEP';
|
||
oids['RSAES-OAEP'] = '1.2.840.113549.1.1.7';
|
||
oids['1.2.840.113549.1.1.8'] = 'mgf1';
|
||
oids['mgf1'] = '1.2.840.113549.1.1.8';
|
||
oids['1.2.840.113549.1.1.9'] = 'pSpecified';
|
||
oids['pSpecified'] = '1.2.840.113549.1.1.9';
|
||
oids['1.2.840.113549.1.1.10'] = 'RSASSA-PSS';
|
||
oids['RSASSA-PSS'] = '1.2.840.113549.1.1.10';
|
||
oids['1.2.840.113549.1.1.11'] = 'sha256WithRSAEncryption';
|
||
oids['sha256WithRSAEncryption'] = '1.2.840.113549.1.1.11';
|
||
oids['1.2.840.113549.1.1.12'] = 'sha384WithRSAEncryption';
|
||
oids['sha384WithRSAEncryption'] = '1.2.840.113549.1.1.12';
|
||
oids['1.2.840.113549.1.1.13'] = 'sha512WithRSAEncryption';
|
||
oids['sha512WithRSAEncryption'] = '1.2.840.113549.1.1.13';
|
||
|
||
oids['1.3.14.3.2.7'] = 'desCBC';
|
||
oids['desCBC'] = '1.3.14.3.2.7';
|
||
|
||
oids['1.3.14.3.2.26'] = 'sha1';
|
||
oids['sha1'] = '1.3.14.3.2.26';
|
||
oids['2.16.840.1.101.3.4.2.1'] = 'sha256';
|
||
oids['sha256'] = '2.16.840.1.101.3.4.2.1';
|
||
oids['2.16.840.1.101.3.4.2.2'] = 'sha384';
|
||
oids['sha384'] = '2.16.840.1.101.3.4.2.2';
|
||
oids['2.16.840.1.101.3.4.2.3'] = 'sha512';
|
||
oids['sha512'] = '2.16.840.1.101.3.4.2.3';
|
||
oids['1.2.840.113549.2.5'] = 'md5';
|
||
oids['md5'] = '1.2.840.113549.2.5';
|
||
|
||
// pkcs#7 content types
|
||
oids['1.2.840.113549.1.7.1'] = 'data';
|
||
oids['data'] = '1.2.840.113549.1.7.1';
|
||
oids['1.2.840.113549.1.7.2'] = 'signedData';
|
||
oids['signedData'] = '1.2.840.113549.1.7.2';
|
||
oids['1.2.840.113549.1.7.3'] = 'envelopedData';
|
||
oids['envelopedData'] = '1.2.840.113549.1.7.3';
|
||
oids['1.2.840.113549.1.7.4'] = 'signedAndEnvelopedData';
|
||
oids['signedAndEnvelopedData'] = '1.2.840.113549.1.7.4';
|
||
oids['1.2.840.113549.1.7.5'] = 'digestedData';
|
||
oids['digestedData'] = '1.2.840.113549.1.7.5';
|
||
oids['1.2.840.113549.1.7.6'] = 'encryptedData';
|
||
oids['encryptedData'] = '1.2.840.113549.1.7.6';
|
||
|
||
// pkcs#9 oids
|
||
oids['1.2.840.113549.1.9.1'] = 'emailAddress';
|
||
oids['emailAddress'] = '1.2.840.113549.1.9.1';
|
||
oids['1.2.840.113549.1.9.2'] = 'unstructuredName';
|
||
oids['unstructuredName'] = '1.2.840.113549.1.9.2';
|
||
oids['1.2.840.113549.1.9.3'] = 'contentType';
|
||
oids['contentType'] = '1.2.840.113549.1.9.3';
|
||
oids['1.2.840.113549.1.9.4'] = 'messageDigest';
|
||
oids['messageDigest'] = '1.2.840.113549.1.9.4';
|
||
oids['1.2.840.113549.1.9.5'] = 'signingTime';
|
||
oids['signingTime'] = '1.2.840.113549.1.9.5';
|
||
oids['1.2.840.113549.1.9.6'] = 'counterSignature';
|
||
oids['counterSignature'] = '1.2.840.113549.1.9.6';
|
||
oids['1.2.840.113549.1.9.7'] = 'challengePassword';
|
||
oids['challengePassword'] = '1.2.840.113549.1.9.7';
|
||
oids['1.2.840.113549.1.9.8'] = 'unstructuredAddress';
|
||
oids['unstructuredAddress'] = '1.2.840.113549.1.9.8';
|
||
oids['1.2.840.113549.1.9.14'] = 'extensionRequest';
|
||
oids['extensionRequest'] = '1.2.840.113549.1.9.14';
|
||
|
||
oids['1.2.840.113549.1.9.20'] = 'friendlyName';
|
||
oids['friendlyName'] = '1.2.840.113549.1.9.20';
|
||
oids['1.2.840.113549.1.9.21'] = 'localKeyId';
|
||
oids['localKeyId'] = '1.2.840.113549.1.9.21';
|
||
oids['1.2.840.113549.1.9.22.1'] = 'x509Certificate';
|
||
oids['x509Certificate'] = '1.2.840.113549.1.9.22.1';
|
||
|
||
// pkcs#12 safe bags
|
||
oids['1.2.840.113549.1.12.10.1.1'] = 'keyBag';
|
||
oids['keyBag'] = '1.2.840.113549.1.12.10.1.1';
|
||
oids['1.2.840.113549.1.12.10.1.2'] = 'pkcs8ShroudedKeyBag';
|
||
oids['pkcs8ShroudedKeyBag'] = '1.2.840.113549.1.12.10.1.2';
|
||
oids['1.2.840.113549.1.12.10.1.3'] = 'certBag';
|
||
oids['certBag'] = '1.2.840.113549.1.12.10.1.3';
|
||
oids['1.2.840.113549.1.12.10.1.4'] = 'crlBag';
|
||
oids['crlBag'] = '1.2.840.113549.1.12.10.1.4';
|
||
oids['1.2.840.113549.1.12.10.1.5'] = 'secretBag';
|
||
oids['secretBag'] = '1.2.840.113549.1.12.10.1.5';
|
||
oids['1.2.840.113549.1.12.10.1.6'] = 'safeContentsBag';
|
||
oids['safeContentsBag'] = '1.2.840.113549.1.12.10.1.6';
|
||
|
||
// password-based-encryption for pkcs#12
|
||
oids['1.2.840.113549.1.5.13'] = 'pkcs5PBES2';
|
||
oids['pkcs5PBES2'] = '1.2.840.113549.1.5.13';
|
||
oids['1.2.840.113549.1.5.12'] = 'pkcs5PBKDF2';
|
||
oids['pkcs5PBKDF2'] = '1.2.840.113549.1.5.12';
|
||
|
||
oids['1.2.840.113549.1.12.1.1'] = 'pbeWithSHAAnd128BitRC4';
|
||
oids['pbeWithSHAAnd128BitRC4'] = '1.2.840.113549.1.12.1.1';
|
||
oids['1.2.840.113549.1.12.1.2'] = 'pbeWithSHAAnd40BitRC4';
|
||
oids['pbeWithSHAAnd40BitRC4'] = '1.2.840.113549.1.12.1.2';
|
||
oids['1.2.840.113549.1.12.1.3'] = 'pbeWithSHAAnd3-KeyTripleDES-CBC';
|
||
oids['pbeWithSHAAnd3-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.3';
|
||
oids['1.2.840.113549.1.12.1.4'] = 'pbeWithSHAAnd2-KeyTripleDES-CBC';
|
||
oids['pbeWithSHAAnd2-KeyTripleDES-CBC'] = '1.2.840.113549.1.12.1.4';
|
||
oids['1.2.840.113549.1.12.1.5'] = 'pbeWithSHAAnd128BitRC2-CBC';
|
||
oids['pbeWithSHAAnd128BitRC2-CBC'] = '1.2.840.113549.1.12.1.5';
|
||
oids['1.2.840.113549.1.12.1.6'] = 'pbewithSHAAnd40BitRC2-CBC';
|
||
oids['pbewithSHAAnd40BitRC2-CBC'] = '1.2.840.113549.1.12.1.6';
|
||
|
||
// symmetric key algorithm oids
|
||
oids['1.2.840.113549.3.7'] = 'des-EDE3-CBC';
|
||
oids['des-EDE3-CBC'] = '1.2.840.113549.3.7';
|
||
oids['2.16.840.1.101.3.4.1.2'] = 'aes128-CBC';
|
||
oids['aes128-CBC'] = '2.16.840.1.101.3.4.1.2';
|
||
oids['2.16.840.1.101.3.4.1.22'] = 'aes192-CBC';
|
||
oids['aes192-CBC'] = '2.16.840.1.101.3.4.1.22';
|
||
oids['2.16.840.1.101.3.4.1.42'] = 'aes256-CBC';
|
||
oids['aes256-CBC'] = '2.16.840.1.101.3.4.1.42';
|
||
|
||
// certificate issuer/subject OIDs
|
||
oids['2.5.4.3'] = 'commonName';
|
||
oids['commonName'] = '2.5.4.3';
|
||
oids['2.5.4.5'] = 'serialName';
|
||
oids['serialName'] = '2.5.4.5';
|
||
oids['2.5.4.6'] = 'countryName';
|
||
oids['countryName'] = '2.5.4.6';
|
||
oids['2.5.4.7'] = 'localityName';
|
||
oids['localityName'] = '2.5.4.7';
|
||
oids['2.5.4.8'] = 'stateOrProvinceName';
|
||
oids['stateOrProvinceName'] = '2.5.4.8';
|
||
oids['2.5.4.10'] = 'organizationName';
|
||
oids['organizationName'] = '2.5.4.10';
|
||
oids['2.5.4.11'] = 'organizationalUnitName';
|
||
oids['organizationalUnitName'] = '2.5.4.11';
|
||
|
||
// X.509 extension OIDs
|
||
oids['2.16.840.1.113730.1.1'] = 'nsCertType';
|
||
oids['nsCertType'] = '2.16.840.1.113730.1.1';
|
||
oids['2.5.29.1'] = 'authorityKeyIdentifier'; // deprecated, use .35
|
||
oids['2.5.29.2'] = 'keyAttributes'; // obsolete use .37 or .15
|
||
oids['2.5.29.3'] = 'certificatePolicies'; // deprecated, use .32
|
||
oids['2.5.29.4'] = 'keyUsageRestriction'; // obsolete use .37 or .15
|
||
oids['2.5.29.5'] = 'policyMapping'; // deprecated use .33
|
||
oids['2.5.29.6'] = 'subtreesConstraint'; // obsolete use .30
|
||
oids['2.5.29.7'] = 'subjectAltName'; // deprecated use .17
|
||
oids['2.5.29.8'] = 'issuerAltName'; // deprecated use .18
|
||
oids['2.5.29.9'] = 'subjectDirectoryAttributes';
|
||
oids['2.5.29.10'] = 'basicConstraints'; // deprecated use .19
|
||
oids['2.5.29.11'] = 'nameConstraints'; // deprecated use .30
|
||
oids['2.5.29.12'] = 'policyConstraints'; // deprecated use .36
|
||
oids['2.5.29.13'] = 'basicConstraints'; // deprecated use .19
|
||
oids['2.5.29.14'] = 'subjectKeyIdentifier';
|
||
oids['subjectKeyIdentifier'] = '2.5.29.14';
|
||
oids['2.5.29.15'] = 'keyUsage';
|
||
oids['keyUsage'] = '2.5.29.15';
|
||
oids['2.5.29.16'] = 'privateKeyUsagePeriod';
|
||
oids['2.5.29.17'] = 'subjectAltName';
|
||
oids['subjectAltName'] = '2.5.29.17';
|
||
oids['2.5.29.18'] = 'issuerAltName';
|
||
oids['issuerAltName'] = '2.5.29.18';
|
||
oids['2.5.29.19'] = 'basicConstraints';
|
||
oids['basicConstraints'] = '2.5.29.19';
|
||
oids['2.5.29.20'] = 'cRLNumber';
|
||
oids['2.5.29.21'] = 'cRLReason';
|
||
oids['2.5.29.22'] = 'expirationDate';
|
||
oids['2.5.29.23'] = 'instructionCode';
|
||
oids['2.5.29.24'] = 'invalidityDate';
|
||
oids['2.5.29.25'] = 'cRLDistributionPoints'; // deprecated use .31
|
||
oids['2.5.29.26'] = 'issuingDistributionPoint'; // deprecated use .28
|
||
oids['2.5.29.27'] = 'deltaCRLIndicator';
|
||
oids['2.5.29.28'] = 'issuingDistributionPoint';
|
||
oids['2.5.29.29'] = 'certificateIssuer';
|
||
oids['2.5.29.30'] = 'nameConstraints';
|
||
oids['2.5.29.31'] = 'cRLDistributionPoints';
|
||
oids['2.5.29.32'] = 'certificatePolicies';
|
||
oids['2.5.29.33'] = 'policyMappings';
|
||
oids['2.5.29.34'] = 'policyConstraints'; // deprecated use .36
|
||
oids['2.5.29.35'] = 'authorityKeyIdentifier';
|
||
oids['2.5.29.36'] = 'policyConstraints';
|
||
oids['2.5.29.37'] = 'extKeyUsage';
|
||
oids['extKeyUsage'] = '2.5.29.37';
|
||
oids['2.5.29.46'] = 'freshestCRL';
|
||
oids['2.5.29.54'] = 'inhibitAnyPolicy';
|
||
|
||
// extKeyUsage purposes
|
||
oids['1.3.6.1.5.5.7.3.1'] = 'serverAuth';
|
||
oids['serverAuth'] = '1.3.6.1.5.5.7.3.1';
|
||
oids['1.3.6.1.5.5.7.3.2'] = 'clientAuth';
|
||
oids['clientAuth'] = '1.3.6.1.5.5.7.3.2';
|
||
oids['1.3.6.1.5.5.7.3.3'] = 'codeSigning';
|
||
oids['codeSigning'] = '1.3.6.1.5.5.7.3.3';
|
||
oids['1.3.6.1.5.5.7.3.4'] = 'emailProtection';
|
||
oids['emailProtection'] = '1.3.6.1.5.5.7.3.4';
|
||
oids['1.3.6.1.5.5.7.3.8'] = 'timeStamping';
|
||
oids['timeStamping'] = '1.3.6.1.5.5.7.3.8';
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'oids';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/oids',['require', 'module'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of Abstract Syntax Notation Number One.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2015 Digital Bazaar, Inc.
|
||
*
|
||
* An API for storing data using the Abstract Syntax Notation Number One
|
||
* format using DER (Distinguished Encoding Rules) encoding. This encoding is
|
||
* commonly used to store data for PKI, i.e. X.509 Certificates, and this
|
||
* implementation exists for that purpose.
|
||
*
|
||
* Abstract Syntax Notation Number One (ASN.1) is used to define the abstract
|
||
* syntax of information without restricting the way the information is encoded
|
||
* for transmission. It provides a standard that allows for open systems
|
||
* communication. ASN.1 defines the syntax of information data and a number of
|
||
* simple data types as well as a notation for describing them and specifying
|
||
* values for them.
|
||
*
|
||
* The RSA algorithm creates public and private keys that are often stored in
|
||
* X.509 or PKCS#X formats -- which use ASN.1 (encoded in DER format). This
|
||
* class provides the most basic functionality required to store and load DSA
|
||
* keys that are encoded according to ASN.1.
|
||
*
|
||
* The most common binary encodings for ASN.1 are BER (Basic Encoding Rules)
|
||
* and DER (Distinguished Encoding Rules). DER is just a subset of BER that
|
||
* has stricter requirements for how data must be encoded.
|
||
*
|
||
* Each ASN.1 structure has a tag (a byte identifying the ASN.1 structure type)
|
||
* and a byte array for the value of this ASN1 structure which may be data or a
|
||
* list of ASN.1 structures.
|
||
*
|
||
* Each ASN.1 structure using BER is (Tag-Length-Value):
|
||
*
|
||
* | byte 0 | bytes X | bytes Y |
|
||
* |--------|---------|----------
|
||
* | tag | length | value |
|
||
*
|
||
* ASN.1 allows for tags to be of "High-tag-number form" which allows a tag to
|
||
* be two or more octets, but that is not supported by this class. A tag is
|
||
* only 1 byte. Bits 1-5 give the tag number (ie the data type within a
|
||
* particular 'class'), 6 indicates whether or not the ASN.1 value is
|
||
* constructed from other ASN.1 values, and bits 7 and 8 give the 'class'. If
|
||
* bits 7 and 8 are both zero, the class is UNIVERSAL. If only bit 7 is set,
|
||
* then the class is APPLICATION. If only bit 8 is set, then the class is
|
||
* CONTEXT_SPECIFIC. If both bits 7 and 8 are set, then the class is PRIVATE.
|
||
* The tag numbers for the data types for the class UNIVERSAL are listed below:
|
||
*
|
||
* UNIVERSAL 0 Reserved for use by the encoding rules
|
||
* UNIVERSAL 1 Boolean type
|
||
* UNIVERSAL 2 Integer type
|
||
* UNIVERSAL 3 Bitstring type
|
||
* UNIVERSAL 4 Octetstring type
|
||
* UNIVERSAL 5 Null type
|
||
* UNIVERSAL 6 Object identifier type
|
||
* UNIVERSAL 7 Object descriptor type
|
||
* UNIVERSAL 8 External type and Instance-of type
|
||
* UNIVERSAL 9 Real type
|
||
* UNIVERSAL 10 Enumerated type
|
||
* UNIVERSAL 11 Embedded-pdv type
|
||
* UNIVERSAL 12 UTF8String type
|
||
* UNIVERSAL 13 Relative object identifier type
|
||
* UNIVERSAL 14-15 Reserved for future editions
|
||
* UNIVERSAL 16 Sequence and Sequence-of types
|
||
* UNIVERSAL 17 Set and Set-of types
|
||
* UNIVERSAL 18-22, 25-30 Character string types
|
||
* UNIVERSAL 23-24 Time types
|
||
*
|
||
* The length of an ASN.1 structure is specified after the tag identifier.
|
||
* There is a definite form and an indefinite form. The indefinite form may
|
||
* be used if the encoding is constructed and not all immediately available.
|
||
* The indefinite form is encoded using a length byte with only the 8th bit
|
||
* set. The end of the constructed object is marked using end-of-contents
|
||
* octets (two zero bytes).
|
||
*
|
||
* The definite form looks like this:
|
||
*
|
||
* The length may take up 1 or more bytes, it depends on the length of the
|
||
* value of the ASN.1 structure. DER encoding requires that if the ASN.1
|
||
* structure has a value that has a length greater than 127, more than 1 byte
|
||
* will be used to store its length, otherwise just one byte will be used.
|
||
* This is strict.
|
||
*
|
||
* In the case that the length of the ASN.1 value is less than 127, 1 octet
|
||
* (byte) is used to store the "short form" length. The 8th bit has a value of
|
||
* 0 indicating the length is "short form" and not "long form" and bits 7-1
|
||
* give the length of the data. (The 8th bit is the left-most, most significant
|
||
* bit: also known as big endian or network format).
|
||
*
|
||
* In the case that the length of the ASN.1 value is greater than 127, 2 to
|
||
* 127 octets (bytes) are used to store the "long form" length. The first
|
||
* byte's 8th bit is set to 1 to indicate the length is "long form." Bits 7-1
|
||
* give the number of additional octets. All following octets are in base 256
|
||
* with the most significant digit first (typical big-endian binary unsigned
|
||
* integer storage). So, for instance, if the length of a value was 257, the
|
||
* first byte would be set to:
|
||
*
|
||
* 10000010 = 130 = 0x82.
|
||
*
|
||
* This indicates there are 2 octets (base 256) for the length. The second and
|
||
* third bytes (the octets just mentioned) would store the length in base 256:
|
||
*
|
||
* octet 2: 00000001 = 1 * 256^1 = 256
|
||
* octet 3: 00000001 = 1 * 256^0 = 1
|
||
* total = 257
|
||
*
|
||
* The algorithm for converting a js integer value of 257 to base-256 is:
|
||
*
|
||
* var value = 257;
|
||
* var bytes = [];
|
||
* bytes[0] = (value >>> 8) & 0xFF; // most significant byte first
|
||
* bytes[1] = value & 0xFF; // least significant byte last
|
||
*
|
||
* On the ASN.1 UNIVERSAL Object Identifier (OID) type:
|
||
*
|
||
* An OID can be written like: "value1.value2.value3...valueN"
|
||
*
|
||
* The DER encoding rules:
|
||
*
|
||
* The first byte has the value 40 * value1 + value2.
|
||
* The following bytes, if any, encode the remaining values. Each value is
|
||
* encoded in base 128, most significant digit first (big endian), with as
|
||
* few digits as possible, and the most significant bit of each byte set
|
||
* to 1 except the last in each value's encoding. For example: Given the
|
||
* OID "1.2.840.113549", its DER encoding is (remember each byte except the
|
||
* last one in each encoding is OR'd with 0x80):
|
||
*
|
||
* byte 1: 40 * 1 + 2 = 42 = 0x2A.
|
||
* bytes 2-3: 128 * 6 + 72 = 840 = 6 72 = 6 72 = 0x0648 = 0x8648
|
||
* bytes 4-6: 16384 * 6 + 128 * 119 + 13 = 6 119 13 = 0x06770D = 0x86F70D
|
||
*
|
||
* The final value is: 0x2A864886F70D.
|
||
* The full OID (including ASN.1 tag and length of 6 bytes) is:
|
||
* 0x06062A864886F70D
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* ASN.1 API */
|
||
var asn1 = forge.asn1 = forge.asn1 || {};
|
||
|
||
/**
|
||
* ASN.1 classes.
|
||
*/
|
||
asn1.Class = {
|
||
UNIVERSAL: 0x00,
|
||
APPLICATION: 0x40,
|
||
CONTEXT_SPECIFIC: 0x80,
|
||
PRIVATE: 0xC0
|
||
};
|
||
|
||
/**
|
||
* ASN.1 types. Not all types are supported by this implementation, only
|
||
* those necessary to implement a simple PKI are implemented.
|
||
*/
|
||
asn1.Type = {
|
||
NONE: 0,
|
||
BOOLEAN: 1,
|
||
INTEGER: 2,
|
||
BITSTRING: 3,
|
||
OCTETSTRING: 4,
|
||
NULL: 5,
|
||
OID: 6,
|
||
ODESC: 7,
|
||
EXTERNAL: 8,
|
||
REAL: 9,
|
||
ENUMERATED: 10,
|
||
EMBEDDED: 11,
|
||
UTF8: 12,
|
||
ROID: 13,
|
||
SEQUENCE: 16,
|
||
SET: 17,
|
||
PRINTABLESTRING: 19,
|
||
IA5STRING: 22,
|
||
UTCTIME: 23,
|
||
GENERALIZEDTIME: 24,
|
||
BMPSTRING: 30
|
||
};
|
||
|
||
/**
|
||
* Creates a new asn1 object.
|
||
*
|
||
* @param tagClass the tag class for the object.
|
||
* @param type the data type (tag number) for the object.
|
||
* @param constructed true if the asn1 object is in constructed form.
|
||
* @param value the value for the object, if it is not constructed.
|
||
*
|
||
* @return the asn1 object.
|
||
*/
|
||
asn1.create = function(tagClass, type, constructed, value) {
|
||
/* An asn1 object has a tagClass, a type, a constructed flag, and a
|
||
value. The value's type depends on the constructed flag. If
|
||
constructed, it will contain a list of other asn1 objects. If not,
|
||
it will contain the ASN.1 value as an array of bytes formatted
|
||
according to the ASN.1 data type. */
|
||
|
||
// remove undefined values
|
||
if(forge.util.isArray(value)) {
|
||
var tmp = [];
|
||
for(var i = 0; i < value.length; ++i) {
|
||
if(value[i] !== undefined) {
|
||
tmp.push(value[i]);
|
||
}
|
||
}
|
||
value = tmp;
|
||
}
|
||
|
||
return {
|
||
tagClass: tagClass,
|
||
type: type,
|
||
constructed: constructed,
|
||
composed: constructed || forge.util.isArray(value),
|
||
value: value
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Gets the length of a BER-encoded ASN.1 value.
|
||
*
|
||
* In case the length is not specified, undefined is returned.
|
||
*
|
||
* @param b the BER-encoded ASN.1 byte buffer, starting with the first
|
||
* length byte.
|
||
*
|
||
* @return the length of the BER-encoded ASN.1 value or undefined.
|
||
*/
|
||
var _getValueLength = asn1.getBerValueLength = function(b) {
|
||
// TODO: move this function and related DER/BER functions to a der.js
|
||
// file; better abstract ASN.1 away from der/ber.
|
||
var b2 = b.getByte();
|
||
if(b2 === 0x80) {
|
||
return undefined;
|
||
}
|
||
|
||
// see if the length is "short form" or "long form" (bit 8 set)
|
||
var length;
|
||
var longForm = b2 & 0x80;
|
||
if(!longForm) {
|
||
// length is just the first byte
|
||
length = b2;
|
||
} else {
|
||
// the number of bytes the length is specified in bits 7 through 1
|
||
// and each length byte is in big-endian base-256
|
||
length = b.getInt((b2 & 0x7F) << 3);
|
||
}
|
||
return length;
|
||
};
|
||
|
||
/**
|
||
* Parses an asn1 object from a byte buffer in DER format.
|
||
*
|
||
* @param bytes the byte buffer to parse from.
|
||
* @param strict true to be strict when checking value lengths, false to
|
||
* allow truncated values (default: true).
|
||
*
|
||
* @return the parsed asn1 object.
|
||
*/
|
||
asn1.fromDer = function(bytes, strict) {
|
||
if(strict === undefined) {
|
||
strict = true;
|
||
}
|
||
|
||
// wrap in buffer if needed
|
||
if(typeof bytes === 'string') {
|
||
bytes = forge.util.createBuffer(bytes);
|
||
}
|
||
|
||
// minimum length for ASN.1 DER structure is 2
|
||
if(bytes.length() < 2) {
|
||
var error = new Error('Too few bytes to parse DER.');
|
||
error.bytes = bytes.length();
|
||
throw error;
|
||
}
|
||
|
||
// get the first byte
|
||
var b1 = bytes.getByte();
|
||
|
||
// get the tag class
|
||
var tagClass = (b1 & 0xC0);
|
||
|
||
// get the type (bits 1-5)
|
||
var type = b1 & 0x1F;
|
||
|
||
// get the value length
|
||
var length = _getValueLength(bytes);
|
||
|
||
// ensure there are enough bytes to get the value
|
||
if(bytes.length() < length) {
|
||
if(strict) {
|
||
var error = new Error('Too few bytes to read ASN.1 value.');
|
||
error.detail = bytes.length() + ' < ' + length;
|
||
throw error;
|
||
}
|
||
// Note: be lenient with truncated values
|
||
length = bytes.length();
|
||
}
|
||
|
||
// prepare to get value
|
||
var value;
|
||
|
||
// constructed flag is bit 6 (32 = 0x20) of the first byte
|
||
var constructed = ((b1 & 0x20) === 0x20);
|
||
|
||
// determine if the value is composed of other ASN.1 objects (if its
|
||
// constructed it will be and if its a BITSTRING it may be)
|
||
var composed = constructed;
|
||
if(!composed && tagClass === asn1.Class.UNIVERSAL &&
|
||
type === asn1.Type.BITSTRING && length > 1) {
|
||
/* The first octet gives the number of bits by which the length of the
|
||
bit string is less than the next multiple of eight (this is called
|
||
the "number of unused bits").
|
||
|
||
The second and following octets give the value of the bit string
|
||
converted to an octet string. */
|
||
// if there are no unused bits, maybe the bitstring holds ASN.1 objs
|
||
var read = bytes.read;
|
||
var unused = bytes.getByte();
|
||
if(unused === 0) {
|
||
// if the first byte indicates UNIVERSAL or CONTEXT_SPECIFIC,
|
||
// and the length is valid, assume we've got an ASN.1 object
|
||
b1 = bytes.getByte();
|
||
var tc = (b1 & 0xC0);
|
||
if(tc === asn1.Class.UNIVERSAL || tc === asn1.Class.CONTEXT_SPECIFIC) {
|
||
try {
|
||
var len = _getValueLength(bytes);
|
||
composed = (len === length - (bytes.read - read));
|
||
if(composed) {
|
||
// adjust read/length to account for unused bits byte
|
||
++read;
|
||
--length;
|
||
}
|
||
} catch(ex) {}
|
||
}
|
||
}
|
||
// restore read pointer
|
||
bytes.read = read;
|
||
}
|
||
|
||
if(composed) {
|
||
// parse child asn1 objects from the value
|
||
value = [];
|
||
if(length === undefined) {
|
||
// asn1 object of indefinite length, read until end tag
|
||
for(;;) {
|
||
if(bytes.bytes(2) === String.fromCharCode(0, 0)) {
|
||
bytes.getBytes(2);
|
||
break;
|
||
}
|
||
value.push(asn1.fromDer(bytes, strict));
|
||
}
|
||
} else {
|
||
// parsing asn1 object of definite length
|
||
var start = bytes.length();
|
||
while(length > 0) {
|
||
value.push(asn1.fromDer(bytes, strict));
|
||
length -= start - bytes.length();
|
||
start = bytes.length();
|
||
}
|
||
}
|
||
} else {
|
||
// asn1 not composed, get raw value
|
||
// TODO: do DER to OID conversion and vice-versa in .toDer?
|
||
|
||
if(length === undefined) {
|
||
if(strict) {
|
||
throw new Error('Non-constructed ASN.1 object of indefinite length.');
|
||
}
|
||
// be lenient and use remaining bytes
|
||
length = bytes.length();
|
||
}
|
||
|
||
if(type === asn1.Type.BMPSTRING) {
|
||
value = '';
|
||
for(var i = 0; i < length; i += 2) {
|
||
value += String.fromCharCode(bytes.getInt16());
|
||
}
|
||
} else {
|
||
value = bytes.getBytes(length);
|
||
}
|
||
}
|
||
|
||
// create and return asn1 object
|
||
return asn1.create(tagClass, type, constructed, value);
|
||
};
|
||
|
||
/**
|
||
* Converts the given asn1 object to a buffer of bytes in DER format.
|
||
*
|
||
* @param asn1 the asn1 object to convert to bytes.
|
||
*
|
||
* @return the buffer of bytes.
|
||
*/
|
||
asn1.toDer = function(obj) {
|
||
var bytes = forge.util.createBuffer();
|
||
|
||
// build the first byte
|
||
var b1 = obj.tagClass | obj.type;
|
||
|
||
// for storing the ASN.1 value
|
||
var value = forge.util.createBuffer();
|
||
|
||
// if composed, use each child asn1 object's DER bytes as value
|
||
if(obj.composed) {
|
||
// turn on 6th bit (0x20 = 32) to indicate asn1 is constructed
|
||
// from other asn1 objects
|
||
if(obj.constructed) {
|
||
b1 |= 0x20;
|
||
} else {
|
||
// type is a bit string, add unused bits of 0x00
|
||
value.putByte(0x00);
|
||
}
|
||
|
||
// add all of the child DER bytes together
|
||
for(var i = 0; i < obj.value.length; ++i) {
|
||
if(obj.value[i] !== undefined) {
|
||
value.putBuffer(asn1.toDer(obj.value[i]));
|
||
}
|
||
}
|
||
} else {
|
||
// use asn1.value directly
|
||
if(obj.type === asn1.Type.BMPSTRING) {
|
||
for(var i = 0; i < obj.value.length; ++i) {
|
||
value.putInt16(obj.value.charCodeAt(i));
|
||
}
|
||
} else {
|
||
value.putBytes(obj.value);
|
||
}
|
||
}
|
||
|
||
// add tag byte
|
||
bytes.putByte(b1);
|
||
|
||
// use "short form" encoding
|
||
if(value.length() <= 127) {
|
||
// one byte describes the length
|
||
// bit 8 = 0 and bits 7-1 = length
|
||
bytes.putByte(value.length() & 0x7F);
|
||
} else {
|
||
// use "long form" encoding
|
||
// 2 to 127 bytes describe the length
|
||
// first byte: bit 8 = 1 and bits 7-1 = # of additional bytes
|
||
// other bytes: length in base 256, big-endian
|
||
var len = value.length();
|
||
var lenBytes = '';
|
||
do {
|
||
lenBytes += String.fromCharCode(len & 0xFF);
|
||
len = len >>> 8;
|
||
} while(len > 0);
|
||
|
||
// set first byte to # bytes used to store the length and turn on
|
||
// bit 8 to indicate long-form length is used
|
||
bytes.putByte(lenBytes.length | 0x80);
|
||
|
||
// concatenate length bytes in reverse since they were generated
|
||
// little endian and we need big endian
|
||
for(var i = lenBytes.length - 1; i >= 0; --i) {
|
||
bytes.putByte(lenBytes.charCodeAt(i));
|
||
}
|
||
}
|
||
|
||
// concatenate value bytes
|
||
bytes.putBuffer(value);
|
||
return bytes;
|
||
};
|
||
|
||
/**
|
||
* Converts an OID dot-separated string to a byte buffer. The byte buffer
|
||
* contains only the DER-encoded value, not any tag or length bytes.
|
||
*
|
||
* @param oid the OID dot-separated string.
|
||
*
|
||
* @return the byte buffer.
|
||
*/
|
||
asn1.oidToDer = function(oid) {
|
||
// split OID into individual values
|
||
var values = oid.split('.');
|
||
var bytes = forge.util.createBuffer();
|
||
|
||
// first byte is 40 * value1 + value2
|
||
bytes.putByte(40 * parseInt(values[0], 10) + parseInt(values[1], 10));
|
||
// other bytes are each value in base 128 with 8th bit set except for
|
||
// the last byte for each value
|
||
var last, valueBytes, value, b;
|
||
for(var i = 2; i < values.length; ++i) {
|
||
// produce value bytes in reverse because we don't know how many
|
||
// bytes it will take to store the value
|
||
last = true;
|
||
valueBytes = [];
|
||
value = parseInt(values[i], 10);
|
||
do {
|
||
b = value & 0x7F;
|
||
value = value >>> 7;
|
||
// if value is not last, then turn on 8th bit
|
||
if(!last) {
|
||
b |= 0x80;
|
||
}
|
||
valueBytes.push(b);
|
||
last = false;
|
||
} while(value > 0);
|
||
|
||
// add value bytes in reverse (needs to be in big endian)
|
||
for(var n = valueBytes.length - 1; n >= 0; --n) {
|
||
bytes.putByte(valueBytes[n]);
|
||
}
|
||
}
|
||
|
||
return bytes;
|
||
};
|
||
|
||
/**
|
||
* Converts a DER-encoded byte buffer to an OID dot-separated string. The
|
||
* byte buffer should contain only the DER-encoded value, not any tag or
|
||
* length bytes.
|
||
*
|
||
* @param bytes the byte buffer.
|
||
*
|
||
* @return the OID dot-separated string.
|
||
*/
|
||
asn1.derToOid = function(bytes) {
|
||
var oid;
|
||
|
||
// wrap in buffer if needed
|
||
if(typeof bytes === 'string') {
|
||
bytes = forge.util.createBuffer(bytes);
|
||
}
|
||
|
||
// first byte is 40 * value1 + value2
|
||
var b = bytes.getByte();
|
||
oid = Math.floor(b / 40) + '.' + (b % 40);
|
||
|
||
// other bytes are each value in base 128 with 8th bit set except for
|
||
// the last byte for each value
|
||
var value = 0;
|
||
while(bytes.length() > 0) {
|
||
b = bytes.getByte();
|
||
value = value << 7;
|
||
// not the last byte for the value
|
||
if(b & 0x80) {
|
||
value += b & 0x7F;
|
||
} else {
|
||
// last byte
|
||
oid += '.' + (value + b);
|
||
value = 0;
|
||
}
|
||
}
|
||
|
||
return oid;
|
||
};
|
||
|
||
/**
|
||
* Converts a UTCTime value to a date.
|
||
*
|
||
* Note: GeneralizedTime has 4 digits for the year and is used for X.509
|
||
* dates passed 2049. Parsing that structure hasn't been implemented yet.
|
||
*
|
||
* @param utc the UTCTime value to convert.
|
||
*
|
||
* @return the date.
|
||
*/
|
||
asn1.utcTimeToDate = function(utc) {
|
||
/* The following formats can be used:
|
||
|
||
YYMMDDhhmmZ
|
||
YYMMDDhhmm+hh'mm'
|
||
YYMMDDhhmm-hh'mm'
|
||
YYMMDDhhmmssZ
|
||
YYMMDDhhmmss+hh'mm'
|
||
YYMMDDhhmmss-hh'mm'
|
||
|
||
Where:
|
||
|
||
YY is the least significant two digits of the year
|
||
MM is the month (01 to 12)
|
||
DD is the day (01 to 31)
|
||
hh is the hour (00 to 23)
|
||
mm are the minutes (00 to 59)
|
||
ss are the seconds (00 to 59)
|
||
Z indicates that local time is GMT, + indicates that local time is
|
||
later than GMT, and - indicates that local time is earlier than GMT
|
||
hh' is the absolute value of the offset from GMT in hours
|
||
mm' is the absolute value of the offset from GMT in minutes */
|
||
var date = new Date();
|
||
|
||
// if YY >= 50 use 19xx, if YY < 50 use 20xx
|
||
var year = parseInt(utc.substr(0, 2), 10);
|
||
year = (year >= 50) ? 1900 + year : 2000 + year;
|
||
var MM = parseInt(utc.substr(2, 2), 10) - 1; // use 0-11 for month
|
||
var DD = parseInt(utc.substr(4, 2), 10);
|
||
var hh = parseInt(utc.substr(6, 2), 10);
|
||
var mm = parseInt(utc.substr(8, 2), 10);
|
||
var ss = 0;
|
||
|
||
// not just YYMMDDhhmmZ
|
||
if(utc.length > 11) {
|
||
// get character after minutes
|
||
var c = utc.charAt(10);
|
||
var end = 10;
|
||
|
||
// see if seconds are present
|
||
if(c !== '+' && c !== '-') {
|
||
// get seconds
|
||
ss = parseInt(utc.substr(10, 2), 10);
|
||
end += 2;
|
||
}
|
||
}
|
||
|
||
// update date
|
||
date.setUTCFullYear(year, MM, DD);
|
||
date.setUTCHours(hh, mm, ss, 0);
|
||
|
||
if(end) {
|
||
// get +/- after end of time
|
||
c = utc.charAt(end);
|
||
if(c === '+' || c === '-') {
|
||
// get hours+minutes offset
|
||
var hhoffset = parseInt(utc.substr(end + 1, 2), 10);
|
||
var mmoffset = parseInt(utc.substr(end + 4, 2), 10);
|
||
|
||
// calculate offset in milliseconds
|
||
var offset = hhoffset * 60 + mmoffset;
|
||
offset *= 60000;
|
||
|
||
// apply offset
|
||
if(c === '+') {
|
||
date.setTime(+date - offset);
|
||
} else {
|
||
date.setTime(+date + offset);
|
||
}
|
||
}
|
||
}
|
||
|
||
return date;
|
||
};
|
||
|
||
/**
|
||
* Converts a GeneralizedTime value to a date.
|
||
*
|
||
* @param gentime the GeneralizedTime value to convert.
|
||
*
|
||
* @return the date.
|
||
*/
|
||
asn1.generalizedTimeToDate = function(gentime) {
|
||
/* The following formats can be used:
|
||
|
||
YYYYMMDDHHMMSS
|
||
YYYYMMDDHHMMSS.fff
|
||
YYYYMMDDHHMMSSZ
|
||
YYYYMMDDHHMMSS.fffZ
|
||
YYYYMMDDHHMMSS+hh'mm'
|
||
YYYYMMDDHHMMSS.fff+hh'mm'
|
||
YYYYMMDDHHMMSS-hh'mm'
|
||
YYYYMMDDHHMMSS.fff-hh'mm'
|
||
|
||
Where:
|
||
|
||
YYYY is the year
|
||
MM is the month (01 to 12)
|
||
DD is the day (01 to 31)
|
||
hh is the hour (00 to 23)
|
||
mm are the minutes (00 to 59)
|
||
ss are the seconds (00 to 59)
|
||
.fff is the second fraction, accurate to three decimal places
|
||
Z indicates that local time is GMT, + indicates that local time is
|
||
later than GMT, and - indicates that local time is earlier than GMT
|
||
hh' is the absolute value of the offset from GMT in hours
|
||
mm' is the absolute value of the offset from GMT in minutes */
|
||
var date = new Date();
|
||
|
||
var YYYY = parseInt(gentime.substr(0, 4), 10);
|
||
var MM = parseInt(gentime.substr(4, 2), 10) - 1; // use 0-11 for month
|
||
var DD = parseInt(gentime.substr(6, 2), 10);
|
||
var hh = parseInt(gentime.substr(8, 2), 10);
|
||
var mm = parseInt(gentime.substr(10, 2), 10);
|
||
var ss = parseInt(gentime.substr(12, 2), 10);
|
||
var fff = 0;
|
||
var offset = 0;
|
||
var isUTC = false;
|
||
|
||
if(gentime.charAt(gentime.length - 1) === 'Z') {
|
||
isUTC = true;
|
||
}
|
||
|
||
var end = gentime.length - 5, c = gentime.charAt(end);
|
||
if(c === '+' || c === '-') {
|
||
// get hours+minutes offset
|
||
var hhoffset = parseInt(gentime.substr(end + 1, 2), 10);
|
||
var mmoffset = parseInt(gentime.substr(end + 4, 2), 10);
|
||
|
||
// calculate offset in milliseconds
|
||
offset = hhoffset * 60 + mmoffset;
|
||
offset *= 60000;
|
||
|
||
// apply offset
|
||
if(c === '+') {
|
||
offset *= -1;
|
||
}
|
||
|
||
isUTC = true;
|
||
}
|
||
|
||
// check for second fraction
|
||
if(gentime.charAt(14) === '.') {
|
||
fff = parseFloat(gentime.substr(14), 10) * 1000;
|
||
}
|
||
|
||
if(isUTC) {
|
||
date.setUTCFullYear(YYYY, MM, DD);
|
||
date.setUTCHours(hh, mm, ss, fff);
|
||
|
||
// apply offset
|
||
date.setTime(+date + offset);
|
||
} else {
|
||
date.setFullYear(YYYY, MM, DD);
|
||
date.setHours(hh, mm, ss, fff);
|
||
}
|
||
|
||
return date;
|
||
};
|
||
|
||
/**
|
||
* Converts a date to a UTCTime value.
|
||
*
|
||
* Note: GeneralizedTime has 4 digits for the year and is used for X.509
|
||
* dates passed 2049. Converting to a GeneralizedTime hasn't been
|
||
* implemented yet.
|
||
*
|
||
* @param date the date to convert.
|
||
*
|
||
* @return the UTCTime value.
|
||
*/
|
||
asn1.dateToUtcTime = function(date) {
|
||
// TODO: validate; currently assumes proper format
|
||
if(typeof date === 'string') {
|
||
return date;
|
||
}
|
||
|
||
var rval = '';
|
||
|
||
// create format YYMMDDhhmmssZ
|
||
var format = [];
|
||
format.push(('' + date.getUTCFullYear()).substr(2));
|
||
format.push('' + (date.getUTCMonth() + 1));
|
||
format.push('' + date.getUTCDate());
|
||
format.push('' + date.getUTCHours());
|
||
format.push('' + date.getUTCMinutes());
|
||
format.push('' + date.getUTCSeconds());
|
||
|
||
// ensure 2 digits are used for each format entry
|
||
for(var i = 0; i < format.length; ++i) {
|
||
if(format[i].length < 2) {
|
||
rval += '0';
|
||
}
|
||
rval += format[i];
|
||
}
|
||
rval += 'Z';
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts a date to a GeneralizedTime value.
|
||
*
|
||
* @param date the date to convert.
|
||
*
|
||
* @return the GeneralizedTime value as a string.
|
||
*/
|
||
asn1.dateToGeneralizedTime = function(date) {
|
||
// TODO: validate; currently assumes proper format
|
||
if(typeof date === 'string') {
|
||
return date;
|
||
}
|
||
|
||
var rval = '';
|
||
|
||
// create format YYYYMMDDHHMMSSZ
|
||
var format = [];
|
||
format.push('' + date.getUTCFullYear());
|
||
format.push('' + (date.getUTCMonth() + 1));
|
||
format.push('' + date.getUTCDate());
|
||
format.push('' + date.getUTCHours());
|
||
format.push('' + date.getUTCMinutes());
|
||
format.push('' + date.getUTCSeconds());
|
||
|
||
// ensure 2 digits are used for each format entry
|
||
for(var i = 0; i < format.length; ++i) {
|
||
if(format[i].length < 2) {
|
||
rval += '0';
|
||
}
|
||
rval += format[i];
|
||
}
|
||
rval += 'Z';
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts a javascript integer to a DER-encoded byte buffer to be used
|
||
* as the value for an INTEGER type.
|
||
*
|
||
* @param x the integer.
|
||
*
|
||
* @return the byte buffer.
|
||
*/
|
||
asn1.integerToDer = function(x) {
|
||
var rval = forge.util.createBuffer();
|
||
if(x >= -0x80 && x < 0x80) {
|
||
return rval.putSignedInt(x, 8);
|
||
}
|
||
if(x >= -0x8000 && x < 0x8000) {
|
||
return rval.putSignedInt(x, 16);
|
||
}
|
||
if(x >= -0x800000 && x < 0x800000) {
|
||
return rval.putSignedInt(x, 24);
|
||
}
|
||
if(x >= -0x80000000 && x < 0x80000000) {
|
||
return rval.putSignedInt(x, 32);
|
||
}
|
||
var error = new Error('Integer too large; max is 32-bits.');
|
||
error.integer = x;
|
||
throw error;
|
||
};
|
||
|
||
/**
|
||
* Converts a DER-encoded byte buffer to a javascript integer. This is
|
||
* typically used to decode the value of an INTEGER type.
|
||
*
|
||
* @param bytes the byte buffer.
|
||
*
|
||
* @return the integer.
|
||
*/
|
||
asn1.derToInteger = function(bytes) {
|
||
// wrap in buffer if needed
|
||
if(typeof bytes === 'string') {
|
||
bytes = forge.util.createBuffer(bytes);
|
||
}
|
||
|
||
var n = bytes.length() * 8;
|
||
if(n > 32) {
|
||
throw new Error('Integer too large; max is 32-bits.');
|
||
}
|
||
return bytes.getSignedInt(n);
|
||
};
|
||
|
||
/**
|
||
* Validates the that given ASN.1 object is at least a super set of the
|
||
* given ASN.1 structure. Only tag classes and types are checked. An
|
||
* optional map may also be provided to capture ASN.1 values while the
|
||
* structure is checked.
|
||
*
|
||
* To capture an ASN.1 value, set an object in the validator's 'capture'
|
||
* parameter to the key to use in the capture map. To capture the full
|
||
* ASN.1 object, specify 'captureAsn1'.
|
||
*
|
||
* Objects in the validator may set a field 'optional' to true to indicate
|
||
* that it isn't necessary to pass validation.
|
||
*
|
||
* @param obj the ASN.1 object to validate.
|
||
* @param v the ASN.1 structure validator.
|
||
* @param capture an optional map to capture values in.
|
||
* @param errors an optional array for storing validation errors.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
asn1.validate = function(obj, v, capture, errors) {
|
||
var rval = false;
|
||
|
||
// ensure tag class and type are the same if specified
|
||
if((obj.tagClass === v.tagClass || typeof(v.tagClass) === 'undefined') &&
|
||
(obj.type === v.type || typeof(v.type) === 'undefined')) {
|
||
// ensure constructed flag is the same if specified
|
||
if(obj.constructed === v.constructed ||
|
||
typeof(v.constructed) === 'undefined') {
|
||
rval = true;
|
||
|
||
// handle sub values
|
||
if(v.value && forge.util.isArray(v.value)) {
|
||
var j = 0;
|
||
for(var i = 0; rval && i < v.value.length; ++i) {
|
||
rval = v.value[i].optional || false;
|
||
if(obj.value[j]) {
|
||
rval = asn1.validate(obj.value[j], v.value[i], capture, errors);
|
||
if(rval) {
|
||
++j;
|
||
} else if(v.value[i].optional) {
|
||
rval = true;
|
||
}
|
||
}
|
||
if(!rval && errors) {
|
||
errors.push(
|
||
'[' + v.name + '] ' +
|
||
'Tag class "' + v.tagClass + '", type "' +
|
||
v.type + '" expected value length "' +
|
||
v.value.length + '", got "' +
|
||
obj.value.length + '"');
|
||
}
|
||
}
|
||
}
|
||
|
||
if(rval && capture) {
|
||
if(v.capture) {
|
||
capture[v.capture] = obj.value;
|
||
}
|
||
if(v.captureAsn1) {
|
||
capture[v.captureAsn1] = obj;
|
||
}
|
||
}
|
||
} else if(errors) {
|
||
errors.push(
|
||
'[' + v.name + '] ' +
|
||
'Expected constructed "' + v.constructed + '", got "' +
|
||
obj.constructed + '"');
|
||
}
|
||
} else if(errors) {
|
||
if(obj.tagClass !== v.tagClass) {
|
||
errors.push(
|
||
'[' + v.name + '] ' +
|
||
'Expected tag class "' + v.tagClass + '", got "' +
|
||
obj.tagClass + '"');
|
||
}
|
||
if(obj.type !== v.type) {
|
||
errors.push(
|
||
'[' + v.name + '] ' +
|
||
'Expected type "' + v.type + '", got "' + obj.type + '"');
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
// regex for testing for non-latin characters
|
||
var _nonLatinRegex = /[^\\u0000-\\u00ff]/;
|
||
|
||
/**
|
||
* Pretty prints an ASN.1 object to a string.
|
||
*
|
||
* @param obj the object to write out.
|
||
* @param level the level in the tree.
|
||
* @param indentation the indentation to use.
|
||
*
|
||
* @return the string.
|
||
*/
|
||
asn1.prettyPrint = function(obj, level, indentation) {
|
||
var rval = '';
|
||
|
||
// set default level and indentation
|
||
level = level || 0;
|
||
indentation = indentation || 2;
|
||
|
||
// start new line for deep levels
|
||
if(level > 0) {
|
||
rval += '\n';
|
||
}
|
||
|
||
// create indent
|
||
var indent = '';
|
||
for(var i = 0; i < level * indentation; ++i) {
|
||
indent += ' ';
|
||
}
|
||
|
||
// print class:type
|
||
rval += indent + 'Tag: ';
|
||
switch(obj.tagClass) {
|
||
case asn1.Class.UNIVERSAL:
|
||
rval += 'Universal:';
|
||
break;
|
||
case asn1.Class.APPLICATION:
|
||
rval += 'Application:';
|
||
break;
|
||
case asn1.Class.CONTEXT_SPECIFIC:
|
||
rval += 'Context-Specific:';
|
||
break;
|
||
case asn1.Class.PRIVATE:
|
||
rval += 'Private:';
|
||
break;
|
||
}
|
||
|
||
if(obj.tagClass === asn1.Class.UNIVERSAL) {
|
||
rval += obj.type;
|
||
|
||
// known types
|
||
switch(obj.type) {
|
||
case asn1.Type.NONE:
|
||
rval += ' (None)';
|
||
break;
|
||
case asn1.Type.BOOLEAN:
|
||
rval += ' (Boolean)';
|
||
break;
|
||
case asn1.Type.BITSTRING:
|
||
rval += ' (Bit string)';
|
||
break;
|
||
case asn1.Type.INTEGER:
|
||
rval += ' (Integer)';
|
||
break;
|
||
case asn1.Type.OCTETSTRING:
|
||
rval += ' (Octet string)';
|
||
break;
|
||
case asn1.Type.NULL:
|
||
rval += ' (Null)';
|
||
break;
|
||
case asn1.Type.OID:
|
||
rval += ' (Object Identifier)';
|
||
break;
|
||
case asn1.Type.ODESC:
|
||
rval += ' (Object Descriptor)';
|
||
break;
|
||
case asn1.Type.EXTERNAL:
|
||
rval += ' (External or Instance of)';
|
||
break;
|
||
case asn1.Type.REAL:
|
||
rval += ' (Real)';
|
||
break;
|
||
case asn1.Type.ENUMERATED:
|
||
rval += ' (Enumerated)';
|
||
break;
|
||
case asn1.Type.EMBEDDED:
|
||
rval += ' (Embedded PDV)';
|
||
break;
|
||
case asn1.Type.UTF8:
|
||
rval += ' (UTF8)';
|
||
break;
|
||
case asn1.Type.ROID:
|
||
rval += ' (Relative Object Identifier)';
|
||
break;
|
||
case asn1.Type.SEQUENCE:
|
||
rval += ' (Sequence)';
|
||
break;
|
||
case asn1.Type.SET:
|
||
rval += ' (Set)';
|
||
break;
|
||
case asn1.Type.PRINTABLESTRING:
|
||
rval += ' (Printable String)';
|
||
break;
|
||
case asn1.Type.IA5String:
|
||
rval += ' (IA5String (ASCII))';
|
||
break;
|
||
case asn1.Type.UTCTIME:
|
||
rval += ' (UTC time)';
|
||
break;
|
||
case asn1.Type.GENERALIZEDTIME:
|
||
rval += ' (Generalized time)';
|
||
break;
|
||
case asn1.Type.BMPSTRING:
|
||
rval += ' (BMP String)';
|
||
break;
|
||
}
|
||
} else {
|
||
rval += obj.type;
|
||
}
|
||
|
||
rval += '\n';
|
||
rval += indent + 'Constructed: ' + obj.constructed + '\n';
|
||
|
||
if(obj.composed) {
|
||
var subvalues = 0;
|
||
var sub = '';
|
||
for(var i = 0; i < obj.value.length; ++i) {
|
||
if(obj.value[i] !== undefined) {
|
||
subvalues += 1;
|
||
sub += asn1.prettyPrint(obj.value[i], level + 1, indentation);
|
||
if((i + 1) < obj.value.length) {
|
||
sub += ',';
|
||
}
|
||
}
|
||
}
|
||
rval += indent + 'Sub values: ' + subvalues + sub;
|
||
} else {
|
||
rval += indent + 'Value: ';
|
||
if(obj.type === asn1.Type.OID) {
|
||
var oid = asn1.derToOid(obj.value);
|
||
rval += oid;
|
||
if(forge.pki && forge.pki.oids) {
|
||
if(oid in forge.pki.oids) {
|
||
rval += ' (' + forge.pki.oids[oid] + ') ';
|
||
}
|
||
}
|
||
}
|
||
if(obj.type === asn1.Type.INTEGER) {
|
||
try {
|
||
rval += asn1.derToInteger(obj.value);
|
||
} catch(ex) {
|
||
rval += '0x' + forge.util.bytesToHex(obj.value);
|
||
}
|
||
} else if(obj.type === asn1.Type.OCTETSTRING) {
|
||
if(!_nonLatinRegex.test(obj.value)) {
|
||
rval += '(' + obj.value + ') ';
|
||
}
|
||
rval += '0x' + forge.util.bytesToHex(obj.value);
|
||
} else if(obj.type === asn1.Type.UTF8) {
|
||
rval += forge.util.decodeUtf8(obj.value);
|
||
} else if(obj.type === asn1.Type.PRINTABLESTRING ||
|
||
obj.type === asn1.Type.IA5String) {
|
||
rval += obj.value;
|
||
} else if(_nonLatinRegex.test(obj.value)) {
|
||
rval += '0x' + forge.util.bytesToHex(obj.value);
|
||
} else if(obj.value.length === 0) {
|
||
rval += '[null]';
|
||
} else {
|
||
rval += obj.value;
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'asn1';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/asn1',['require', 'module', './util', './oids'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Message Digest Algorithm 5 with 128-bit digest (MD5) implementation.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var md5 = forge.md5 = forge.md5 || {};
|
||
forge.md = forge.md || {};
|
||
forge.md.algorithms = forge.md.algorithms || {};
|
||
forge.md.md5 = forge.md.algorithms.md5 = md5;
|
||
|
||
/**
|
||
* Creates an MD5 message digest object.
|
||
*
|
||
* @return a message digest object.
|
||
*/
|
||
md5.create = function() {
|
||
// do initialization as necessary
|
||
if(!_initialized) {
|
||
_init();
|
||
}
|
||
|
||
// MD5 state contains four 32-bit integers
|
||
var _state = null;
|
||
|
||
// input buffer
|
||
var _input = forge.util.createBuffer();
|
||
|
||
// used for word storage
|
||
var _w = new Array(16);
|
||
|
||
// message digest object
|
||
var md = {
|
||
algorithm: 'md5',
|
||
blockLength: 64,
|
||
digestLength: 16,
|
||
// 56-bit length of message so far (does not including padding)
|
||
messageLength: 0,
|
||
// true message length
|
||
fullMessageLength: null,
|
||
// size of message length in bytes
|
||
messageLengthSize: 8
|
||
};
|
||
|
||
/**
|
||
* Starts the digest.
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.start = function() {
|
||
// up to 56-bit message length for convenience
|
||
md.messageLength = 0;
|
||
|
||
// full message length (set md.messageLength64 for backwards-compatibility)
|
||
md.fullMessageLength = md.messageLength64 = [];
|
||
var int32s = md.messageLengthSize / 4;
|
||
for(var i = 0; i < int32s; ++i) {
|
||
md.fullMessageLength.push(0);
|
||
}
|
||
_input = forge.util.createBuffer();
|
||
_state = {
|
||
h0: 0x67452301,
|
||
h1: 0xEFCDAB89,
|
||
h2: 0x98BADCFE,
|
||
h3: 0x10325476
|
||
};
|
||
return md;
|
||
};
|
||
// start digest automatically for first time
|
||
md.start();
|
||
|
||
/**
|
||
* Updates the digest with the given message input. The given input can
|
||
* treated as raw input (no encoding will be applied) or an encoding of
|
||
* 'utf8' maybe given to encode the input using UTF-8.
|
||
*
|
||
* @param msg the message input to update with.
|
||
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.update = function(msg, encoding) {
|
||
if(encoding === 'utf8') {
|
||
msg = forge.util.encodeUtf8(msg);
|
||
}
|
||
|
||
// update message length
|
||
var len = msg.length;
|
||
md.messageLength += len;
|
||
len = [(len / 0x100000000) >>> 0, len >>> 0];
|
||
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
|
||
md.fullMessageLength[i] += len[1];
|
||
len[1] = len[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0);
|
||
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0;
|
||
len[0] = ((len[1] / 0x100000000) >>> 0);
|
||
}
|
||
|
||
// add bytes to input buffer
|
||
_input.putBytes(msg);
|
||
|
||
// process bytes
|
||
_update(_state, _w, _input);
|
||
|
||
// compact input buffer every 2K or if empty
|
||
if(_input.read > 2048 || _input.length() === 0) {
|
||
_input.compact();
|
||
}
|
||
|
||
return md;
|
||
};
|
||
|
||
/**
|
||
* Produces the digest.
|
||
*
|
||
* @return a byte buffer containing the digest value.
|
||
*/
|
||
md.digest = function() {
|
||
/* Note: Here we copy the remaining bytes in the input buffer and
|
||
add the appropriate MD5 padding. Then we do the final update
|
||
on a copy of the state so that if the user wants to get
|
||
intermediate digests they can do so. */
|
||
|
||
/* Determine the number of bytes that must be added to the message
|
||
to ensure its length is congruent to 448 mod 512. In other words,
|
||
the data to be digested must be a multiple of 512 bits (or 128 bytes).
|
||
This data includes the message, some padding, and the length of the
|
||
message. Since the length of the message will be encoded as 8 bytes (64
|
||
bits), that means that the last segment of the data must have 56 bytes
|
||
(448 bits) of message and padding. Therefore, the length of the message
|
||
plus the padding must be congruent to 448 mod 512 because
|
||
512 - 128 = 448.
|
||
|
||
In order to fill up the message length it must be filled with
|
||
padding that begins with 1 bit followed by all 0 bits. Padding
|
||
must *always* be present, so if the message length is already
|
||
congruent to 448 mod 512, then 512 padding bits must be added. */
|
||
|
||
var finalBlock = forge.util.createBuffer();
|
||
finalBlock.putBytes(_input.bytes());
|
||
|
||
// compute remaining size to be digested (include message length size)
|
||
var remaining = (
|
||
md.fullMessageLength[md.fullMessageLength.length - 1] +
|
||
md.messageLengthSize);
|
||
|
||
// add padding for overflow blockSize - overflow
|
||
// _padding starts with 1 byte with first bit is set (byte value 128), then
|
||
// there may be up to (blockSize - 1) other pad bytes
|
||
var overflow = remaining & (md.blockLength - 1);
|
||
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow));
|
||
|
||
// serialize message length in bits in little-endian order; since length
|
||
// is stored in bytes we multiply by 8 and add carry
|
||
var bits, carry = 0;
|
||
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
|
||
bits = md.fullMessageLength[i] * 8 + carry;
|
||
carry = (bits / 0x100000000) >>> 0;
|
||
finalBlock.putInt32Le(bits >>> 0);
|
||
}
|
||
|
||
var s2 = {
|
||
h0: _state.h0,
|
||
h1: _state.h1,
|
||
h2: _state.h2,
|
||
h3: _state.h3
|
||
};
|
||
_update(s2, _w, finalBlock);
|
||
var rval = forge.util.createBuffer();
|
||
rval.putInt32Le(s2.h0);
|
||
rval.putInt32Le(s2.h1);
|
||
rval.putInt32Le(s2.h2);
|
||
rval.putInt32Le(s2.h3);
|
||
return rval;
|
||
};
|
||
|
||
return md;
|
||
};
|
||
|
||
// padding, constant tables for calculating md5
|
||
var _padding = null;
|
||
var _g = null;
|
||
var _r = null;
|
||
var _k = null;
|
||
var _initialized = false;
|
||
|
||
/**
|
||
* Initializes the constant tables.
|
||
*/
|
||
function _init() {
|
||
// create padding
|
||
_padding = String.fromCharCode(128);
|
||
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
|
||
|
||
// g values
|
||
_g = [
|
||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||
1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12,
|
||
5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2,
|
||
0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9];
|
||
|
||
// rounds table
|
||
_r = [
|
||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21];
|
||
|
||
// get the result of abs(sin(i + 1)) as a 32-bit integer
|
||
_k = new Array(64);
|
||
for(var i = 0; i < 64; ++i) {
|
||
_k[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 0x100000000);
|
||
}
|
||
|
||
// now initialized
|
||
_initialized = true;
|
||
}
|
||
|
||
/**
|
||
* Updates an MD5 state with the given byte buffer.
|
||
*
|
||
* @param s the MD5 state to update.
|
||
* @param w the array to use to store words.
|
||
* @param bytes the byte buffer to update with.
|
||
*/
|
||
function _update(s, w, bytes) {
|
||
// consume 512 bit (64 byte) chunks
|
||
var t, a, b, c, d, f, r, i;
|
||
var len = bytes.length();
|
||
while(len >= 64) {
|
||
// initialize hash value for this chunk
|
||
a = s.h0;
|
||
b = s.h1;
|
||
c = s.h2;
|
||
d = s.h3;
|
||
|
||
// round 1
|
||
for(i = 0; i < 16; ++i) {
|
||
w[i] = bytes.getInt32Le();
|
||
f = d ^ (b & (c ^ d));
|
||
t = (a + f + _k[i] + w[i]);
|
||
r = _r[i];
|
||
a = d;
|
||
d = c;
|
||
c = b;
|
||
b += (t << r) | (t >>> (32 - r));
|
||
}
|
||
// round 2
|
||
for(; i < 32; ++i) {
|
||
f = c ^ (d & (b ^ c));
|
||
t = (a + f + _k[i] + w[_g[i]]);
|
||
r = _r[i];
|
||
a = d;
|
||
d = c;
|
||
c = b;
|
||
b += (t << r) | (t >>> (32 - r));
|
||
}
|
||
// round 3
|
||
for(; i < 48; ++i) {
|
||
f = b ^ c ^ d;
|
||
t = (a + f + _k[i] + w[_g[i]]);
|
||
r = _r[i];
|
||
a = d;
|
||
d = c;
|
||
c = b;
|
||
b += (t << r) | (t >>> (32 - r));
|
||
}
|
||
// round 4
|
||
for(; i < 64; ++i) {
|
||
f = c ^ (b | ~d);
|
||
t = (a + f + _k[i] + w[_g[i]]);
|
||
r = _r[i];
|
||
a = d;
|
||
d = c;
|
||
c = b;
|
||
b += (t << r) | (t >>> (32 - r));
|
||
}
|
||
|
||
// update hash state
|
||
s.h0 = (s.h0 + a) | 0;
|
||
s.h1 = (s.h1 + b) | 0;
|
||
s.h2 = (s.h2 + c) | 0;
|
||
s.h3 = (s.h3 + d) | 0;
|
||
|
||
len -= 64;
|
||
}
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'md5';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/md5',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Secure Hash Algorithm with 160-bit digest (SHA-1) implementation.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2015 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var sha1 = forge.sha1 = forge.sha1 || {};
|
||
forge.md = forge.md || {};
|
||
forge.md.algorithms = forge.md.algorithms || {};
|
||
forge.md.sha1 = forge.md.algorithms.sha1 = sha1;
|
||
|
||
/**
|
||
* Creates a SHA-1 message digest object.
|
||
*
|
||
* @return a message digest object.
|
||
*/
|
||
sha1.create = function() {
|
||
// do initialization as necessary
|
||
if(!_initialized) {
|
||
_init();
|
||
}
|
||
|
||
// SHA-1 state contains five 32-bit integers
|
||
var _state = null;
|
||
|
||
// input buffer
|
||
var _input = forge.util.createBuffer();
|
||
|
||
// used for word storage
|
||
var _w = new Array(80);
|
||
|
||
// message digest object
|
||
var md = {
|
||
algorithm: 'sha1',
|
||
blockLength: 64,
|
||
digestLength: 20,
|
||
// 56-bit length of message so far (does not including padding)
|
||
messageLength: 0,
|
||
// true message length
|
||
fullMessageLength: null,
|
||
// size of message length in bytes
|
||
messageLengthSize: 8
|
||
};
|
||
|
||
/**
|
||
* Starts the digest.
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.start = function() {
|
||
// up to 56-bit message length for convenience
|
||
md.messageLength = 0;
|
||
|
||
// full message length (set md.messageLength64 for backwards-compatibility)
|
||
md.fullMessageLength = md.messageLength64 = [];
|
||
var int32s = md.messageLengthSize / 4;
|
||
for(var i = 0; i < int32s; ++i) {
|
||
md.fullMessageLength.push(0);
|
||
}
|
||
_input = forge.util.createBuffer();
|
||
_state = {
|
||
h0: 0x67452301,
|
||
h1: 0xEFCDAB89,
|
||
h2: 0x98BADCFE,
|
||
h3: 0x10325476,
|
||
h4: 0xC3D2E1F0
|
||
};
|
||
return md;
|
||
};
|
||
// start digest automatically for first time
|
||
md.start();
|
||
|
||
/**
|
||
* Updates the digest with the given message input. The given input can
|
||
* treated as raw input (no encoding will be applied) or an encoding of
|
||
* 'utf8' maybe given to encode the input using UTF-8.
|
||
*
|
||
* @param msg the message input to update with.
|
||
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.update = function(msg, encoding) {
|
||
if(encoding === 'utf8') {
|
||
msg = forge.util.encodeUtf8(msg);
|
||
}
|
||
|
||
// update message length
|
||
var len = msg.length;
|
||
md.messageLength += len;
|
||
len = [(len / 0x100000000) >>> 0, len >>> 0];
|
||
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
|
||
md.fullMessageLength[i] += len[1];
|
||
len[1] = len[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0);
|
||
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0;
|
||
len[0] = ((len[1] / 0x100000000) >>> 0);
|
||
}
|
||
|
||
// add bytes to input buffer
|
||
_input.putBytes(msg);
|
||
|
||
// process bytes
|
||
_update(_state, _w, _input);
|
||
|
||
// compact input buffer every 2K or if empty
|
||
if(_input.read > 2048 || _input.length() === 0) {
|
||
_input.compact();
|
||
}
|
||
|
||
return md;
|
||
};
|
||
|
||
/**
|
||
* Produces the digest.
|
||
*
|
||
* @return a byte buffer containing the digest value.
|
||
*/
|
||
md.digest = function() {
|
||
/* Note: Here we copy the remaining bytes in the input buffer and
|
||
add the appropriate SHA-1 padding. Then we do the final update
|
||
on a copy of the state so that if the user wants to get
|
||
intermediate digests they can do so. */
|
||
|
||
/* Determine the number of bytes that must be added to the message
|
||
to ensure its length is congruent to 448 mod 512. In other words,
|
||
the data to be digested must be a multiple of 512 bits (or 128 bytes).
|
||
This data includes the message, some padding, and the length of the
|
||
message. Since the length of the message will be encoded as 8 bytes (64
|
||
bits), that means that the last segment of the data must have 56 bytes
|
||
(448 bits) of message and padding. Therefore, the length of the message
|
||
plus the padding must be congruent to 448 mod 512 because
|
||
512 - 128 = 448.
|
||
|
||
In order to fill up the message length it must be filled with
|
||
padding that begins with 1 bit followed by all 0 bits. Padding
|
||
must *always* be present, so if the message length is already
|
||
congruent to 448 mod 512, then 512 padding bits must be added. */
|
||
|
||
var finalBlock = forge.util.createBuffer();
|
||
finalBlock.putBytes(_input.bytes());
|
||
|
||
// compute remaining size to be digested (include message length size)
|
||
var remaining = (
|
||
md.fullMessageLength[md.fullMessageLength.length - 1] +
|
||
md.messageLengthSize);
|
||
|
||
// add padding for overflow blockSize - overflow
|
||
// _padding starts with 1 byte with first bit is set (byte value 128), then
|
||
// there may be up to (blockSize - 1) other pad bytes
|
||
var overflow = remaining & (md.blockLength - 1);
|
||
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow));
|
||
|
||
// serialize message length in bits in big-endian order; since length
|
||
// is stored in bytes we multiply by 8 and add carry from next int
|
||
var messageLength = forge.util.createBuffer();
|
||
var next, carry;
|
||
var bits = md.fullMessageLength[0] * 8;
|
||
for(var i = 0; i < md.fullMessageLength.length; ++i) {
|
||
next = md.fullMessageLength[i + 1] * 8;
|
||
carry = (next / 0x100000000) >>> 0;
|
||
bits += carry;
|
||
finalBlock.putInt32(bits >>> 0);
|
||
bits = next;
|
||
}
|
||
|
||
var s2 = {
|
||
h0: _state.h0,
|
||
h1: _state.h1,
|
||
h2: _state.h2,
|
||
h3: _state.h3,
|
||
h4: _state.h4
|
||
};
|
||
_update(s2, _w, finalBlock);
|
||
var rval = forge.util.createBuffer();
|
||
rval.putInt32(s2.h0);
|
||
rval.putInt32(s2.h1);
|
||
rval.putInt32(s2.h2);
|
||
rval.putInt32(s2.h3);
|
||
rval.putInt32(s2.h4);
|
||
return rval;
|
||
};
|
||
|
||
return md;
|
||
};
|
||
|
||
// sha-1 padding bytes not initialized yet
|
||
var _padding = null;
|
||
var _initialized = false;
|
||
|
||
/**
|
||
* Initializes the constant tables.
|
||
*/
|
||
function _init() {
|
||
// create padding
|
||
_padding = String.fromCharCode(128);
|
||
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
|
||
|
||
// now initialized
|
||
_initialized = true;
|
||
}
|
||
|
||
/**
|
||
* Updates a SHA-1 state with the given byte buffer.
|
||
*
|
||
* @param s the SHA-1 state to update.
|
||
* @param w the array to use to store words.
|
||
* @param bytes the byte buffer to update with.
|
||
*/
|
||
function _update(s, w, bytes) {
|
||
// consume 512 bit (64 byte) chunks
|
||
var t, a, b, c, d, e, f, i;
|
||
var len = bytes.length();
|
||
while(len >= 64) {
|
||
// the w array will be populated with sixteen 32-bit big-endian words
|
||
// and then extended into 80 32-bit words according to SHA-1 algorithm
|
||
// and for 32-79 using Max Locktyukhin's optimization
|
||
|
||
// initialize hash value for this chunk
|
||
a = s.h0;
|
||
b = s.h1;
|
||
c = s.h2;
|
||
d = s.h3;
|
||
e = s.h4;
|
||
|
||
// round 1
|
||
for(i = 0; i < 16; ++i) {
|
||
t = bytes.getInt32();
|
||
w[i] = t;
|
||
f = d ^ (b & (c ^ d));
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
for(; i < 20; ++i) {
|
||
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
|
||
t = (t << 1) | (t >>> 31);
|
||
w[i] = t;
|
||
f = d ^ (b & (c ^ d));
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0x5A827999 + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
// round 2
|
||
for(; i < 32; ++i) {
|
||
t = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]);
|
||
t = (t << 1) | (t >>> 31);
|
||
w[i] = t;
|
||
f = b ^ c ^ d;
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
for(; i < 40; ++i) {
|
||
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
|
||
t = (t << 2) | (t >>> 30);
|
||
w[i] = t;
|
||
f = b ^ c ^ d;
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0x6ED9EBA1 + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
// round 3
|
||
for(; i < 60; ++i) {
|
||
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
|
||
t = (t << 2) | (t >>> 30);
|
||
w[i] = t;
|
||
f = (b & c) | (d & (b ^ c));
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0x8F1BBCDC + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
// round 4
|
||
for(; i < 80; ++i) {
|
||
t = (w[i - 6] ^ w[i - 16] ^ w[i - 28] ^ w[i - 32]);
|
||
t = (t << 2) | (t >>> 30);
|
||
w[i] = t;
|
||
f = b ^ c ^ d;
|
||
t = ((a << 5) | (a >>> 27)) + f + e + 0xCA62C1D6 + t;
|
||
e = d;
|
||
d = c;
|
||
c = (b << 30) | (b >>> 2);
|
||
b = a;
|
||
a = t;
|
||
}
|
||
|
||
// update hash state
|
||
s.h0 = (s.h0 + a) | 0;
|
||
s.h1 = (s.h1 + b) | 0;
|
||
s.h2 = (s.h2 + c) | 0;
|
||
s.h3 = (s.h3 + d) | 0;
|
||
s.h4 = (s.h4 + e) | 0;
|
||
|
||
len -= 64;
|
||
}
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'sha1';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/sha1',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Secure Hash Algorithm with 256-bit digest (SHA-256) implementation.
|
||
*
|
||
* See FIPS 180-2 for details.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2015 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var sha256 = forge.sha256 = forge.sha256 || {};
|
||
forge.md = forge.md || {};
|
||
forge.md.algorithms = forge.md.algorithms || {};
|
||
forge.md.sha256 = forge.md.algorithms.sha256 = sha256;
|
||
|
||
/**
|
||
* Creates a SHA-256 message digest object.
|
||
*
|
||
* @return a message digest object.
|
||
*/
|
||
sha256.create = function() {
|
||
// do initialization as necessary
|
||
if(!_initialized) {
|
||
_init();
|
||
}
|
||
|
||
// SHA-256 state contains eight 32-bit integers
|
||
var _state = null;
|
||
|
||
// input buffer
|
||
var _input = forge.util.createBuffer();
|
||
|
||
// used for word storage
|
||
var _w = new Array(64);
|
||
|
||
// message digest object
|
||
var md = {
|
||
algorithm: 'sha256',
|
||
blockLength: 64,
|
||
digestLength: 32,
|
||
// 56-bit length of message so far (does not including padding)
|
||
messageLength: 0,
|
||
// true message length
|
||
fullMessageLength: null,
|
||
// size of message length in bytes
|
||
messageLengthSize: 8
|
||
};
|
||
|
||
/**
|
||
* Starts the digest.
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.start = function() {
|
||
// up to 56-bit message length for convenience
|
||
md.messageLength = 0;
|
||
|
||
// full message length (set md.messageLength64 for backwards-compatibility)
|
||
md.fullMessageLength = md.messageLength64 = [];
|
||
var int32s = md.messageLengthSize / 4;
|
||
for(var i = 0; i < int32s; ++i) {
|
||
md.fullMessageLength.push(0);
|
||
}
|
||
_input = forge.util.createBuffer();
|
||
_state = {
|
||
h0: 0x6A09E667,
|
||
h1: 0xBB67AE85,
|
||
h2: 0x3C6EF372,
|
||
h3: 0xA54FF53A,
|
||
h4: 0x510E527F,
|
||
h5: 0x9B05688C,
|
||
h6: 0x1F83D9AB,
|
||
h7: 0x5BE0CD19
|
||
};
|
||
return md;
|
||
};
|
||
// start digest automatically for first time
|
||
md.start();
|
||
|
||
/**
|
||
* Updates the digest with the given message input. The given input can
|
||
* treated as raw input (no encoding will be applied) or an encoding of
|
||
* 'utf8' maybe given to encode the input using UTF-8.
|
||
*
|
||
* @param msg the message input to update with.
|
||
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.update = function(msg, encoding) {
|
||
if(encoding === 'utf8') {
|
||
msg = forge.util.encodeUtf8(msg);
|
||
}
|
||
|
||
// update message length
|
||
var len = msg.length;
|
||
md.messageLength += len;
|
||
len = [(len / 0x100000000) >>> 0, len >>> 0];
|
||
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
|
||
md.fullMessageLength[i] += len[1];
|
||
len[1] = len[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0);
|
||
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0;
|
||
len[0] = ((len[1] / 0x100000000) >>> 0);
|
||
}
|
||
|
||
// add bytes to input buffer
|
||
_input.putBytes(msg);
|
||
|
||
// process bytes
|
||
_update(_state, _w, _input);
|
||
|
||
// compact input buffer every 2K or if empty
|
||
if(_input.read > 2048 || _input.length() === 0) {
|
||
_input.compact();
|
||
}
|
||
|
||
return md;
|
||
};
|
||
|
||
/**
|
||
* Produces the digest.
|
||
*
|
||
* @return a byte buffer containing the digest value.
|
||
*/
|
||
md.digest = function() {
|
||
/* Note: Here we copy the remaining bytes in the input buffer and
|
||
add the appropriate SHA-256 padding. Then we do the final update
|
||
on a copy of the state so that if the user wants to get
|
||
intermediate digests they can do so. */
|
||
|
||
/* Determine the number of bytes that must be added to the message
|
||
to ensure its length is congruent to 448 mod 512. In other words,
|
||
the data to be digested must be a multiple of 512 bits (or 128 bytes).
|
||
This data includes the message, some padding, and the length of the
|
||
message. Since the length of the message will be encoded as 8 bytes (64
|
||
bits), that means that the last segment of the data must have 56 bytes
|
||
(448 bits) of message and padding. Therefore, the length of the message
|
||
plus the padding must be congruent to 448 mod 512 because
|
||
512 - 128 = 448.
|
||
|
||
In order to fill up the message length it must be filled with
|
||
padding that begins with 1 bit followed by all 0 bits. Padding
|
||
must *always* be present, so if the message length is already
|
||
congruent to 448 mod 512, then 512 padding bits must be added. */
|
||
|
||
var finalBlock = forge.util.createBuffer();
|
||
finalBlock.putBytes(_input.bytes());
|
||
|
||
// compute remaining size to be digested (include message length size)
|
||
var remaining = (
|
||
md.fullMessageLength[md.fullMessageLength.length - 1] +
|
||
md.messageLengthSize);
|
||
|
||
// add padding for overflow blockSize - overflow
|
||
// _padding starts with 1 byte with first bit is set (byte value 128), then
|
||
// there may be up to (blockSize - 1) other pad bytes
|
||
var overflow = remaining & (md.blockLength - 1);
|
||
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow));
|
||
|
||
// serialize message length in bits in big-endian order; since length
|
||
// is stored in bytes we multiply by 8 and add carry from next int
|
||
var messageLength = forge.util.createBuffer();
|
||
var next, carry;
|
||
var bits = md.fullMessageLength[0] * 8;
|
||
for(var i = 0; i < md.fullMessageLength.length; ++i) {
|
||
next = md.fullMessageLength[i + 1] * 8;
|
||
carry = (next / 0x100000000) >>> 0;
|
||
bits += carry;
|
||
finalBlock.putInt32(bits >>> 0);
|
||
bits = next;
|
||
}
|
||
|
||
var s2 = {
|
||
h0: _state.h0,
|
||
h1: _state.h1,
|
||
h2: _state.h2,
|
||
h3: _state.h3,
|
||
h4: _state.h4,
|
||
h5: _state.h5,
|
||
h6: _state.h6,
|
||
h7: _state.h7
|
||
};
|
||
_update(s2, _w, finalBlock);
|
||
var rval = forge.util.createBuffer();
|
||
rval.putInt32(s2.h0);
|
||
rval.putInt32(s2.h1);
|
||
rval.putInt32(s2.h2);
|
||
rval.putInt32(s2.h3);
|
||
rval.putInt32(s2.h4);
|
||
rval.putInt32(s2.h5);
|
||
rval.putInt32(s2.h6);
|
||
rval.putInt32(s2.h7);
|
||
return rval;
|
||
};
|
||
|
||
return md;
|
||
};
|
||
|
||
// sha-256 padding bytes not initialized yet
|
||
var _padding = null;
|
||
var _initialized = false;
|
||
|
||
// table of constants
|
||
var _k = null;
|
||
|
||
/**
|
||
* Initializes the constant tables.
|
||
*/
|
||
function _init() {
|
||
// create padding
|
||
_padding = String.fromCharCode(128);
|
||
_padding += forge.util.fillString(String.fromCharCode(0x00), 64);
|
||
|
||
// create K table for SHA-256
|
||
_k = [
|
||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
|
||
|
||
// now initialized
|
||
_initialized = true;
|
||
}
|
||
|
||
/**
|
||
* Updates a SHA-256 state with the given byte buffer.
|
||
*
|
||
* @param s the SHA-256 state to update.
|
||
* @param w the array to use to store words.
|
||
* @param bytes the byte buffer to update with.
|
||
*/
|
||
function _update(s, w, bytes) {
|
||
// consume 512 bit (64 byte) chunks
|
||
var t1, t2, s0, s1, ch, maj, i, a, b, c, d, e, f, g, h;
|
||
var len = bytes.length();
|
||
while(len >= 64) {
|
||
// the w array will be populated with sixteen 32-bit big-endian words
|
||
// and then extended into 64 32-bit words according to SHA-256
|
||
for(i = 0; i < 16; ++i) {
|
||
w[i] = bytes.getInt32();
|
||
}
|
||
for(; i < 64; ++i) {
|
||
// XOR word 2 words ago rot right 17, rot right 19, shft right 10
|
||
t1 = w[i - 2];
|
||
t1 =
|
||
((t1 >>> 17) | (t1 << 15)) ^
|
||
((t1 >>> 19) | (t1 << 13)) ^
|
||
(t1 >>> 10);
|
||
// XOR word 15 words ago rot right 7, rot right 18, shft right 3
|
||
t2 = w[i - 15];
|
||
t2 =
|
||
((t2 >>> 7) | (t2 << 25)) ^
|
||
((t2 >>> 18) | (t2 << 14)) ^
|
||
(t2 >>> 3);
|
||
// sum(t1, word 7 ago, t2, word 16 ago) modulo 2^32
|
||
w[i] = (t1 + w[i - 7] + t2 + w[i - 16]) | 0;
|
||
}
|
||
|
||
// initialize hash value for this chunk
|
||
a = s.h0;
|
||
b = s.h1;
|
||
c = s.h2;
|
||
d = s.h3;
|
||
e = s.h4;
|
||
f = s.h5;
|
||
g = s.h6;
|
||
h = s.h7;
|
||
|
||
// round function
|
||
for(i = 0; i < 64; ++i) {
|
||
// Sum1(e)
|
||
s1 =
|
||
((e >>> 6) | (e << 26)) ^
|
||
((e >>> 11) | (e << 21)) ^
|
||
((e >>> 25) | (e << 7));
|
||
// Ch(e, f, g) (optimized the same way as SHA-1)
|
||
ch = g ^ (e & (f ^ g));
|
||
// Sum0(a)
|
||
s0 =
|
||
((a >>> 2) | (a << 30)) ^
|
||
((a >>> 13) | (a << 19)) ^
|
||
((a >>> 22) | (a << 10));
|
||
// Maj(a, b, c) (optimized the same way as SHA-1)
|
||
maj = (a & b) | (c & (a ^ b));
|
||
|
||
// main algorithm
|
||
t1 = h + s1 + ch + _k[i] + w[i];
|
||
t2 = s0 + maj;
|
||
h = g;
|
||
g = f;
|
||
f = e;
|
||
e = (d + t1) | 0;
|
||
d = c;
|
||
c = b;
|
||
b = a;
|
||
a = (t1 + t2) | 0;
|
||
}
|
||
|
||
// update hash state
|
||
s.h0 = (s.h0 + a) | 0;
|
||
s.h1 = (s.h1 + b) | 0;
|
||
s.h2 = (s.h2 + c) | 0;
|
||
s.h3 = (s.h3 + d) | 0;
|
||
s.h4 = (s.h4 + e) | 0;
|
||
s.h5 = (s.h5 + f) | 0;
|
||
s.h6 = (s.h6 + g) | 0;
|
||
s.h7 = (s.h7 + h) | 0;
|
||
len -= 64;
|
||
}
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'sha256';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/sha256',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Secure Hash Algorithm with a 1024-bit block size implementation.
|
||
*
|
||
* This includes: SHA-512, SHA-384, SHA-512/224, and SHA-512/256. For
|
||
* SHA-256 (block size 512 bits), see sha256.js.
|
||
*
|
||
* See FIPS 180-4 for details.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2014-2015 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var sha512 = forge.sha512 = forge.sha512 || {};
|
||
forge.md = forge.md || {};
|
||
forge.md.algorithms = forge.md.algorithms || {};
|
||
|
||
// SHA-512
|
||
forge.md.sha512 = forge.md.algorithms.sha512 = sha512;
|
||
|
||
// SHA-384
|
||
var sha384 = forge.sha384 = forge.sha512.sha384 = forge.sha512.sha384 || {};
|
||
sha384.create = function() {
|
||
return sha512.create('SHA-384');
|
||
};
|
||
forge.md.sha384 = forge.md.algorithms.sha384 = sha384;
|
||
|
||
// SHA-512/256
|
||
forge.sha512.sha256 = forge.sha512.sha256 || {
|
||
create: function() {
|
||
return sha512.create('SHA-512/256');
|
||
}
|
||
};
|
||
forge.md['sha512/256'] = forge.md.algorithms['sha512/256'] =
|
||
forge.sha512.sha256;
|
||
|
||
// SHA-512/224
|
||
forge.sha512.sha224 = forge.sha512.sha224 || {
|
||
create: function() {
|
||
return sha512.create('SHA-512/224');
|
||
}
|
||
};
|
||
forge.md['sha512/224'] = forge.md.algorithms['sha512/224'] =
|
||
forge.sha512.sha224;
|
||
|
||
/**
|
||
* Creates a SHA-2 message digest object.
|
||
*
|
||
* @param algorithm the algorithm to use (SHA-512, SHA-384, SHA-512/224,
|
||
* SHA-512/256).
|
||
*
|
||
* @return a message digest object.
|
||
*/
|
||
sha512.create = function(algorithm) {
|
||
// do initialization as necessary
|
||
if(!_initialized) {
|
||
_init();
|
||
}
|
||
|
||
if(typeof algorithm === 'undefined') {
|
||
algorithm = 'SHA-512';
|
||
}
|
||
|
||
if(!(algorithm in _states)) {
|
||
throw new Error('Invalid SHA-512 algorithm: ' + algorithm);
|
||
}
|
||
|
||
// SHA-512 state contains eight 64-bit integers (each as two 32-bit ints)
|
||
var _state = _states[algorithm];
|
||
var _h = null;
|
||
|
||
// input buffer
|
||
var _input = forge.util.createBuffer();
|
||
|
||
// used for 64-bit word storage
|
||
var _w = new Array(80);
|
||
for(var wi = 0; wi < 80; ++wi) {
|
||
_w[wi] = new Array(2);
|
||
}
|
||
|
||
// message digest object
|
||
var md = {
|
||
// SHA-512 => sha512
|
||
algorithm: algorithm.replace('-', '').toLowerCase(),
|
||
blockLength: 128,
|
||
digestLength: 64,
|
||
// 56-bit length of message so far (does not including padding)
|
||
messageLength: 0,
|
||
// true message length
|
||
fullMessageLength: null,
|
||
// size of message length in bytes
|
||
messageLengthSize: 16
|
||
};
|
||
|
||
/**
|
||
* Starts the digest.
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.start = function() {
|
||
// up to 56-bit message length for convenience
|
||
md.messageLength = 0;
|
||
|
||
// full message length (set md.messageLength128 for backwards-compatibility)
|
||
md.fullMessageLength = md.messageLength128 = [];
|
||
var int32s = md.messageLengthSize / 4;
|
||
for(var i = 0; i < int32s; ++i) {
|
||
md.fullMessageLength.push(0);
|
||
}
|
||
_input = forge.util.createBuffer();
|
||
_h = new Array(_state.length);
|
||
for(var i = 0; i < _state.length; ++i) {
|
||
_h[i] = _state[i].slice(0);
|
||
}
|
||
return md;
|
||
};
|
||
// start digest automatically for first time
|
||
md.start();
|
||
|
||
/**
|
||
* Updates the digest with the given message input. The given input can
|
||
* treated as raw input (no encoding will be applied) or an encoding of
|
||
* 'utf8' maybe given to encode the input using UTF-8.
|
||
*
|
||
* @param msg the message input to update with.
|
||
* @param encoding the encoding to use (default: 'raw', other: 'utf8').
|
||
*
|
||
* @return this digest object.
|
||
*/
|
||
md.update = function(msg, encoding) {
|
||
if(encoding === 'utf8') {
|
||
msg = forge.util.encodeUtf8(msg);
|
||
}
|
||
|
||
// update message length
|
||
var len = msg.length;
|
||
md.messageLength += len;
|
||
len = [(len / 0x100000000) >>> 0, len >>> 0];
|
||
for(var i = md.fullMessageLength.length - 1; i >= 0; --i) {
|
||
md.fullMessageLength[i] += len[1];
|
||
len[1] = len[0] + ((md.fullMessageLength[i] / 0x100000000) >>> 0);
|
||
md.fullMessageLength[i] = md.fullMessageLength[i] >>> 0;
|
||
len[0] = ((len[1] / 0x100000000) >>> 0);
|
||
}
|
||
|
||
// add bytes to input buffer
|
||
_input.putBytes(msg);
|
||
|
||
// process bytes
|
||
_update(_h, _w, _input);
|
||
|
||
// compact input buffer every 2K or if empty
|
||
if(_input.read > 2048 || _input.length() === 0) {
|
||
_input.compact();
|
||
}
|
||
|
||
return md;
|
||
};
|
||
|
||
/**
|
||
* Produces the digest.
|
||
*
|
||
* @return a byte buffer containing the digest value.
|
||
*/
|
||
md.digest = function() {
|
||
/* Note: Here we copy the remaining bytes in the input buffer and
|
||
add the appropriate SHA-512 padding. Then we do the final update
|
||
on a copy of the state so that if the user wants to get
|
||
intermediate digests they can do so. */
|
||
|
||
/* Determine the number of bytes that must be added to the message
|
||
to ensure its length is congruent to 896 mod 1024. In other words,
|
||
the data to be digested must be a multiple of 1024 bits (or 128 bytes).
|
||
This data includes the message, some padding, and the length of the
|
||
message. Since the length of the message will be encoded as 16 bytes (128
|
||
bits), that means that the last segment of the data must have 112 bytes
|
||
(896 bits) of message and padding. Therefore, the length of the message
|
||
plus the padding must be congruent to 896 mod 1024 because
|
||
1024 - 128 = 896.
|
||
|
||
In order to fill up the message length it must be filled with
|
||
padding that begins with 1 bit followed by all 0 bits. Padding
|
||
must *always* be present, so if the message length is already
|
||
congruent to 896 mod 1024, then 1024 padding bits must be added. */
|
||
|
||
var finalBlock = forge.util.createBuffer();
|
||
finalBlock.putBytes(_input.bytes());
|
||
|
||
// compute remaining size to be digested (include message length size)
|
||
var remaining = (
|
||
md.fullMessageLength[md.fullMessageLength.length - 1] +
|
||
md.messageLengthSize);
|
||
|
||
// add padding for overflow blockSize - overflow
|
||
// _padding starts with 1 byte with first bit is set (byte value 128), then
|
||
// there may be up to (blockSize - 1) other pad bytes
|
||
var overflow = remaining & (md.blockLength - 1);
|
||
finalBlock.putBytes(_padding.substr(0, md.blockLength - overflow));
|
||
|
||
// serialize message length in bits in big-endian order; since length
|
||
// is stored in bytes we multiply by 8 and add carry from next int
|
||
var messageLength = forge.util.createBuffer();
|
||
var next, carry;
|
||
var bits = md.fullMessageLength[0] * 8;
|
||
for(var i = 0; i < md.fullMessageLength.length; ++i) {
|
||
next = md.fullMessageLength[i + 1] * 8;
|
||
carry = (next / 0x100000000) >>> 0;
|
||
bits += carry;
|
||
finalBlock.putInt32(bits >>> 0);
|
||
bits = next;
|
||
}
|
||
|
||
var h = new Array(_h.length);
|
||
for(var i = 0; i < _h.length; ++i) {
|
||
h[i] = _h[i].slice(0);
|
||
}
|
||
_update(h, _w, finalBlock);
|
||
var rval = forge.util.createBuffer();
|
||
var hlen;
|
||
if(algorithm === 'SHA-512') {
|
||
hlen = h.length;
|
||
} else if(algorithm === 'SHA-384') {
|
||
hlen = h.length - 2;
|
||
} else {
|
||
hlen = h.length - 4;
|
||
}
|
||
for(var i = 0; i < hlen; ++i) {
|
||
rval.putInt32(h[i][0]);
|
||
if(i !== hlen - 1 || algorithm !== 'SHA-512/224') {
|
||
rval.putInt32(h[i][1]);
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
return md;
|
||
};
|
||
|
||
// sha-512 padding bytes not initialized yet
|
||
var _padding = null;
|
||
var _initialized = false;
|
||
|
||
// table of constants
|
||
var _k = null;
|
||
|
||
// initial hash states
|
||
var _states = null;
|
||
|
||
/**
|
||
* Initializes the constant tables.
|
||
*/
|
||
function _init() {
|
||
// create padding
|
||
_padding = String.fromCharCode(128);
|
||
_padding += forge.util.fillString(String.fromCharCode(0x00), 128);
|
||
|
||
// create K table for SHA-512
|
||
_k = [
|
||
[0x428a2f98, 0xd728ae22], [0x71374491, 0x23ef65cd],
|
||
[0xb5c0fbcf, 0xec4d3b2f], [0xe9b5dba5, 0x8189dbbc],
|
||
[0x3956c25b, 0xf348b538], [0x59f111f1, 0xb605d019],
|
||
[0x923f82a4, 0xaf194f9b], [0xab1c5ed5, 0xda6d8118],
|
||
[0xd807aa98, 0xa3030242], [0x12835b01, 0x45706fbe],
|
||
[0x243185be, 0x4ee4b28c], [0x550c7dc3, 0xd5ffb4e2],
|
||
[0x72be5d74, 0xf27b896f], [0x80deb1fe, 0x3b1696b1],
|
||
[0x9bdc06a7, 0x25c71235], [0xc19bf174, 0xcf692694],
|
||
[0xe49b69c1, 0x9ef14ad2], [0xefbe4786, 0x384f25e3],
|
||
[0x0fc19dc6, 0x8b8cd5b5], [0x240ca1cc, 0x77ac9c65],
|
||
[0x2de92c6f, 0x592b0275], [0x4a7484aa, 0x6ea6e483],
|
||
[0x5cb0a9dc, 0xbd41fbd4], [0x76f988da, 0x831153b5],
|
||
[0x983e5152, 0xee66dfab], [0xa831c66d, 0x2db43210],
|
||
[0xb00327c8, 0x98fb213f], [0xbf597fc7, 0xbeef0ee4],
|
||
[0xc6e00bf3, 0x3da88fc2], [0xd5a79147, 0x930aa725],
|
||
[0x06ca6351, 0xe003826f], [0x14292967, 0x0a0e6e70],
|
||
[0x27b70a85, 0x46d22ffc], [0x2e1b2138, 0x5c26c926],
|
||
[0x4d2c6dfc, 0x5ac42aed], [0x53380d13, 0x9d95b3df],
|
||
[0x650a7354, 0x8baf63de], [0x766a0abb, 0x3c77b2a8],
|
||
[0x81c2c92e, 0x47edaee6], [0x92722c85, 0x1482353b],
|
||
[0xa2bfe8a1, 0x4cf10364], [0xa81a664b, 0xbc423001],
|
||
[0xc24b8b70, 0xd0f89791], [0xc76c51a3, 0x0654be30],
|
||
[0xd192e819, 0xd6ef5218], [0xd6990624, 0x5565a910],
|
||
[0xf40e3585, 0x5771202a], [0x106aa070, 0x32bbd1b8],
|
||
[0x19a4c116, 0xb8d2d0c8], [0x1e376c08, 0x5141ab53],
|
||
[0x2748774c, 0xdf8eeb99], [0x34b0bcb5, 0xe19b48a8],
|
||
[0x391c0cb3, 0xc5c95a63], [0x4ed8aa4a, 0xe3418acb],
|
||
[0x5b9cca4f, 0x7763e373], [0x682e6ff3, 0xd6b2b8a3],
|
||
[0x748f82ee, 0x5defb2fc], [0x78a5636f, 0x43172f60],
|
||
[0x84c87814, 0xa1f0ab72], [0x8cc70208, 0x1a6439ec],
|
||
[0x90befffa, 0x23631e28], [0xa4506ceb, 0xde82bde9],
|
||
[0xbef9a3f7, 0xb2c67915], [0xc67178f2, 0xe372532b],
|
||
[0xca273ece, 0xea26619c], [0xd186b8c7, 0x21c0c207],
|
||
[0xeada7dd6, 0xcde0eb1e], [0xf57d4f7f, 0xee6ed178],
|
||
[0x06f067aa, 0x72176fba], [0x0a637dc5, 0xa2c898a6],
|
||
[0x113f9804, 0xbef90dae], [0x1b710b35, 0x131c471b],
|
||
[0x28db77f5, 0x23047d84], [0x32caab7b, 0x40c72493],
|
||
[0x3c9ebe0a, 0x15c9bebc], [0x431d67c4, 0x9c100d4c],
|
||
[0x4cc5d4be, 0xcb3e42b6], [0x597f299c, 0xfc657e2a],
|
||
[0x5fcb6fab, 0x3ad6faec], [0x6c44198c, 0x4a475817]
|
||
];
|
||
|
||
// initial hash states
|
||
_states = {};
|
||
_states['SHA-512'] = [
|
||
[0x6a09e667, 0xf3bcc908],
|
||
[0xbb67ae85, 0x84caa73b],
|
||
[0x3c6ef372, 0xfe94f82b],
|
||
[0xa54ff53a, 0x5f1d36f1],
|
||
[0x510e527f, 0xade682d1],
|
||
[0x9b05688c, 0x2b3e6c1f],
|
||
[0x1f83d9ab, 0xfb41bd6b],
|
||
[0x5be0cd19, 0x137e2179]
|
||
];
|
||
_states['SHA-384'] = [
|
||
[0xcbbb9d5d, 0xc1059ed8],
|
||
[0x629a292a, 0x367cd507],
|
||
[0x9159015a, 0x3070dd17],
|
||
[0x152fecd8, 0xf70e5939],
|
||
[0x67332667, 0xffc00b31],
|
||
[0x8eb44a87, 0x68581511],
|
||
[0xdb0c2e0d, 0x64f98fa7],
|
||
[0x47b5481d, 0xbefa4fa4]
|
||
];
|
||
_states['SHA-512/256'] = [
|
||
[0x22312194, 0xFC2BF72C],
|
||
[0x9F555FA3, 0xC84C64C2],
|
||
[0x2393B86B, 0x6F53B151],
|
||
[0x96387719, 0x5940EABD],
|
||
[0x96283EE2, 0xA88EFFE3],
|
||
[0xBE5E1E25, 0x53863992],
|
||
[0x2B0199FC, 0x2C85B8AA],
|
||
[0x0EB72DDC, 0x81C52CA2]
|
||
];
|
||
_states['SHA-512/224'] = [
|
||
[0x8C3D37C8, 0x19544DA2],
|
||
[0x73E19966, 0x89DCD4D6],
|
||
[0x1DFAB7AE, 0x32FF9C82],
|
||
[0x679DD514, 0x582F9FCF],
|
||
[0x0F6D2B69, 0x7BD44DA8],
|
||
[0x77E36F73, 0x04C48942],
|
||
[0x3F9D85A8, 0x6A1D36C8],
|
||
[0x1112E6AD, 0x91D692A1]
|
||
];
|
||
|
||
// now initialized
|
||
_initialized = true;
|
||
}
|
||
|
||
/**
|
||
* Updates a SHA-512 state with the given byte buffer.
|
||
*
|
||
* @param s the SHA-512 state to update.
|
||
* @param w the array to use to store words.
|
||
* @param bytes the byte buffer to update with.
|
||
*/
|
||
function _update(s, w, bytes) {
|
||
// consume 512 bit (128 byte) chunks
|
||
var t1_hi, t1_lo;
|
||
var t2_hi, t2_lo;
|
||
var s0_hi, s0_lo;
|
||
var s1_hi, s1_lo;
|
||
var ch_hi, ch_lo;
|
||
var maj_hi, maj_lo;
|
||
var a_hi, a_lo;
|
||
var b_hi, b_lo;
|
||
var c_hi, c_lo;
|
||
var d_hi, d_lo;
|
||
var e_hi, e_lo;
|
||
var f_hi, f_lo;
|
||
var g_hi, g_lo;
|
||
var h_hi, h_lo;
|
||
var i, hi, lo, w2, w7, w15, w16;
|
||
var len = bytes.length();
|
||
while(len >= 128) {
|
||
// the w array will be populated with sixteen 64-bit big-endian words
|
||
// and then extended into 64 64-bit words according to SHA-512
|
||
for(i = 0; i < 16; ++i) {
|
||
w[i][0] = bytes.getInt32() >>> 0;
|
||
w[i][1] = bytes.getInt32() >>> 0;
|
||
}
|
||
for(; i < 80; ++i) {
|
||
// for word 2 words ago: ROTR 19(x) ^ ROTR 61(x) ^ SHR 6(x)
|
||
w2 = w[i - 2];
|
||
hi = w2[0];
|
||
lo = w2[1];
|
||
|
||
// high bits
|
||
t1_hi = (
|
||
((hi >>> 19) | (lo << 13)) ^ // ROTR 19
|
||
((lo >>> 29) | (hi << 3)) ^ // ROTR 61/(swap + ROTR 29)
|
||
(hi >>> 6)) >>> 0; // SHR 6
|
||
// low bits
|
||
t1_lo = (
|
||
((hi << 13) | (lo >>> 19)) ^ // ROTR 19
|
||
((lo << 3) | (hi >>> 29)) ^ // ROTR 61/(swap + ROTR 29)
|
||
((hi << 26) | (lo >>> 6))) >>> 0; // SHR 6
|
||
|
||
// for word 15 words ago: ROTR 1(x) ^ ROTR 8(x) ^ SHR 7(x)
|
||
w15 = w[i - 15];
|
||
hi = w15[0];
|
||
lo = w15[1];
|
||
|
||
// high bits
|
||
t2_hi = (
|
||
((hi >>> 1) | (lo << 31)) ^ // ROTR 1
|
||
((hi >>> 8) | (lo << 24)) ^ // ROTR 8
|
||
(hi >>> 7)) >>> 0; // SHR 7
|
||
// low bits
|
||
t2_lo = (
|
||
((hi << 31) | (lo >>> 1)) ^ // ROTR 1
|
||
((hi << 24) | (lo >>> 8)) ^ // ROTR 8
|
||
((hi << 25) | (lo >>> 7))) >>> 0; // SHR 7
|
||
|
||
// sum(t1, word 7 ago, t2, word 16 ago) modulo 2^64 (carry lo overflow)
|
||
w7 = w[i - 7];
|
||
w16 = w[i - 16];
|
||
lo = (t1_lo + w7[1] + t2_lo + w16[1]);
|
||
w[i][0] = (t1_hi + w7[0] + t2_hi + w16[0] +
|
||
((lo / 0x100000000) >>> 0)) >>> 0;
|
||
w[i][1] = lo >>> 0;
|
||
}
|
||
|
||
// initialize hash value for this chunk
|
||
a_hi = s[0][0];
|
||
a_lo = s[0][1];
|
||
b_hi = s[1][0];
|
||
b_lo = s[1][1];
|
||
c_hi = s[2][0];
|
||
c_lo = s[2][1];
|
||
d_hi = s[3][0];
|
||
d_lo = s[3][1];
|
||
e_hi = s[4][0];
|
||
e_lo = s[4][1];
|
||
f_hi = s[5][0];
|
||
f_lo = s[5][1];
|
||
g_hi = s[6][0];
|
||
g_lo = s[6][1];
|
||
h_hi = s[7][0];
|
||
h_lo = s[7][1];
|
||
|
||
// round function
|
||
for(i = 0; i < 80; ++i) {
|
||
// Sum1(e) = ROTR 14(e) ^ ROTR 18(e) ^ ROTR 41(e)
|
||
s1_hi = (
|
||
((e_hi >>> 14) | (e_lo << 18)) ^ // ROTR 14
|
||
((e_hi >>> 18) | (e_lo << 14)) ^ // ROTR 18
|
||
((e_lo >>> 9) | (e_hi << 23))) >>> 0; // ROTR 41/(swap + ROTR 9)
|
||
s1_lo = (
|
||
((e_hi << 18) | (e_lo >>> 14)) ^ // ROTR 14
|
||
((e_hi << 14) | (e_lo >>> 18)) ^ // ROTR 18
|
||
((e_lo << 23) | (e_hi >>> 9))) >>> 0; // ROTR 41/(swap + ROTR 9)
|
||
|
||
// Ch(e, f, g) (optimized the same way as SHA-1)
|
||
ch_hi = (g_hi ^ (e_hi & (f_hi ^ g_hi))) >>> 0;
|
||
ch_lo = (g_lo ^ (e_lo & (f_lo ^ g_lo))) >>> 0;
|
||
|
||
// Sum0(a) = ROTR 28(a) ^ ROTR 34(a) ^ ROTR 39(a)
|
||
s0_hi = (
|
||
((a_hi >>> 28) | (a_lo << 4)) ^ // ROTR 28
|
||
((a_lo >>> 2) | (a_hi << 30)) ^ // ROTR 34/(swap + ROTR 2)
|
||
((a_lo >>> 7) | (a_hi << 25))) >>> 0; // ROTR 39/(swap + ROTR 7)
|
||
s0_lo = (
|
||
((a_hi << 4) | (a_lo >>> 28)) ^ // ROTR 28
|
||
((a_lo << 30) | (a_hi >>> 2)) ^ // ROTR 34/(swap + ROTR 2)
|
||
((a_lo << 25) | (a_hi >>> 7))) >>> 0; // ROTR 39/(swap + ROTR 7)
|
||
|
||
// Maj(a, b, c) (optimized the same way as SHA-1)
|
||
maj_hi = ((a_hi & b_hi) | (c_hi & (a_hi ^ b_hi))) >>> 0;
|
||
maj_lo = ((a_lo & b_lo) | (c_lo & (a_lo ^ b_lo))) >>> 0;
|
||
|
||
// main algorithm
|
||
// t1 = (h + s1 + ch + _k[i] + _w[i]) modulo 2^64 (carry lo overflow)
|
||
lo = (h_lo + s1_lo + ch_lo + _k[i][1] + w[i][1]);
|
||
t1_hi = (h_hi + s1_hi + ch_hi + _k[i][0] + w[i][0] +
|
||
((lo / 0x100000000) >>> 0)) >>> 0;
|
||
t1_lo = lo >>> 0;
|
||
|
||
// t2 = s0 + maj modulo 2^64 (carry lo overflow)
|
||
lo = s0_lo + maj_lo;
|
||
t2_hi = (s0_hi + maj_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
t2_lo = lo >>> 0;
|
||
|
||
h_hi = g_hi;
|
||
h_lo = g_lo;
|
||
|
||
g_hi = f_hi;
|
||
g_lo = f_lo;
|
||
|
||
f_hi = e_hi;
|
||
f_lo = e_lo;
|
||
|
||
// e = (d + t1) modulo 2^64 (carry lo overflow)
|
||
lo = d_lo + t1_lo;
|
||
e_hi = (d_hi + t1_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
e_lo = lo >>> 0;
|
||
|
||
d_hi = c_hi;
|
||
d_lo = c_lo;
|
||
|
||
c_hi = b_hi;
|
||
c_lo = b_lo;
|
||
|
||
b_hi = a_hi;
|
||
b_lo = a_lo;
|
||
|
||
// a = (t1 + t2) modulo 2^64 (carry lo overflow)
|
||
lo = t1_lo + t2_lo;
|
||
a_hi = (t1_hi + t2_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
a_lo = lo >>> 0;
|
||
}
|
||
|
||
// update hash state (additional modulo 2^64)
|
||
lo = s[0][1] + a_lo;
|
||
s[0][0] = (s[0][0] + a_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[0][1] = lo >>> 0;
|
||
|
||
lo = s[1][1] + b_lo;
|
||
s[1][0] = (s[1][0] + b_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[1][1] = lo >>> 0;
|
||
|
||
lo = s[2][1] + c_lo;
|
||
s[2][0] = (s[2][0] + c_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[2][1] = lo >>> 0;
|
||
|
||
lo = s[3][1] + d_lo;
|
||
s[3][0] = (s[3][0] + d_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[3][1] = lo >>> 0;
|
||
|
||
lo = s[4][1] + e_lo;
|
||
s[4][0] = (s[4][0] + e_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[4][1] = lo >>> 0;
|
||
|
||
lo = s[5][1] + f_lo;
|
||
s[5][0] = (s[5][0] + f_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[5][1] = lo >>> 0;
|
||
|
||
lo = s[6][1] + g_lo;
|
||
s[6][0] = (s[6][0] + g_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[6][1] = lo >>> 0;
|
||
|
||
lo = s[7][1] + h_lo;
|
||
s[7][0] = (s[7][0] + h_hi + ((lo / 0x100000000) >>> 0)) >>> 0;
|
||
s[7][1] = lo >>> 0;
|
||
|
||
len -= 128;
|
||
}
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'sha512';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/sha512',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Node.js module for Forge message digests.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright 2011-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.md = forge.md || {};
|
||
forge.md.algorithms = {
|
||
md5: forge.md5,
|
||
sha1: forge.sha1,
|
||
sha256: forge.sha256
|
||
};
|
||
forge.md.md5 = forge.md5;
|
||
forge.md.sha1 = forge.sha1;
|
||
forge.md.sha256 = forge.sha256;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'md';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define(
|
||
'js/md',['require', 'module', './md5', './sha1', './sha256', './sha512'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Hash-based Message Authentication Code implementation. Requires a message
|
||
* digest object that can be obtained, for example, from forge.md.sha1 or
|
||
* forge.md.md5.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2012 Digital Bazaar, Inc. All rights reserved.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* HMAC API */
|
||
var hmac = forge.hmac = forge.hmac || {};
|
||
|
||
/**
|
||
* Creates an HMAC object that uses the given message digest object.
|
||
*
|
||
* @return an HMAC object.
|
||
*/
|
||
hmac.create = function() {
|
||
// the hmac key to use
|
||
var _key = null;
|
||
|
||
// the message digest to use
|
||
var _md = null;
|
||
|
||
// the inner padding
|
||
var _ipadding = null;
|
||
|
||
// the outer padding
|
||
var _opadding = null;
|
||
|
||
// hmac context
|
||
var ctx = {};
|
||
|
||
/**
|
||
* Starts or restarts the HMAC with the given key and message digest.
|
||
*
|
||
* @param md the message digest to use, null to reuse the previous one,
|
||
* a string to use builtin 'sha1', 'md5', 'sha256'.
|
||
* @param key the key to use as a string, array of bytes, byte buffer,
|
||
* or null to reuse the previous key.
|
||
*/
|
||
ctx.start = function(md, key) {
|
||
if(md !== null) {
|
||
if(typeof md === 'string') {
|
||
// create builtin message digest
|
||
md = md.toLowerCase();
|
||
if(md in forge.md.algorithms) {
|
||
_md = forge.md.algorithms[md].create();
|
||
} else {
|
||
throw new Error('Unknown hash algorithm "' + md + '"');
|
||
}
|
||
} else {
|
||
// store message digest
|
||
_md = md;
|
||
}
|
||
}
|
||
|
||
if(key === null) {
|
||
// reuse previous key
|
||
key = _key;
|
||
} else {
|
||
if(typeof key === 'string') {
|
||
// convert string into byte buffer
|
||
key = forge.util.createBuffer(key);
|
||
} else if(forge.util.isArray(key)) {
|
||
// convert byte array into byte buffer
|
||
var tmp = key;
|
||
key = forge.util.createBuffer();
|
||
for(var i = 0; i < tmp.length; ++i) {
|
||
key.putByte(tmp[i]);
|
||
}
|
||
}
|
||
|
||
// if key is longer than blocksize, hash it
|
||
var keylen = key.length();
|
||
if(keylen > _md.blockLength) {
|
||
_md.start();
|
||
_md.update(key.bytes());
|
||
key = _md.digest();
|
||
}
|
||
|
||
// mix key into inner and outer padding
|
||
// ipadding = [0x36 * blocksize] ^ key
|
||
// opadding = [0x5C * blocksize] ^ key
|
||
_ipadding = forge.util.createBuffer();
|
||
_opadding = forge.util.createBuffer();
|
||
keylen = key.length();
|
||
for(var i = 0; i < keylen; ++i) {
|
||
var tmp = key.at(i);
|
||
_ipadding.putByte(0x36 ^ tmp);
|
||
_opadding.putByte(0x5C ^ tmp);
|
||
}
|
||
|
||
// if key is shorter than blocksize, add additional padding
|
||
if(keylen < _md.blockLength) {
|
||
var tmp = _md.blockLength - keylen;
|
||
for(var i = 0; i < tmp; ++i) {
|
||
_ipadding.putByte(0x36);
|
||
_opadding.putByte(0x5C);
|
||
}
|
||
}
|
||
_key = key;
|
||
_ipadding = _ipadding.bytes();
|
||
_opadding = _opadding.bytes();
|
||
}
|
||
|
||
// digest is done like so: hash(opadding | hash(ipadding | message))
|
||
|
||
// prepare to do inner hash
|
||
// hash(ipadding | message)
|
||
_md.start();
|
||
_md.update(_ipadding);
|
||
};
|
||
|
||
/**
|
||
* Updates the HMAC with the given message bytes.
|
||
*
|
||
* @param bytes the bytes to update with.
|
||
*/
|
||
ctx.update = function(bytes) {
|
||
_md.update(bytes);
|
||
};
|
||
|
||
/**
|
||
* Produces the Message Authentication Code (MAC).
|
||
*
|
||
* @return a byte buffer containing the digest value.
|
||
*/
|
||
ctx.getMac = function() {
|
||
// digest is done like so: hash(opadding | hash(ipadding | message))
|
||
// here we do the outer hashing
|
||
var inner = _md.digest().bytes();
|
||
_md.start();
|
||
_md.update(_opadding);
|
||
_md.update(inner);
|
||
return _md.digest();
|
||
};
|
||
// alias for getMac
|
||
ctx.digest = ctx.getMac;
|
||
|
||
return ctx;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'hmac';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/hmac',['require', 'module', './md', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of basic PEM (Privacy Enhanced Mail) algorithms.
|
||
*
|
||
* See: RFC 1421.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2013-2014 Digital Bazaar, Inc.
|
||
*
|
||
* A Forge PEM object has the following fields:
|
||
*
|
||
* type: identifies the type of message (eg: "RSA PRIVATE KEY").
|
||
*
|
||
* procType: identifies the type of processing performed on the message,
|
||
* it has two subfields: version and type, eg: 4,ENCRYPTED.
|
||
*
|
||
* contentDomain: identifies the type of content in the message, typically
|
||
* only uses the value: "RFC822".
|
||
*
|
||
* dekInfo: identifies the message encryption algorithm and mode and includes
|
||
* any parameters for the algorithm, it has two subfields: algorithm and
|
||
* parameters, eg: DES-CBC,F8143EDE5960C597.
|
||
*
|
||
* headers: contains all other PEM encapsulated headers -- where order is
|
||
* significant (for pairing data like recipient ID + key info).
|
||
*
|
||
* body: the binary-encoded body.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for pem API
|
||
var pem = forge.pem = forge.pem || {};
|
||
|
||
/**
|
||
* Encodes (serializes) the given PEM object.
|
||
*
|
||
* @param msg the PEM message object to encode.
|
||
* @param options the options to use:
|
||
* maxline the maximum characters per line for the body, (default: 64).
|
||
*
|
||
* @return the PEM-formatted string.
|
||
*/
|
||
pem.encode = function(msg, options) {
|
||
options = options || {};
|
||
var rval = '-----BEGIN ' + msg.type + '-----\r\n';
|
||
|
||
// encode special headers
|
||
var header;
|
||
if(msg.procType) {
|
||
header = {
|
||
name: 'Proc-Type',
|
||
values: [String(msg.procType.version), msg.procType.type]
|
||
};
|
||
rval += foldHeader(header);
|
||
}
|
||
if(msg.contentDomain) {
|
||
header = {name: 'Content-Domain', values: [msg.contentDomain]};
|
||
rval += foldHeader(header);
|
||
}
|
||
if(msg.dekInfo) {
|
||
header = {name: 'DEK-Info', values: [msg.dekInfo.algorithm]};
|
||
if(msg.dekInfo.parameters) {
|
||
header.values.push(msg.dekInfo.parameters);
|
||
}
|
||
rval += foldHeader(header);
|
||
}
|
||
|
||
if(msg.headers) {
|
||
// encode all other headers
|
||
for(var i = 0; i < msg.headers.length; ++i) {
|
||
rval += foldHeader(msg.headers[i]);
|
||
}
|
||
}
|
||
|
||
// terminate header
|
||
if(msg.procType) {
|
||
rval += '\r\n';
|
||
}
|
||
|
||
// add body
|
||
rval += forge.util.encode64(msg.body, options.maxline || 64) + '\r\n';
|
||
|
||
rval += '-----END ' + msg.type + '-----\r\n';
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Decodes (deserializes) all PEM messages found in the given string.
|
||
*
|
||
* @param str the PEM-formatted string to decode.
|
||
*
|
||
* @return the PEM message objects in an array.
|
||
*/
|
||
pem.decode = function(str) {
|
||
var rval = [];
|
||
|
||
// split string into PEM messages (be lenient w/EOF on BEGIN line)
|
||
var rMessage = /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g;
|
||
var rHeader = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/;
|
||
var rCRLF = /\r?\n/;
|
||
var match;
|
||
while(true) {
|
||
match = rMessage.exec(str);
|
||
if(!match) {
|
||
break;
|
||
}
|
||
|
||
var msg = {
|
||
type: match[1],
|
||
procType: null,
|
||
contentDomain: null,
|
||
dekInfo: null,
|
||
headers: [],
|
||
body: forge.util.decode64(match[3])
|
||
};
|
||
rval.push(msg);
|
||
|
||
// no headers
|
||
if(!match[2]) {
|
||
continue;
|
||
}
|
||
|
||
// parse headers
|
||
var lines = match[2].split(rCRLF);
|
||
var li = 0;
|
||
while(match && li < lines.length) {
|
||
// get line, trim any rhs whitespace
|
||
var line = lines[li].replace(/\s+$/, '');
|
||
|
||
// RFC2822 unfold any following folded lines
|
||
for(var nl = li + 1; nl < lines.length; ++nl) {
|
||
var next = lines[nl];
|
||
if(!/\s/.test(next[0])) {
|
||
break;
|
||
}
|
||
line += next;
|
||
li = nl;
|
||
}
|
||
|
||
// parse header
|
||
match = line.match(rHeader);
|
||
if(match) {
|
||
var header = {name: match[1], values: []};
|
||
var values = match[2].split(',');
|
||
for(var vi = 0; vi < values.length; ++vi) {
|
||
header.values.push(ltrim(values[vi]));
|
||
}
|
||
|
||
// Proc-Type must be the first header
|
||
if(!msg.procType) {
|
||
if(header.name !== 'Proc-Type') {
|
||
throw new Error('Invalid PEM formatted message. The first ' +
|
||
'encapsulated header must be "Proc-Type".');
|
||
} else if(header.values.length !== 2) {
|
||
throw new Error('Invalid PEM formatted message. The "Proc-Type" ' +
|
||
'header must have two subfields.');
|
||
}
|
||
msg.procType = {version: values[0], type: values[1]};
|
||
} else if(!msg.contentDomain && header.name === 'Content-Domain') {
|
||
// special-case Content-Domain
|
||
msg.contentDomain = values[0] || '';
|
||
} else if(!msg.dekInfo && header.name === 'DEK-Info') {
|
||
// special-case DEK-Info
|
||
if(header.values.length === 0) {
|
||
throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
|
||
'header must have at least one subfield.');
|
||
}
|
||
msg.dekInfo = {algorithm: values[0], parameters: values[1] || null};
|
||
} else {
|
||
msg.headers.push(header);
|
||
}
|
||
}
|
||
|
||
++li;
|
||
}
|
||
|
||
if(msg.procType === 'ENCRYPTED' && !msg.dekInfo) {
|
||
throw new Error('Invalid PEM formatted message. The "DEK-Info" ' +
|
||
'header must be present if "Proc-Type" is "ENCRYPTED".');
|
||
}
|
||
}
|
||
|
||
if(rval.length === 0) {
|
||
throw new Error('Invalid PEM formatted message.');
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
function foldHeader(header) {
|
||
var rval = header.name + ': ';
|
||
|
||
// ensure values with CRLF are folded
|
||
var values = [];
|
||
var insertSpace = function(match, $1) {
|
||
return ' ' + $1;
|
||
};
|
||
for(var i = 0; i < header.values.length; ++i) {
|
||
values.push(header.values[i].replace(/^(\S+\r\n)/, insertSpace));
|
||
}
|
||
rval += values.join(',') + '\r\n';
|
||
|
||
// do folding
|
||
var length = 0;
|
||
var candidate = -1;
|
||
for(var i = 0; i < rval.length; ++i, ++length) {
|
||
if(length > 65 && candidate !== -1) {
|
||
var insert = rval[candidate];
|
||
if(insert === ',') {
|
||
++candidate;
|
||
rval = rval.substr(0, candidate) + '\r\n ' + rval.substr(candidate);
|
||
} else {
|
||
rval = rval.substr(0, candidate) +
|
||
'\r\n' + insert + rval.substr(candidate + 1);
|
||
}
|
||
length = (i - candidate - 1);
|
||
candidate = -1;
|
||
++i;
|
||
} else if(rval[i] === ' ' || rval[i] === '\t' || rval[i] === ',') {
|
||
candidate = i;
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
|
||
function ltrim(str) {
|
||
return str.replace(/^\s+/, '');
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pem';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pem',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* DES (Data Encryption Standard) implementation.
|
||
*
|
||
* This implementation supports DES as well as 3DES-EDE in ECB and CBC mode.
|
||
* It is based on the BSD-licensed implementation by Paul Tero:
|
||
*
|
||
* Paul Tero, July 2001
|
||
* http://www.tero.co.uk/des/
|
||
*
|
||
* Optimised for performance with large blocks by Michael Hayworth, November 2001
|
||
* http://www.netdealing.com
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED "AS IS" AND
|
||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
* SUCH DAMAGE.
|
||
*
|
||
* @author Stefan Siegl
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
* Copyright (c) 2012-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* DES API */
|
||
forge.des = forge.des || {};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var cipher = forge.cipher.createCipher('DES-<mode>', key);
|
||
* cipher.start({iv: iv});
|
||
*
|
||
* Creates an DES cipher object to encrypt data using the given symmetric key.
|
||
* The output will be stored in the 'output' member of the returned cipher.
|
||
*
|
||
* The key and iv may be given as binary-encoded strings of bytes or
|
||
* byte buffers.
|
||
*
|
||
* @param key the symmetric key to use (64 or 192 bits).
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
* @param mode the cipher mode to use (default: 'CBC' if IV is
|
||
* given, 'ECB' if null).
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.des.startEncrypting = function(key, iv, output, mode) {
|
||
var cipher = _createCipher({
|
||
key: key,
|
||
output: output,
|
||
decrypt: false,
|
||
mode: mode || (iv === null ? 'ECB' : 'CBC')
|
||
});
|
||
cipher.start(iv);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var cipher = forge.cipher.createCipher('DES-<mode>', key);
|
||
*
|
||
* Creates an DES cipher object to encrypt data using the given symmetric key.
|
||
*
|
||
* The key may be given as a binary-encoded string of bytes or a byte buffer.
|
||
*
|
||
* @param key the symmetric key to use (64 or 192 bits).
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.des.createEncryptionCipher = function(key, mode) {
|
||
return _createCipher({
|
||
key: key,
|
||
output: null,
|
||
decrypt: false,
|
||
mode: mode
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var decipher = forge.cipher.createDecipher('DES-<mode>', key);
|
||
* decipher.start({iv: iv});
|
||
*
|
||
* Creates an DES cipher object to decrypt data using the given symmetric key.
|
||
* The output will be stored in the 'output' member of the returned cipher.
|
||
*
|
||
* The key and iv may be given as binary-encoded strings of bytes or
|
||
* byte buffers.
|
||
*
|
||
* @param key the symmetric key to use (64 or 192 bits).
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
* @param mode the cipher mode to use (default: 'CBC' if IV is
|
||
* given, 'ECB' if null).
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.des.startDecrypting = function(key, iv, output, mode) {
|
||
var cipher = _createCipher({
|
||
key: key,
|
||
output: output,
|
||
decrypt: true,
|
||
mode: mode || (iv === null ? 'ECB' : 'CBC')
|
||
});
|
||
cipher.start(iv);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* var decipher = forge.cipher.createDecipher('DES-<mode>', key);
|
||
*
|
||
* Creates an DES cipher object to decrypt data using the given symmetric key.
|
||
*
|
||
* The key may be given as a binary-encoded string of bytes or a byte buffer.
|
||
*
|
||
* @param key the symmetric key to use (64 or 192 bits).
|
||
* @param mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.des.createDecryptionCipher = function(key, mode) {
|
||
return _createCipher({
|
||
key: key,
|
||
output: null,
|
||
decrypt: true,
|
||
mode: mode
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Creates a new DES cipher algorithm object.
|
||
*
|
||
* @param name the name of the algorithm.
|
||
* @param mode the mode factory function.
|
||
*
|
||
* @return the DES algorithm object.
|
||
*/
|
||
forge.des.Algorithm = function(name, mode) {
|
||
var self = this;
|
||
self.name = name;
|
||
self.mode = new mode({
|
||
blockSize: 8,
|
||
cipher: {
|
||
encrypt: function(inBlock, outBlock) {
|
||
return _updateBlock(self._keys, inBlock, outBlock, false);
|
||
},
|
||
decrypt: function(inBlock, outBlock) {
|
||
return _updateBlock(self._keys, inBlock, outBlock, true);
|
||
}
|
||
}
|
||
});
|
||
self._init = false;
|
||
};
|
||
|
||
/**
|
||
* Initializes this DES algorithm by expanding its key.
|
||
*
|
||
* @param options the options to use.
|
||
* key the key to use with this algorithm.
|
||
* decrypt true if the algorithm should be initialized for decryption,
|
||
* false for encryption.
|
||
*/
|
||
forge.des.Algorithm.prototype.initialize = function(options) {
|
||
if(this._init) {
|
||
return;
|
||
}
|
||
|
||
var key = forge.util.createBuffer(options.key);
|
||
if(this.name.indexOf('3DES') === 0) {
|
||
if(key.length() !== 24) {
|
||
throw new Error('Invalid Triple-DES key size: ' + key.length() * 8);
|
||
}
|
||
}
|
||
|
||
// do key expansion to 16 or 48 subkeys (single or triple DES)
|
||
this._keys = _createKeys(key);
|
||
this._init = true;
|
||
};
|
||
|
||
|
||
/** Register DES algorithms **/
|
||
|
||
registerAlgorithm('DES-ECB', forge.cipher.modes.ecb);
|
||
registerAlgorithm('DES-CBC', forge.cipher.modes.cbc);
|
||
registerAlgorithm('DES-CFB', forge.cipher.modes.cfb);
|
||
registerAlgorithm('DES-OFB', forge.cipher.modes.ofb);
|
||
registerAlgorithm('DES-CTR', forge.cipher.modes.ctr);
|
||
|
||
registerAlgorithm('3DES-ECB', forge.cipher.modes.ecb);
|
||
registerAlgorithm('3DES-CBC', forge.cipher.modes.cbc);
|
||
registerAlgorithm('3DES-CFB', forge.cipher.modes.cfb);
|
||
registerAlgorithm('3DES-OFB', forge.cipher.modes.ofb);
|
||
registerAlgorithm('3DES-CTR', forge.cipher.modes.ctr);
|
||
|
||
function registerAlgorithm(name, mode) {
|
||
var factory = function() {
|
||
return new forge.des.Algorithm(name, mode);
|
||
};
|
||
forge.cipher.registerAlgorithm(name, factory);
|
||
}
|
||
|
||
|
||
/** DES implementation **/
|
||
|
||
var spfunction1 = [0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004];
|
||
var spfunction2 = [-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000];
|
||
var spfunction3 = [0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200];
|
||
var spfunction4 = [0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080];
|
||
var spfunction5 = [0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,0x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100];
|
||
var spfunction6 = [0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010];
|
||
var spfunction7 = [0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002];
|
||
var spfunction8 = [0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000];
|
||
|
||
/**
|
||
* Create necessary sub keys.
|
||
*
|
||
* @param key the 64-bit or 192-bit key.
|
||
*
|
||
* @return the expanded keys.
|
||
*/
|
||
function _createKeys(key) {
|
||
var pc2bytes0 = [0,0x4,0x20000000,0x20000004,0x10000,0x10004,0x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204],
|
||
pc2bytes1 = [0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101],
|
||
pc2bytes2 = [0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808],
|
||
pc2bytes3 = [0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000],
|
||
pc2bytes4 = [0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010],
|
||
pc2bytes5 = [0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420],
|
||
pc2bytes6 = [0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002],
|
||
pc2bytes7 = [0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800],
|
||
pc2bytes8 = [0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002],
|
||
pc2bytes9 = [0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408],
|
||
pc2bytes10 = [0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020],
|
||
pc2bytes11 = [0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200],
|
||
pc2bytes12 = [0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010],
|
||
pc2bytes13 = [0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105];
|
||
|
||
// how many iterations (1 for des, 3 for triple des)
|
||
// changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
|
||
var iterations = key.length() > 8 ? 3 : 1;
|
||
|
||
// stores the return keys
|
||
var keys = [];
|
||
|
||
// now define the left shifts which need to be done
|
||
var shifts = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0];
|
||
|
||
var n = 0, tmp;
|
||
for(var j = 0; j < iterations; j ++) {
|
||
var left = key.getInt32();
|
||
var right = key.getInt32();
|
||
|
||
tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
|
||
right ^= tmp;
|
||
left ^= (tmp << 4);
|
||
|
||
tmp = ((right >>> -16) ^ left) & 0x0000ffff;
|
||
left ^= tmp;
|
||
right ^= (tmp << -16);
|
||
|
||
tmp = ((left >>> 2) ^ right) & 0x33333333;
|
||
right ^= tmp;
|
||
left ^= (tmp << 2);
|
||
|
||
tmp = ((right >>> -16) ^ left) & 0x0000ffff;
|
||
left ^= tmp;
|
||
right ^= (tmp << -16);
|
||
|
||
tmp = ((left >>> 1) ^ right) & 0x55555555;
|
||
right ^= tmp;
|
||
left ^= (tmp << 1);
|
||
|
||
tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
|
||
left ^= tmp;
|
||
right ^= (tmp << 8);
|
||
|
||
tmp = ((left >>> 1) ^ right) & 0x55555555;
|
||
right ^= tmp;
|
||
left ^= (tmp << 1);
|
||
|
||
// right needs to be shifted and OR'd with last four bits of left
|
||
tmp = (left << 8) | ((right >>> 20) & 0x000000f0);
|
||
|
||
// left needs to be put upside down
|
||
left = ((right << 24) | ((right << 8) & 0xff0000) |
|
||
((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0));
|
||
right = tmp;
|
||
|
||
// now go through and perform these shifts on the left and right keys
|
||
for(var i = 0; i < shifts.length; ++i) {
|
||
//shift the keys either one or two bits to the left
|
||
if(shifts[i]) {
|
||
left = (left << 2) | (left >>> 26);
|
||
right = (right << 2) | (right >>> 26);
|
||
} else {
|
||
left = (left << 1) | (left >>> 27);
|
||
right = (right << 1) | (right >>> 27);
|
||
}
|
||
left &= -0xf;
|
||
right &= -0xf;
|
||
|
||
// now apply PC-2, in such a way that E is easier when encrypting or
|
||
// decrypting this conversion will look like PC-2 except only the last 6
|
||
// bits of each byte are used rather than 48 consecutive bits and the
|
||
// order of lines will be according to how the S selection functions will
|
||
// be applied: S2, S4, S6, S8, S1, S3, S5, S7
|
||
var lefttmp = (
|
||
pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf] |
|
||
pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf] |
|
||
pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf] |
|
||
pc2bytes6[(left >>> 4) & 0xf]);
|
||
var righttmp = (
|
||
pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf] |
|
||
pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf] |
|
||
pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf] |
|
||
pc2bytes13[(right >>> 4) & 0xf]);
|
||
tmp = ((righttmp >>> 16) ^ lefttmp) & 0x0000ffff;
|
||
keys[n++] = lefttmp ^ tmp;
|
||
keys[n++] = righttmp ^ (tmp << 16);
|
||
}
|
||
}
|
||
|
||
return keys;
|
||
}
|
||
|
||
/**
|
||
* Updates a single block (1 byte) using DES. The update will either
|
||
* encrypt or decrypt the block.
|
||
*
|
||
* @param keys the expanded keys.
|
||
* @param input the input block (an array of 32-bit words).
|
||
* @param output the updated output block.
|
||
* @param decrypt true to decrypt the block, false to encrypt it.
|
||
*/
|
||
function _updateBlock(keys, input, output, decrypt) {
|
||
// set up loops for single or triple DES
|
||
var iterations = keys.length === 32 ? 3 : 9;
|
||
var looping;
|
||
if(iterations === 3) {
|
||
looping = decrypt ? [30, -2, -2] : [0, 32, 2];
|
||
} else {
|
||
looping = (decrypt ?
|
||
[94, 62, -2, 32, 64, 2, 30, -2, -2] :
|
||
[0, 32, 2, 62, 30, -2, 64, 96, 2]);
|
||
}
|
||
|
||
var tmp;
|
||
|
||
var left = input[0];
|
||
var right = input[1];
|
||
|
||
// first each 64 bit chunk of the message must be permuted according to IP
|
||
tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
|
||
right ^= tmp;
|
||
left ^= (tmp << 4);
|
||
|
||
tmp = ((left >>> 16) ^ right) & 0x0000ffff;
|
||
right ^= tmp;
|
||
left ^= (tmp << 16);
|
||
|
||
tmp = ((right >>> 2) ^ left) & 0x33333333;
|
||
left ^= tmp;
|
||
right ^= (tmp << 2);
|
||
|
||
tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
|
||
left ^= tmp;
|
||
right ^= (tmp << 8);
|
||
|
||
tmp = ((left >>> 1) ^ right) & 0x55555555;
|
||
right ^= tmp;
|
||
left ^= (tmp << 1);
|
||
|
||
// rotate left 1 bit
|
||
left = ((left << 1) | (left >>> 31));
|
||
right = ((right << 1) | (right >>> 31));
|
||
|
||
for(var j = 0; j < iterations; j += 3) {
|
||
var endloop = looping[j + 1];
|
||
var loopinc = looping[j + 2];
|
||
|
||
// now go through and perform the encryption or decryption
|
||
for(var i = looping[j]; i != endloop; i += loopinc) {
|
||
var right1 = right ^ keys[i];
|
||
var right2 = ((right >>> 4) | (right << 28)) ^ keys[i + 1];
|
||
|
||
// passing these bytes through the S selection functions
|
||
tmp = left;
|
||
left = right;
|
||
right = tmp ^ (
|
||
spfunction2[(right1 >>> 24) & 0x3f] |
|
||
spfunction4[(right1 >>> 16) & 0x3f] |
|
||
spfunction6[(right1 >>> 8) & 0x3f] |
|
||
spfunction8[right1 & 0x3f] |
|
||
spfunction1[(right2 >>> 24) & 0x3f] |
|
||
spfunction3[(right2 >>> 16) & 0x3f] |
|
||
spfunction5[(right2 >>> 8) & 0x3f] |
|
||
spfunction7[right2 & 0x3f]);
|
||
}
|
||
// unreverse left and right
|
||
tmp = left;
|
||
left = right;
|
||
right = tmp;
|
||
}
|
||
|
||
// rotate right 1 bit
|
||
left = ((left >>> 1) | (left << 31));
|
||
right = ((right >>> 1) | (right << 31));
|
||
|
||
// now perform IP-1, which is IP in the opposite direction
|
||
tmp = ((left >>> 1) ^ right) & 0x55555555;
|
||
right ^= tmp;
|
||
left ^= (tmp << 1);
|
||
|
||
tmp = ((right >>> 8) ^ left) & 0x00ff00ff;
|
||
left ^= tmp;
|
||
right ^= (tmp << 8);
|
||
|
||
tmp = ((right >>> 2) ^ left) & 0x33333333;
|
||
left ^= tmp;
|
||
right ^= (tmp << 2);
|
||
|
||
tmp = ((left >>> 16) ^ right) & 0x0000ffff;
|
||
right ^= tmp;
|
||
left ^= (tmp << 16);
|
||
|
||
tmp = ((left >>> 4) ^ right) & 0x0f0f0f0f;
|
||
right ^= tmp;
|
||
left ^= (tmp << 4);
|
||
|
||
output[0] = left;
|
||
output[1] = right;
|
||
}
|
||
|
||
/**
|
||
* Deprecated. Instead, use:
|
||
*
|
||
* forge.cipher.createCipher('DES-<mode>', key);
|
||
* forge.cipher.createDecipher('DES-<mode>', key);
|
||
*
|
||
* Creates a deprecated DES cipher object. This object's mode will default to
|
||
* CBC (cipher-block-chaining).
|
||
*
|
||
* The key may be given as a binary-encoded string of bytes or a byte buffer.
|
||
*
|
||
* @param options the options to use.
|
||
* key the symmetric key to use (64 or 192 bits).
|
||
* output the buffer to write to.
|
||
* decrypt true for decryption, false for encryption.
|
||
* mode the cipher mode to use (default: 'CBC').
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
function _createCipher(options) {
|
||
options = options || {};
|
||
var mode = (options.mode || 'CBC').toUpperCase();
|
||
var algorithm = 'DES-' + mode;
|
||
|
||
var cipher;
|
||
if(options.decrypt) {
|
||
cipher = forge.cipher.createDecipher(algorithm, options.key);
|
||
} else {
|
||
cipher = forge.cipher.createCipher(algorithm, options.key);
|
||
}
|
||
|
||
// backwards compatible start API
|
||
var start = cipher.start;
|
||
cipher.start = function(iv, options) {
|
||
// backwards compatibility: support second arg as output buffer
|
||
var output = null;
|
||
if(options instanceof forge.util.ByteBuffer) {
|
||
output = options;
|
||
options = {};
|
||
}
|
||
options = options || {};
|
||
options.output = output;
|
||
options.iv = iv;
|
||
start.call(cipher, options);
|
||
};
|
||
|
||
return cipher;
|
||
}
|
||
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'des';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define(
|
||
'js/des',['require', 'module', './cipher', './cipherModes', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Password-Based Key-Derivation Function #2 implementation.
|
||
*
|
||
* See RFC 2898 for details.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var pkcs5 = forge.pkcs5 = forge.pkcs5 || {};
|
||
|
||
var _nodejs = (
|
||
typeof process !== 'undefined' && process.versions && process.versions.node);
|
||
var crypto;
|
||
if(_nodejs && !forge.disableNativeCode) {
|
||
crypto = require('crypto');
|
||
}
|
||
|
||
/**
|
||
* Derives a key from a password.
|
||
*
|
||
* @param p the password as a binary-encoded string of bytes.
|
||
* @param s the salt as a binary-encoded string of bytes.
|
||
* @param c the iteration count, a positive integer.
|
||
* @param dkLen the intended length, in bytes, of the derived key,
|
||
* (max: 2^32 - 1) * hash length of the PRF.
|
||
* @param [md] the message digest (or algorithm identifier as a string) to use
|
||
* in the PRF, defaults to SHA-1.
|
||
* @param [callback(err, key)] presence triggers asynchronous version, called
|
||
* once the operation completes.
|
||
*
|
||
* @return the derived key, as a binary-encoded string of bytes, for the
|
||
* synchronous version (if no callback is specified).
|
||
*/
|
||
forge.pbkdf2 = pkcs5.pbkdf2 = function(p, s, c, dkLen, md, callback) {
|
||
if(typeof md === 'function') {
|
||
callback = md;
|
||
md = null;
|
||
}
|
||
|
||
// use native implementation if possible and not disabled, note that
|
||
// some node versions only support SHA-1, others allow digest to be changed
|
||
if(_nodejs && !forge.disableNativeCode && crypto.pbkdf2 &&
|
||
(md === null || typeof md !== 'object') &&
|
||
(crypto.pbkdf2Sync.length > 4 || (!md || md === 'sha1'))) {
|
||
if(typeof md !== 'string') {
|
||
// default prf to SHA-1
|
||
md = 'sha1';
|
||
}
|
||
s = new Buffer(s, 'binary');
|
||
if(!callback) {
|
||
if(crypto.pbkdf2Sync.length === 4) {
|
||
return crypto.pbkdf2Sync(p, s, c, dkLen).toString('binary');
|
||
}
|
||
return crypto.pbkdf2Sync(p, s, c, dkLen, md).toString('binary');
|
||
}
|
||
if(crypto.pbkdf2Sync.length === 4) {
|
||
return crypto.pbkdf2(p, s, c, dkLen, function(err, key) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
callback(null, key.toString('binary'));
|
||
});
|
||
}
|
||
return crypto.pbkdf2(p, s, c, dkLen, md, function(err, key) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
callback(null, key.toString('binary'));
|
||
});
|
||
}
|
||
|
||
if(typeof md === 'undefined' || md === null) {
|
||
// default prf to SHA-1
|
||
md = forge.md.sha1.create();
|
||
}
|
||
if(typeof md === 'string') {
|
||
if(!(md in forge.md.algorithms)) {
|
||
throw new Error('Unknown hash algorithm: ' + md);
|
||
}
|
||
md = forge.md[md].create();
|
||
}
|
||
|
||
var hLen = md.digestLength;
|
||
|
||
/* 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
|
||
stop. */
|
||
if(dkLen > (0xFFFFFFFF * hLen)) {
|
||
var err = new Error('Derived key is too long.');
|
||
if(callback) {
|
||
return callback(err);
|
||
}
|
||
throw err;
|
||
}
|
||
|
||
/* 2. Let len be the number of hLen-octet blocks in the derived key,
|
||
rounding up, and let r be the number of octets in the last
|
||
block:
|
||
|
||
len = CEIL(dkLen / hLen),
|
||
r = dkLen - (len - 1) * hLen. */
|
||
var len = Math.ceil(dkLen / hLen);
|
||
var r = dkLen - (len - 1) * hLen;
|
||
|
||
/* 3. For each block of the derived key apply the function F defined
|
||
below to the password P, the salt S, the iteration count c, and
|
||
the block index to compute the block:
|
||
|
||
T_1 = F(P, S, c, 1),
|
||
T_2 = F(P, S, c, 2),
|
||
...
|
||
T_len = F(P, S, c, len),
|
||
|
||
where the function F is defined as the exclusive-or sum of the
|
||
first c iterates of the underlying pseudorandom function PRF
|
||
applied to the password P and the concatenation of the salt S
|
||
and the block index i:
|
||
|
||
F(P, S, c, i) = u_1 XOR u_2 XOR ... XOR u_c
|
||
|
||
where
|
||
|
||
u_1 = PRF(P, S || INT(i)),
|
||
u_2 = PRF(P, u_1),
|
||
...
|
||
u_c = PRF(P, u_{c-1}).
|
||
|
||
Here, INT(i) is a four-octet encoding of the integer i, most
|
||
significant octet first. */
|
||
var prf = forge.hmac.create();
|
||
prf.start(md, p);
|
||
var dk = '';
|
||
var xor, u_c, u_c1;
|
||
|
||
// sync version
|
||
if(!callback) {
|
||
for(var i = 1; i <= len; ++i) {
|
||
// PRF(P, S || INT(i)) (first iteration)
|
||
prf.start(null, null);
|
||
prf.update(s);
|
||
prf.update(forge.util.int32ToBytes(i));
|
||
xor = u_c1 = prf.digest().getBytes();
|
||
|
||
// PRF(P, u_{c-1}) (other iterations)
|
||
for(var j = 2; j <= c; ++j) {
|
||
prf.start(null, null);
|
||
prf.update(u_c1);
|
||
u_c = prf.digest().getBytes();
|
||
// F(p, s, c, i)
|
||
xor = forge.util.xorBytes(xor, u_c, hLen);
|
||
u_c1 = u_c;
|
||
}
|
||
|
||
/* 4. Concatenate the blocks and extract the first dkLen octets to
|
||
produce a derived key DK:
|
||
|
||
DK = T_1 || T_2 || ... || T_len<0..r-1> */
|
||
dk += (i < len) ? xor : xor.substr(0, r);
|
||
}
|
||
/* 5. Output the derived key DK. */
|
||
return dk;
|
||
}
|
||
|
||
// async version
|
||
var i = 1, j;
|
||
function outer() {
|
||
if(i > len) {
|
||
// done
|
||
return callback(null, dk);
|
||
}
|
||
|
||
// PRF(P, S || INT(i)) (first iteration)
|
||
prf.start(null, null);
|
||
prf.update(s);
|
||
prf.update(forge.util.int32ToBytes(i));
|
||
xor = u_c1 = prf.digest().getBytes();
|
||
|
||
// PRF(P, u_{c-1}) (other iterations)
|
||
j = 2;
|
||
inner();
|
||
}
|
||
|
||
function inner() {
|
||
if(j <= c) {
|
||
prf.start(null, null);
|
||
prf.update(u_c1);
|
||
u_c = prf.digest().getBytes();
|
||
// F(p, s, c, i)
|
||
xor = forge.util.xorBytes(xor, u_c, hLen);
|
||
u_c1 = u_c;
|
||
++j;
|
||
return forge.util.setImmediate(inner);
|
||
}
|
||
|
||
/* 4. Concatenate the blocks and extract the first dkLen octets to
|
||
produce a derived key DK:
|
||
|
||
DK = T_1 || T_2 || ... || T_len<0..r-1> */
|
||
dk += (i < len) ? xor : xor.substr(0, r);
|
||
|
||
++i;
|
||
outer();
|
||
}
|
||
|
||
outer();
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pbkdf2';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pbkdf2',['require', 'module', './hmac', './md', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* A javascript implementation of a cryptographically-secure
|
||
* Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed
|
||
* here though the use of SHA-256 is not enforced; when generating an
|
||
* a PRNG context, the hashing algorithm and block cipher used for
|
||
* the generator are specified via a plugin.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var _nodejs = (
|
||
typeof process !== 'undefined' && process.versions && process.versions.node);
|
||
var _crypto = null;
|
||
if(!forge.disableNativeCode && _nodejs && !process.versions['node-webkit']) {
|
||
_crypto = require('crypto');
|
||
}
|
||
|
||
/* PRNG API */
|
||
var prng = forge.prng = forge.prng || {};
|
||
|
||
/**
|
||
* Creates a new PRNG context.
|
||
*
|
||
* A PRNG plugin must be passed in that will provide:
|
||
*
|
||
* 1. A function that initializes the key and seed of a PRNG context. It
|
||
* will be given a 16 byte key and a 16 byte seed. Any key expansion
|
||
* or transformation of the seed from a byte string into an array of
|
||
* integers (or similar) should be performed.
|
||
* 2. The cryptographic function used by the generator. It takes a key and
|
||
* a seed.
|
||
* 3. A seed increment function. It takes the seed and returns seed + 1.
|
||
* 4. An api to create a message digest.
|
||
*
|
||
* For an example, see random.js.
|
||
*
|
||
* @param plugin the PRNG plugin to use.
|
||
*/
|
||
prng.create = function(plugin) {
|
||
var ctx = {
|
||
plugin: plugin,
|
||
key: null,
|
||
seed: null,
|
||
time: null,
|
||
// number of reseeds so far
|
||
reseeds: 0,
|
||
// amount of data generated so far
|
||
generated: 0
|
||
};
|
||
|
||
// create 32 entropy pools (each is a message digest)
|
||
var md = plugin.md;
|
||
var pools = new Array(32);
|
||
for(var i = 0; i < 32; ++i) {
|
||
pools[i] = md.create();
|
||
}
|
||
ctx.pools = pools;
|
||
|
||
// entropy pools are written to cyclically, starting at index 0
|
||
ctx.pool = 0;
|
||
|
||
/**
|
||
* Generates random bytes. The bytes may be generated synchronously or
|
||
* asynchronously. Web workers must use the asynchronous interface or
|
||
* else the behavior is undefined.
|
||
*
|
||
* @param count the number of random bytes to generate.
|
||
* @param [callback(err, bytes)] called once the operation completes.
|
||
*
|
||
* @return count random bytes as a string.
|
||
*/
|
||
ctx.generate = function(count, callback) {
|
||
// do synchronously
|
||
if(!callback) {
|
||
return ctx.generateSync(count);
|
||
}
|
||
|
||
// simple generator using counter-based CBC
|
||
var cipher = ctx.plugin.cipher;
|
||
var increment = ctx.plugin.increment;
|
||
var formatKey = ctx.plugin.formatKey;
|
||
var formatSeed = ctx.plugin.formatSeed;
|
||
var b = forge.util.createBuffer();
|
||
|
||
// reset key for every request
|
||
ctx.key = null;
|
||
|
||
generate();
|
||
|
||
function generate(err) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
|
||
// sufficient bytes generated
|
||
if(b.length() >= count) {
|
||
return callback(null, b.getBytes(count));
|
||
}
|
||
|
||
// if amount of data generated is greater than 1 MiB, trigger reseed
|
||
if(ctx.generated > 0xfffff) {
|
||
ctx.key = null;
|
||
}
|
||
|
||
if(ctx.key === null) {
|
||
// prevent stack overflow
|
||
return forge.util.nextTick(function() {
|
||
_reseed(generate);
|
||
});
|
||
}
|
||
|
||
// generate the random bytes
|
||
var bytes = cipher(ctx.key, ctx.seed);
|
||
ctx.generated += bytes.length;
|
||
b.putBytes(bytes);
|
||
|
||
// generate bytes for a new key and seed
|
||
ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
|
||
ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
|
||
|
||
forge.util.setImmediate(generate);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Generates random bytes synchronously.
|
||
*
|
||
* @param count the number of random bytes to generate.
|
||
*
|
||
* @return count random bytes as a string.
|
||
*/
|
||
ctx.generateSync = function(count) {
|
||
// simple generator using counter-based CBC
|
||
var cipher = ctx.plugin.cipher;
|
||
var increment = ctx.plugin.increment;
|
||
var formatKey = ctx.plugin.formatKey;
|
||
var formatSeed = ctx.plugin.formatSeed;
|
||
|
||
// reset key for every request
|
||
ctx.key = null;
|
||
|
||
var b = forge.util.createBuffer();
|
||
while(b.length() < count) {
|
||
// if amount of data generated is greater than 1 MiB, trigger reseed
|
||
if(ctx.generated > 0xfffff) {
|
||
ctx.key = null;
|
||
}
|
||
|
||
if(ctx.key === null) {
|
||
_reseedSync();
|
||
}
|
||
|
||
// generate the random bytes
|
||
var bytes = cipher(ctx.key, ctx.seed);
|
||
ctx.generated += bytes.length;
|
||
b.putBytes(bytes);
|
||
|
||
// generate bytes for a new key and seed
|
||
ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
|
||
ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
|
||
}
|
||
|
||
return b.getBytes(count);
|
||
};
|
||
|
||
/**
|
||
* Private function that asynchronously reseeds a generator.
|
||
*
|
||
* @param callback(err) called once the operation completes.
|
||
*/
|
||
function _reseed(callback) {
|
||
if(ctx.pools[0].messageLength >= 32) {
|
||
_seed();
|
||
return callback();
|
||
}
|
||
// not enough seed data...
|
||
var needed = (32 - ctx.pools[0].messageLength) << 5;
|
||
ctx.seedFile(needed, function(err, bytes) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
ctx.collect(bytes);
|
||
_seed();
|
||
callback();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Private function that synchronously reseeds a generator.
|
||
*/
|
||
function _reseedSync() {
|
||
if(ctx.pools[0].messageLength >= 32) {
|
||
return _seed();
|
||
}
|
||
// not enough seed data...
|
||
var needed = (32 - ctx.pools[0].messageLength) << 5;
|
||
ctx.collect(ctx.seedFileSync(needed));
|
||
_seed();
|
||
}
|
||
|
||
/**
|
||
* Private function that seeds a generator once enough bytes are available.
|
||
*/
|
||
function _seed() {
|
||
// create a plugin-based message digest
|
||
var md = ctx.plugin.md.create();
|
||
|
||
// digest pool 0's entropy and restart it
|
||
md.update(ctx.pools[0].digest().getBytes());
|
||
ctx.pools[0].start();
|
||
|
||
// digest the entropy of other pools whose index k meet the
|
||
// condition '2^k mod n == 0' where n is the number of reseeds
|
||
var k = 1;
|
||
for(var i = 1; i < 32; ++i) {
|
||
// prevent signed numbers from being used
|
||
k = (k === 31) ? 0x80000000 : (k << 2);
|
||
if(k % ctx.reseeds === 0) {
|
||
md.update(ctx.pools[i].digest().getBytes());
|
||
ctx.pools[i].start();
|
||
}
|
||
}
|
||
|
||
// get digest for key bytes and iterate again for seed bytes
|
||
var keyBytes = md.digest().getBytes();
|
||
md.start();
|
||
md.update(keyBytes);
|
||
var seedBytes = md.digest().getBytes();
|
||
|
||
// update
|
||
ctx.key = ctx.plugin.formatKey(keyBytes);
|
||
ctx.seed = ctx.plugin.formatSeed(seedBytes);
|
||
ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1;
|
||
ctx.generated = 0;
|
||
}
|
||
|
||
/**
|
||
* The built-in default seedFile. This seedFile is used when entropy
|
||
* is needed immediately.
|
||
*
|
||
* @param needed the number of bytes that are needed.
|
||
*
|
||
* @return the random bytes.
|
||
*/
|
||
function defaultSeedFile(needed) {
|
||
// use window.crypto.getRandomValues strong source of entropy if available
|
||
var getRandomValues = null;
|
||
if(typeof window !== 'undefined') {
|
||
var _crypto = window.crypto || window.msCrypto;
|
||
if(_crypto && _crypto.getRandomValues) {
|
||
getRandomValues = function(arr) {
|
||
return _crypto.getRandomValues(arr);
|
||
};
|
||
}
|
||
}
|
||
|
||
var b = forge.util.createBuffer();
|
||
if(getRandomValues) {
|
||
while(b.length() < needed) {
|
||
// max byte length is 65536 before QuotaExceededError is thrown
|
||
// http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
|
||
var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4);
|
||
var entropy = new Uint32Array(Math.floor(count));
|
||
try {
|
||
getRandomValues(entropy);
|
||
for(var i = 0; i < entropy.length; ++i) {
|
||
b.putInt32(entropy[i]);
|
||
}
|
||
} catch(e) {
|
||
/* only ignore QuotaExceededError */
|
||
if(!(typeof QuotaExceededError !== 'undefined' &&
|
||
e instanceof QuotaExceededError)) {
|
||
throw e;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// be sad and add some weak random data
|
||
if(b.length() < needed) {
|
||
/* Draws from Park-Miller "minimal standard" 31 bit PRNG,
|
||
implemented with David G. Carta's optimization: with 32 bit math
|
||
and without division (Public Domain). */
|
||
var hi, lo, next;
|
||
var seed = Math.floor(Math.random() * 0x010000);
|
||
while(b.length() < needed) {
|
||
lo = 16807 * (seed & 0xFFFF);
|
||
hi = 16807 * (seed >> 16);
|
||
lo += (hi & 0x7FFF) << 16;
|
||
lo += hi >> 15;
|
||
lo = (lo & 0x7FFFFFFF) + (lo >> 31);
|
||
seed = lo & 0xFFFFFFFF;
|
||
|
||
// consume lower 3 bytes of seed
|
||
for(var i = 0; i < 3; ++i) {
|
||
// throw in more pseudo random
|
||
next = seed >>> (i << 3);
|
||
next ^= Math.floor(Math.random() * 0x0100);
|
||
b.putByte(String.fromCharCode(next & 0xFF));
|
||
}
|
||
}
|
||
}
|
||
|
||
return b.getBytes(needed);
|
||
}
|
||
// initialize seed file APIs
|
||
if(_crypto) {
|
||
// use nodejs async API
|
||
ctx.seedFile = function(needed, callback) {
|
||
_crypto.randomBytes(needed, function(err, bytes) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
callback(null, bytes.toString());
|
||
});
|
||
};
|
||
// use nodejs sync API
|
||
ctx.seedFileSync = function(needed) {
|
||
return _crypto.randomBytes(needed).toString();
|
||
};
|
||
} else {
|
||
ctx.seedFile = function(needed, callback) {
|
||
try {
|
||
callback(null, defaultSeedFile(needed));
|
||
} catch(e) {
|
||
callback(e);
|
||
}
|
||
};
|
||
ctx.seedFileSync = defaultSeedFile;
|
||
}
|
||
|
||
/**
|
||
* Adds entropy to a prng ctx's accumulator.
|
||
*
|
||
* @param bytes the bytes of entropy as a string.
|
||
*/
|
||
ctx.collect = function(bytes) {
|
||
// iterate over pools distributing entropy cyclically
|
||
var count = bytes.length;
|
||
for(var i = 0; i < count; ++i) {
|
||
ctx.pools[ctx.pool].update(bytes.substr(i, 1));
|
||
ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Collects an integer of n bits.
|
||
*
|
||
* @param i the integer entropy.
|
||
* @param n the number of bits in the integer.
|
||
*/
|
||
ctx.collectInt = function(i, n) {
|
||
var bytes = '';
|
||
for(var x = 0; x < n; x += 8) {
|
||
bytes += String.fromCharCode((i >> x) & 0xFF);
|
||
}
|
||
ctx.collect(bytes);
|
||
};
|
||
|
||
/**
|
||
* Registers a Web Worker to receive immediate entropy from the main thread.
|
||
* This method is required until Web Workers can access the native crypto
|
||
* API. This method should be called twice for each created worker, once in
|
||
* the main thread, and once in the worker itself.
|
||
*
|
||
* @param worker the worker to register.
|
||
*/
|
||
ctx.registerWorker = function(worker) {
|
||
// worker receives random bytes
|
||
if(worker === self) {
|
||
ctx.seedFile = function(needed, callback) {
|
||
function listener(e) {
|
||
var data = e.data;
|
||
if(data.forge && data.forge.prng) {
|
||
self.removeEventListener('message', listener);
|
||
callback(data.forge.prng.err, data.forge.prng.bytes);
|
||
}
|
||
}
|
||
self.addEventListener('message', listener);
|
||
self.postMessage({forge: {prng: {needed: needed}}});
|
||
};
|
||
} else {
|
||
// main thread sends random bytes upon request
|
||
var listener = function(e) {
|
||
var data = e.data;
|
||
if(data.forge && data.forge.prng) {
|
||
ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
|
||
worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
|
||
});
|
||
}
|
||
};
|
||
// TODO: do we need to remove the event listener when the worker dies?
|
||
worker.addEventListener('message', listener);
|
||
}
|
||
};
|
||
|
||
return ctx;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'prng';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/prng',['require', 'module', './md', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
|
||
})();
|
||
|
||
/**
|
||
* An API for getting cryptographically-secure random bytes. The bytes are
|
||
* generated using the Fortuna algorithm devised by Bruce Schneier and
|
||
* Niels Ferguson.
|
||
*
|
||
* Getting strong random bytes is not yet easy to do in javascript. The only
|
||
* truish random entropy that can be collected is from the mouse, keyboard, or
|
||
* from timing with respect to page loads, etc. This generator makes a poor
|
||
* attempt at providing random bytes when those sources haven't yet provided
|
||
* enough entropy to initially seed or to reseed the PRNG.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2009-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// forge.random already defined
|
||
if(forge.random && forge.random.getBytes) {
|
||
return;
|
||
}
|
||
|
||
(function(jQuery) {
|
||
|
||
// the default prng plugin, uses AES-128
|
||
var prng_aes = {};
|
||
var _prng_aes_output = new Array(4);
|
||
var _prng_aes_buffer = forge.util.createBuffer();
|
||
prng_aes.formatKey = function(key) {
|
||
// convert the key into 32-bit integers
|
||
var tmp = forge.util.createBuffer(key);
|
||
key = new Array(4);
|
||
key[0] = tmp.getInt32();
|
||
key[1] = tmp.getInt32();
|
||
key[2] = tmp.getInt32();
|
||
key[3] = tmp.getInt32();
|
||
|
||
// return the expanded key
|
||
return forge.aes._expandKey(key, false);
|
||
};
|
||
prng_aes.formatSeed = function(seed) {
|
||
// convert seed into 32-bit integers
|
||
var tmp = forge.util.createBuffer(seed);
|
||
seed = new Array(4);
|
||
seed[0] = tmp.getInt32();
|
||
seed[1] = tmp.getInt32();
|
||
seed[2] = tmp.getInt32();
|
||
seed[3] = tmp.getInt32();
|
||
return seed;
|
||
};
|
||
prng_aes.cipher = function(key, seed) {
|
||
forge.aes._updateBlock(key, seed, _prng_aes_output, false);
|
||
_prng_aes_buffer.putInt32(_prng_aes_output[0]);
|
||
_prng_aes_buffer.putInt32(_prng_aes_output[1]);
|
||
_prng_aes_buffer.putInt32(_prng_aes_output[2]);
|
||
_prng_aes_buffer.putInt32(_prng_aes_output[3]);
|
||
return _prng_aes_buffer.getBytes();
|
||
};
|
||
prng_aes.increment = function(seed) {
|
||
// FIXME: do we care about carry or signed issues?
|
||
++seed[3];
|
||
return seed;
|
||
};
|
||
prng_aes.md = forge.md.sha256;
|
||
|
||
/**
|
||
* Creates a new PRNG.
|
||
*/
|
||
function spawnPrng() {
|
||
var ctx = forge.prng.create(prng_aes);
|
||
|
||
/**
|
||
* Gets random bytes. If a native secure crypto API is unavailable, this
|
||
* method tries to make the bytes more unpredictable by drawing from data that
|
||
* can be collected from the user of the browser, eg: mouse movement.
|
||
*
|
||
* If a callback is given, this method will be called asynchronously.
|
||
*
|
||
* @param count the number of random bytes to get.
|
||
* @param [callback(err, bytes)] called once the operation completes.
|
||
*
|
||
* @return the random bytes in a string.
|
||
*/
|
||
ctx.getBytes = function(count, callback) {
|
||
return ctx.generate(count, callback);
|
||
};
|
||
|
||
/**
|
||
* Gets random bytes asynchronously. If a native secure crypto API is
|
||
* unavailable, this method tries to make the bytes more unpredictable by
|
||
* drawing from data that can be collected from the user of the browser,
|
||
* eg: mouse movement.
|
||
*
|
||
* @param count the number of random bytes to get.
|
||
*
|
||
* @return the random bytes in a string.
|
||
*/
|
||
ctx.getBytesSync = function(count) {
|
||
return ctx.generate(count);
|
||
};
|
||
|
||
return ctx;
|
||
}
|
||
|
||
// create default prng context
|
||
var _ctx = spawnPrng();
|
||
|
||
// add other sources of entropy only if window.crypto.getRandomValues is not
|
||
// available -- otherwise this source will be automatically used by the prng
|
||
var _nodejs = (
|
||
typeof process !== 'undefined' && process.versions && process.versions.node);
|
||
var getRandomValues = null;
|
||
if(typeof window !== 'undefined') {
|
||
var _crypto = window.crypto || window.msCrypto;
|
||
if(_crypto && _crypto.getRandomValues) {
|
||
getRandomValues = function(arr) {
|
||
return _crypto.getRandomValues(arr);
|
||
};
|
||
}
|
||
}
|
||
if(forge.disableNativeCode || (!_nodejs && !getRandomValues)) {
|
||
// if this is a web worker, do not use weak entropy, instead register to
|
||
// receive strong entropy asynchronously from the main thread
|
||
if(typeof window === 'undefined' || window.document === undefined) {
|
||
// FIXME:
|
||
}
|
||
|
||
// get load time entropy
|
||
_ctx.collectInt(+new Date(), 32);
|
||
|
||
// add some entropy from navigator object
|
||
if(typeof(navigator) !== 'undefined') {
|
||
var _navBytes = '';
|
||
for(var key in navigator) {
|
||
try {
|
||
if(typeof(navigator[key]) == 'string') {
|
||
_navBytes += navigator[key];
|
||
}
|
||
} catch(e) {
|
||
/* Some navigator keys might not be accessible, e.g. the geolocation
|
||
attribute throws an exception if touched in Mozilla chrome://
|
||
context.
|
||
|
||
Silently ignore this and just don't use this as a source of
|
||
entropy. */
|
||
}
|
||
}
|
||
_ctx.collect(_navBytes);
|
||
_navBytes = null;
|
||
}
|
||
|
||
// add mouse and keyboard collectors if jquery is available
|
||
if(jQuery) {
|
||
// set up mouse entropy capture
|
||
jQuery().mousemove(function(e) {
|
||
// add mouse coords
|
||
_ctx.collectInt(e.clientX, 16);
|
||
_ctx.collectInt(e.clientY, 16);
|
||
});
|
||
|
||
// set up keyboard entropy capture
|
||
jQuery().keypress(function(e) {
|
||
_ctx.collectInt(e.charCode, 8);
|
||
});
|
||
}
|
||
}
|
||
|
||
/* Random API */
|
||
if(!forge.random) {
|
||
forge.random = _ctx;
|
||
} else {
|
||
// extend forge.random with _ctx
|
||
for(var key in _ctx) {
|
||
forge.random[key] = _ctx[key];
|
||
}
|
||
}
|
||
|
||
// expose spawn PRNG
|
||
forge.random.createInstance = spawnPrng;
|
||
|
||
})(typeof(jQuery) !== 'undefined' ? jQuery : null);
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'random';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/random',['require', 'module', './aes', './md', './prng', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* RC2 implementation.
|
||
*
|
||
* @author Stefan Siegl
|
||
*
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* Information on the RC2 cipher is available from RFC #2268,
|
||
* http://www.ietf.org/rfc/rfc2268.txt
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var piTable = [
|
||
0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
|
||
0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
|
||
0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
|
||
0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
|
||
0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
|
||
0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
|
||
0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
|
||
0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
|
||
0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
|
||
0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
|
||
0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
|
||
0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
|
||
0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
|
||
0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
|
||
0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
|
||
0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad
|
||
];
|
||
|
||
var s = [1, 2, 3, 5];
|
||
|
||
|
||
/**
|
||
* Rotate a word left by given number of bits.
|
||
*
|
||
* Bits that are shifted out on the left are put back in on the right
|
||
* hand side.
|
||
*
|
||
* @param word The word to shift left.
|
||
* @param bits The number of bits to shift by.
|
||
* @return The rotated word.
|
||
*/
|
||
var rol = function(word, bits) {
|
||
return ((word << bits) & 0xffff) | ((word & 0xffff) >> (16 - bits));
|
||
};
|
||
|
||
/**
|
||
* Rotate a word right by given number of bits.
|
||
*
|
||
* Bits that are shifted out on the right are put back in on the left
|
||
* hand side.
|
||
*
|
||
* @param word The word to shift right.
|
||
* @param bits The number of bits to shift by.
|
||
* @return The rotated word.
|
||
*/
|
||
var ror = function(word, bits) {
|
||
return ((word & 0xffff) >> bits) | ((word << (16 - bits)) & 0xffff);
|
||
};
|
||
|
||
|
||
/* RC2 API */
|
||
forge.rc2 = forge.rc2 || {};
|
||
|
||
/**
|
||
* Perform RC2 key expansion as per RFC #2268, section 2.
|
||
*
|
||
* @param key variable-length user key (between 1 and 128 bytes)
|
||
* @param effKeyBits number of effective key bits (default: 128)
|
||
* @return the expanded RC2 key (ByteBuffer of 128 bytes)
|
||
*/
|
||
forge.rc2.expandKey = function(key, effKeyBits) {
|
||
if(typeof key === 'string') {
|
||
key = forge.util.createBuffer(key);
|
||
}
|
||
effKeyBits = effKeyBits || 128;
|
||
|
||
/* introduce variables that match the names used in RFC #2268 */
|
||
var L = key;
|
||
var T = key.length();
|
||
var T1 = effKeyBits;
|
||
var T8 = Math.ceil(T1 / 8);
|
||
var TM = 0xff >> (T1 & 0x07);
|
||
var i;
|
||
|
||
for(i = T; i < 128; i ++) {
|
||
L.putByte(piTable[(L.at(i - 1) + L.at(i - T)) & 0xff]);
|
||
}
|
||
|
||
L.setAt(128 - T8, piTable[L.at(128 - T8) & TM]);
|
||
|
||
for(i = 127 - T8; i >= 0; i --) {
|
||
L.setAt(i, piTable[L.at(i + 1) ^ L.at(i + T8)]);
|
||
}
|
||
|
||
return L;
|
||
};
|
||
|
||
|
||
/**
|
||
* Creates a RC2 cipher object.
|
||
*
|
||
* @param key the symmetric key to use (as base for key generation).
|
||
* @param bits the number of effective key bits.
|
||
* @param encrypt false for decryption, true for encryption.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
var createCipher = function(key, bits, encrypt) {
|
||
var _finish = false, _input = null, _output = null, _iv = null;
|
||
var mixRound, mashRound;
|
||
var i, j, K = [];
|
||
|
||
/* Expand key and fill into K[] Array */
|
||
key = forge.rc2.expandKey(key, bits);
|
||
for(i = 0; i < 64; i ++) {
|
||
K.push(key.getInt16Le());
|
||
}
|
||
|
||
if(encrypt) {
|
||
/**
|
||
* Perform one mixing round "in place".
|
||
*
|
||
* @param R Array of four words to perform mixing on.
|
||
*/
|
||
mixRound = function(R) {
|
||
for(i = 0; i < 4; i++) {
|
||
R[i] += K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
|
||
((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
|
||
R[i] = rol(R[i], s[i]);
|
||
j ++;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Perform one mashing round "in place".
|
||
*
|
||
* @param R Array of four words to perform mashing on.
|
||
*/
|
||
mashRound = function(R) {
|
||
for(i = 0; i < 4; i ++) {
|
||
R[i] += K[R[(i + 3) % 4] & 63];
|
||
}
|
||
};
|
||
} else {
|
||
/**
|
||
* Perform one r-mixing round "in place".
|
||
*
|
||
* @param R Array of four words to perform mixing on.
|
||
*/
|
||
mixRound = function(R) {
|
||
for(i = 3; i >= 0; i--) {
|
||
R[i] = ror(R[i], s[i]);
|
||
R[i] -= K[j] + (R[(i + 3) % 4] & R[(i + 2) % 4]) +
|
||
((~R[(i + 3) % 4]) & R[(i + 1) % 4]);
|
||
j --;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Perform one r-mashing round "in place".
|
||
*
|
||
* @param R Array of four words to perform mashing on.
|
||
*/
|
||
mashRound = function(R) {
|
||
for(i = 3; i >= 0; i--) {
|
||
R[i] -= K[R[(i + 3) % 4] & 63];
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Run the specified cipher execution plan.
|
||
*
|
||
* This function takes four words from the input buffer, applies the IV on
|
||
* it (if requested) and runs the provided execution plan.
|
||
*
|
||
* The plan must be put together in form of a array of arrays. Where the
|
||
* outer one is simply a list of steps to perform and the inner one needs
|
||
* to have two elements: the first one telling how many rounds to perform,
|
||
* the second one telling what to do (i.e. the function to call).
|
||
*
|
||
* @param {Array} plan The plan to execute.
|
||
*/
|
||
var runPlan = function(plan) {
|
||
var R = [];
|
||
|
||
/* Get data from input buffer and fill the four words into R */
|
||
for(i = 0; i < 4; i ++) {
|
||
var val = _input.getInt16Le();
|
||
|
||
if(_iv !== null) {
|
||
if(encrypt) {
|
||
/* We're encrypting, apply the IV first. */
|
||
val ^= _iv.getInt16Le();
|
||
} else {
|
||
/* We're decryption, keep cipher text for next block. */
|
||
_iv.putInt16Le(val);
|
||
}
|
||
}
|
||
|
||
R.push(val & 0xffff);
|
||
}
|
||
|
||
/* Reset global "j" variable as per spec. */
|
||
j = encrypt ? 0 : 63;
|
||
|
||
/* Run execution plan. */
|
||
for(var ptr = 0; ptr < plan.length; ptr ++) {
|
||
for(var ctr = 0; ctr < plan[ptr][0]; ctr ++) {
|
||
plan[ptr][1](R);
|
||
}
|
||
}
|
||
|
||
/* Write back result to output buffer. */
|
||
for(i = 0; i < 4; i ++) {
|
||
if(_iv !== null) {
|
||
if(encrypt) {
|
||
/* We're encrypting in CBC-mode, feed back encrypted bytes into
|
||
IV buffer to carry it forward to next block. */
|
||
_iv.putInt16Le(R[i]);
|
||
} else {
|
||
R[i] ^= _iv.getInt16Le();
|
||
}
|
||
}
|
||
|
||
_output.putInt16Le(R[i]);
|
||
}
|
||
};
|
||
|
||
|
||
/* Create cipher object */
|
||
var cipher = null;
|
||
cipher = {
|
||
/**
|
||
* Starts or restarts the encryption or decryption process, whichever
|
||
* was previously configured.
|
||
*
|
||
* To use the cipher in CBC mode, iv may be given either as a string
|
||
* of bytes, or as a byte buffer. For ECB mode, give null as iv.
|
||
*
|
||
* @param iv the initialization vector to use, null for ECB mode.
|
||
* @param output the output the buffer to write to, null to create one.
|
||
*/
|
||
start: function(iv, output) {
|
||
if(iv) {
|
||
/* CBC mode */
|
||
if(typeof iv === 'string') {
|
||
iv = forge.util.createBuffer(iv);
|
||
}
|
||
}
|
||
|
||
_finish = false;
|
||
_input = forge.util.createBuffer();
|
||
_output = output || new forge.util.createBuffer();
|
||
_iv = iv;
|
||
|
||
cipher.output = _output;
|
||
},
|
||
|
||
/**
|
||
* Updates the next block.
|
||
*
|
||
* @param input the buffer to read from.
|
||
*/
|
||
update: function(input) {
|
||
if(!_finish) {
|
||
// not finishing, so fill the input buffer with more input
|
||
_input.putBuffer(input);
|
||
}
|
||
|
||
while(_input.length() >= 8) {
|
||
runPlan([
|
||
[ 5, mixRound ],
|
||
[ 1, mashRound ],
|
||
[ 6, mixRound ],
|
||
[ 1, mashRound ],
|
||
[ 5, mixRound ]
|
||
]);
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Finishes encrypting or decrypting.
|
||
*
|
||
* @param pad a padding function to use, null for PKCS#7 padding,
|
||
* signature(blockSize, buffer, decrypt).
|
||
*
|
||
* @return true if successful, false on error.
|
||
*/
|
||
finish: function(pad) {
|
||
var rval = true;
|
||
|
||
if(encrypt) {
|
||
if(pad) {
|
||
rval = pad(8, _input, !encrypt);
|
||
} else {
|
||
// add PKCS#7 padding to block (each pad byte is the
|
||
// value of the number of pad bytes)
|
||
var padding = (_input.length() === 8) ? 8 : (8 - _input.length());
|
||
_input.fillWithByte(padding, padding);
|
||
}
|
||
}
|
||
|
||
if(rval) {
|
||
// do final update
|
||
_finish = true;
|
||
cipher.update();
|
||
}
|
||
|
||
if(!encrypt) {
|
||
// check for error: input data not a multiple of block size
|
||
rval = (_input.length() === 0);
|
||
if(rval) {
|
||
if(pad) {
|
||
rval = pad(8, _output, !encrypt);
|
||
} else {
|
||
// ensure padding byte count is valid
|
||
var len = _output.length();
|
||
var count = _output.at(len - 1);
|
||
|
||
if(count > len) {
|
||
rval = false;
|
||
} else {
|
||
// trim off padding bytes
|
||
_output.truncate(count);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
};
|
||
|
||
return cipher;
|
||
};
|
||
|
||
|
||
/**
|
||
* Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
|
||
* given symmetric key. The output will be stored in the 'output' member
|
||
* of the returned cipher.
|
||
*
|
||
* The key and iv may be given as a string of bytes or a byte buffer.
|
||
* The cipher is initialized to use 128 effective key bits.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.rc2.startEncrypting = function(key, iv, output) {
|
||
var cipher = forge.rc2.createEncryptionCipher(key, 128);
|
||
cipher.start(iv, output);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Creates an RC2 cipher object to encrypt data in ECB or CBC mode using the
|
||
* given symmetric key.
|
||
*
|
||
* The key may be given as a string of bytes or a byte buffer.
|
||
*
|
||
* To start encrypting call start() on the cipher with an iv and optional
|
||
* output buffer.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.rc2.createEncryptionCipher = function(key, bits) {
|
||
return createCipher(key, bits, true);
|
||
};
|
||
|
||
/**
|
||
* Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
|
||
* given symmetric key. The output will be stored in the 'output' member
|
||
* of the returned cipher.
|
||
*
|
||
* The key and iv may be given as a string of bytes or a byte buffer.
|
||
* The cipher is initialized to use 128 effective key bits.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
* @param iv the initialization vector to use.
|
||
* @param output the buffer to write to, null to create one.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.rc2.startDecrypting = function(key, iv, output) {
|
||
var cipher = forge.rc2.createDecryptionCipher(key, 128);
|
||
cipher.start(iv, output);
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Creates an RC2 cipher object to decrypt data in ECB or CBC mode using the
|
||
* given symmetric key.
|
||
*
|
||
* The key may be given as a string of bytes or a byte buffer.
|
||
*
|
||
* To start decrypting call start() on the cipher with an iv and optional
|
||
* output buffer.
|
||
*
|
||
* @param key the symmetric key to use.
|
||
*
|
||
* @return the cipher.
|
||
*/
|
||
forge.rc2.createDecryptionCipher = function(key, bits) {
|
||
return createCipher(key, bits, false);
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'rc2';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/rc2',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
// Copyright (c) 2005 Tom Wu
|
||
// All Rights Reserved.
|
||
// See "LICENSE" for details.
|
||
|
||
// Basic JavaScript BN library - subset useful for RSA encryption.
|
||
|
||
/*
|
||
Licensing (LICENSE)
|
||
-------------------
|
||
|
||
This software is covered under the following copyright:
|
||
*/
|
||
/*
|
||
* Copyright (c) 2003-2005 Tom Wu
|
||
* All Rights Reserved.
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining
|
||
* a copy of this software and associated documentation files (the
|
||
* "Software"), to deal in the Software without restriction, including
|
||
* without limitation the rights to use, copy, modify, merge, publish,
|
||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||
* permit persons to whom the Software is furnished to do so, subject to
|
||
* the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be
|
||
* included in all copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||
*
|
||
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*
|
||
* In addition, the following condition applies:
|
||
*
|
||
* All redistributions must retain an intact copy of this copyright notice
|
||
* and disclaimer.
|
||
*/
|
||
/*
|
||
Address all questions regarding this license to:
|
||
|
||
Tom Wu
|
||
tjw@cs.Stanford.EDU
|
||
*/
|
||
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// Bits per digit
|
||
var dbits;
|
||
|
||
// JavaScript engine analysis
|
||
var canary = 0xdeadbeefcafe;
|
||
var j_lm = ((canary&0xffffff)==0xefcafe);
|
||
|
||
// (public) Constructor
|
||
function BigInteger(a,b,c) {
|
||
this.data = [];
|
||
if(a != null)
|
||
if("number" == typeof a) this.fromNumber(a,b,c);
|
||
else if(b == null && "string" != typeof a) this.fromString(a,256);
|
||
else this.fromString(a,b);
|
||
}
|
||
|
||
// return new, unset BigInteger
|
||
function nbi() { return new BigInteger(null); }
|
||
|
||
// am: Compute w_j += (x*this_i), propagate carries,
|
||
// c is initial carry, returns final carry.
|
||
// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
|
||
// We need to select the fastest one that works in this environment.
|
||
|
||
// am1: use a single mult and divide to get the high bits,
|
||
// max digit bits should be 26 because
|
||
// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
|
||
function am1(i,x,w,j,c,n) {
|
||
while(--n >= 0) {
|
||
var v = x*this.data[i++]+w.data[j]+c;
|
||
c = Math.floor(v/0x4000000);
|
||
w.data[j++] = v&0x3ffffff;
|
||
}
|
||
return c;
|
||
}
|
||
// am2 avoids a big mult-and-extract completely.
|
||
// Max digit bits should be <= 30 because we do bitwise ops
|
||
// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
|
||
function am2(i,x,w,j,c,n) {
|
||
var xl = x&0x7fff, xh = x>>15;
|
||
while(--n >= 0) {
|
||
var l = this.data[i]&0x7fff;
|
||
var h = this.data[i++]>>15;
|
||
var m = xh*l+h*xl;
|
||
l = xl*l+((m&0x7fff)<<15)+w.data[j]+(c&0x3fffffff);
|
||
c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
|
||
w.data[j++] = l&0x3fffffff;
|
||
}
|
||
return c;
|
||
}
|
||
// Alternately, set max digit bits to 28 since some
|
||
// browsers slow down when dealing with 32-bit numbers.
|
||
function am3(i,x,w,j,c,n) {
|
||
var xl = x&0x3fff, xh = x>>14;
|
||
while(--n >= 0) {
|
||
var l = this.data[i]&0x3fff;
|
||
var h = this.data[i++]>>14;
|
||
var m = xh*l+h*xl;
|
||
l = xl*l+((m&0x3fff)<<14)+w.data[j]+c;
|
||
c = (l>>28)+(m>>14)+xh*h;
|
||
w.data[j++] = l&0xfffffff;
|
||
}
|
||
return c;
|
||
}
|
||
|
||
// node.js (no browser)
|
||
if(typeof(navigator) === 'undefined')
|
||
{
|
||
BigInteger.prototype.am = am3;
|
||
dbits = 28;
|
||
} else if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
|
||
BigInteger.prototype.am = am2;
|
||
dbits = 30;
|
||
} else if(j_lm && (navigator.appName != "Netscape")) {
|
||
BigInteger.prototype.am = am1;
|
||
dbits = 26;
|
||
} else { // Mozilla/Netscape seems to prefer am3
|
||
BigInteger.prototype.am = am3;
|
||
dbits = 28;
|
||
}
|
||
|
||
BigInteger.prototype.DB = dbits;
|
||
BigInteger.prototype.DM = ((1<<dbits)-1);
|
||
BigInteger.prototype.DV = (1<<dbits);
|
||
|
||
var BI_FP = 52;
|
||
BigInteger.prototype.FV = Math.pow(2,BI_FP);
|
||
BigInteger.prototype.F1 = BI_FP-dbits;
|
||
BigInteger.prototype.F2 = 2*dbits-BI_FP;
|
||
|
||
// Digit conversions
|
||
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||
var BI_RC = new Array();
|
||
var rr,vv;
|
||
rr = "0".charCodeAt(0);
|
||
for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
|
||
rr = "a".charCodeAt(0);
|
||
for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
|
||
rr = "A".charCodeAt(0);
|
||
for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
|
||
|
||
function int2char(n) { return BI_RM.charAt(n); }
|
||
function intAt(s,i) {
|
||
var c = BI_RC[s.charCodeAt(i)];
|
||
return (c==null)?-1:c;
|
||
}
|
||
|
||
// (protected) copy this to r
|
||
function bnpCopyTo(r) {
|
||
for(var i = this.t-1; i >= 0; --i) r.data[i] = this.data[i];
|
||
r.t = this.t;
|
||
r.s = this.s;
|
||
}
|
||
|
||
// (protected) set from integer value x, -DV <= x < DV
|
||
function bnpFromInt(x) {
|
||
this.t = 1;
|
||
this.s = (x<0)?-1:0;
|
||
if(x > 0) this.data[0] = x;
|
||
else if(x < -1) this.data[0] = x+this.DV;
|
||
else this.t = 0;
|
||
}
|
||
|
||
// return bigint initialized to value
|
||
function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
|
||
|
||
// (protected) set from string and radix
|
||
function bnpFromString(s,b) {
|
||
var k;
|
||
if(b == 16) k = 4;
|
||
else if(b == 8) k = 3;
|
||
else if(b == 256) k = 8; // byte array
|
||
else if(b == 2) k = 1;
|
||
else if(b == 32) k = 5;
|
||
else if(b == 4) k = 2;
|
||
else { this.fromRadix(s,b); return; }
|
||
this.t = 0;
|
||
this.s = 0;
|
||
var i = s.length, mi = false, sh = 0;
|
||
while(--i >= 0) {
|
||
var x = (k==8)?s[i]&0xff:intAt(s,i);
|
||
if(x < 0) {
|
||
if(s.charAt(i) == "-") mi = true;
|
||
continue;
|
||
}
|
||
mi = false;
|
||
if(sh == 0)
|
||
this.data[this.t++] = x;
|
||
else if(sh+k > this.DB) {
|
||
this.data[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
|
||
this.data[this.t++] = (x>>(this.DB-sh));
|
||
} else
|
||
this.data[this.t-1] |= x<<sh;
|
||
sh += k;
|
||
if(sh >= this.DB) sh -= this.DB;
|
||
}
|
||
if(k == 8 && (s[0]&0x80) != 0) {
|
||
this.s = -1;
|
||
if(sh > 0) this.data[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
|
||
}
|
||
this.clamp();
|
||
if(mi) BigInteger.ZERO.subTo(this,this);
|
||
}
|
||
|
||
// (protected) clamp off excess high words
|
||
function bnpClamp() {
|
||
var c = this.s&this.DM;
|
||
while(this.t > 0 && this.data[this.t-1] == c) --this.t;
|
||
}
|
||
|
||
// (public) return string representation in given radix
|
||
function bnToString(b) {
|
||
if(this.s < 0) return "-"+this.negate().toString(b);
|
||
var k;
|
||
if(b == 16) k = 4;
|
||
else if(b == 8) k = 3;
|
||
else if(b == 2) k = 1;
|
||
else if(b == 32) k = 5;
|
||
else if(b == 4) k = 2;
|
||
else return this.toRadix(b);
|
||
var km = (1<<k)-1, d, m = false, r = "", i = this.t;
|
||
var p = this.DB-(i*this.DB)%k;
|
||
if(i-- > 0) {
|
||
if(p < this.DB && (d = this.data[i]>>p) > 0) { m = true; r = int2char(d); }
|
||
while(i >= 0) {
|
||
if(p < k) {
|
||
d = (this.data[i]&((1<<p)-1))<<(k-p);
|
||
d |= this.data[--i]>>(p+=this.DB-k);
|
||
} else {
|
||
d = (this.data[i]>>(p-=k))&km;
|
||
if(p <= 0) { p += this.DB; --i; }
|
||
}
|
||
if(d > 0) m = true;
|
||
if(m) r += int2char(d);
|
||
}
|
||
}
|
||
return m?r:"0";
|
||
}
|
||
|
||
// (public) -this
|
||
function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
|
||
|
||
// (public) |this|
|
||
function bnAbs() { return (this.s<0)?this.negate():this; }
|
||
|
||
// (public) return + if this > a, - if this < a, 0 if equal
|
||
function bnCompareTo(a) {
|
||
var r = this.s-a.s;
|
||
if(r != 0) return r;
|
||
var i = this.t;
|
||
r = i-a.t;
|
||
if(r != 0) return (this.s<0)?-r:r;
|
||
while(--i >= 0) if((r=this.data[i]-a.data[i]) != 0) return r;
|
||
return 0;
|
||
}
|
||
|
||
// returns bit length of the integer x
|
||
function nbits(x) {
|
||
var r = 1, t;
|
||
if((t=x>>>16) != 0) { x = t; r += 16; }
|
||
if((t=x>>8) != 0) { x = t; r += 8; }
|
||
if((t=x>>4) != 0) { x = t; r += 4; }
|
||
if((t=x>>2) != 0) { x = t; r += 2; }
|
||
if((t=x>>1) != 0) { x = t; r += 1; }
|
||
return r;
|
||
}
|
||
|
||
// (public) return the number of bits in "this"
|
||
function bnBitLength() {
|
||
if(this.t <= 0) return 0;
|
||
return this.DB*(this.t-1)+nbits(this.data[this.t-1]^(this.s&this.DM));
|
||
}
|
||
|
||
// (protected) r = this << n*DB
|
||
function bnpDLShiftTo(n,r) {
|
||
var i;
|
||
for(i = this.t-1; i >= 0; --i) r.data[i+n] = this.data[i];
|
||
for(i = n-1; i >= 0; --i) r.data[i] = 0;
|
||
r.t = this.t+n;
|
||
r.s = this.s;
|
||
}
|
||
|
||
// (protected) r = this >> n*DB
|
||
function bnpDRShiftTo(n,r) {
|
||
for(var i = n; i < this.t; ++i) r.data[i-n] = this.data[i];
|
||
r.t = Math.max(this.t-n,0);
|
||
r.s = this.s;
|
||
}
|
||
|
||
// (protected) r = this << n
|
||
function bnpLShiftTo(n,r) {
|
||
var bs = n%this.DB;
|
||
var cbs = this.DB-bs;
|
||
var bm = (1<<cbs)-1;
|
||
var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
|
||
for(i = this.t-1; i >= 0; --i) {
|
||
r.data[i+ds+1] = (this.data[i]>>cbs)|c;
|
||
c = (this.data[i]&bm)<<bs;
|
||
}
|
||
for(i = ds-1; i >= 0; --i) r.data[i] = 0;
|
||
r.data[ds] = c;
|
||
r.t = this.t+ds+1;
|
||
r.s = this.s;
|
||
r.clamp();
|
||
}
|
||
|
||
// (protected) r = this >> n
|
||
function bnpRShiftTo(n,r) {
|
||
r.s = this.s;
|
||
var ds = Math.floor(n/this.DB);
|
||
if(ds >= this.t) { r.t = 0; return; }
|
||
var bs = n%this.DB;
|
||
var cbs = this.DB-bs;
|
||
var bm = (1<<bs)-1;
|
||
r.data[0] = this.data[ds]>>bs;
|
||
for(var i = ds+1; i < this.t; ++i) {
|
||
r.data[i-ds-1] |= (this.data[i]&bm)<<cbs;
|
||
r.data[i-ds] = this.data[i]>>bs;
|
||
}
|
||
if(bs > 0) r.data[this.t-ds-1] |= (this.s&bm)<<cbs;
|
||
r.t = this.t-ds;
|
||
r.clamp();
|
||
}
|
||
|
||
// (protected) r = this - a
|
||
function bnpSubTo(a,r) {
|
||
var i = 0, c = 0, m = Math.min(a.t,this.t);
|
||
while(i < m) {
|
||
c += this.data[i]-a.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
if(a.t < this.t) {
|
||
c -= a.s;
|
||
while(i < this.t) {
|
||
c += this.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
c += this.s;
|
||
} else {
|
||
c += this.s;
|
||
while(i < a.t) {
|
||
c -= a.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
c -= a.s;
|
||
}
|
||
r.s = (c<0)?-1:0;
|
||
if(c < -1) r.data[i++] = this.DV+c;
|
||
else if(c > 0) r.data[i++] = c;
|
||
r.t = i;
|
||
r.clamp();
|
||
}
|
||
|
||
// (protected) r = this * a, r != this,a (HAC 14.12)
|
||
// "this" should be the larger one if appropriate.
|
||
function bnpMultiplyTo(a,r) {
|
||
var x = this.abs(), y = a.abs();
|
||
var i = x.t;
|
||
r.t = i+y.t;
|
||
while(--i >= 0) r.data[i] = 0;
|
||
for(i = 0; i < y.t; ++i) r.data[i+x.t] = x.am(0,y.data[i],r,i,0,x.t);
|
||
r.s = 0;
|
||
r.clamp();
|
||
if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
|
||
}
|
||
|
||
// (protected) r = this^2, r != this (HAC 14.16)
|
||
function bnpSquareTo(r) {
|
||
var x = this.abs();
|
||
var i = r.t = 2*x.t;
|
||
while(--i >= 0) r.data[i] = 0;
|
||
for(i = 0; i < x.t-1; ++i) {
|
||
var c = x.am(i,x.data[i],r,2*i,0,1);
|
||
if((r.data[i+x.t]+=x.am(i+1,2*x.data[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
|
||
r.data[i+x.t] -= x.DV;
|
||
r.data[i+x.t+1] = 1;
|
||
}
|
||
}
|
||
if(r.t > 0) r.data[r.t-1] += x.am(i,x.data[i],r,2*i,0,1);
|
||
r.s = 0;
|
||
r.clamp();
|
||
}
|
||
|
||
// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
|
||
// r != q, this != m. q or r may be null.
|
||
function bnpDivRemTo(m,q,r) {
|
||
var pm = m.abs();
|
||
if(pm.t <= 0) return;
|
||
var pt = this.abs();
|
||
if(pt.t < pm.t) {
|
||
if(q != null) q.fromInt(0);
|
||
if(r != null) this.copyTo(r);
|
||
return;
|
||
}
|
||
if(r == null) r = nbi();
|
||
var y = nbi(), ts = this.s, ms = m.s;
|
||
var nsh = this.DB-nbits(pm.data[pm.t-1]); // normalize modulus
|
||
if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); }
|
||
var ys = y.t;
|
||
var y0 = y.data[ys-1];
|
||
if(y0 == 0) return;
|
||
var yt = y0*(1<<this.F1)+((ys>1)?y.data[ys-2]>>this.F2:0);
|
||
var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
|
||
var i = r.t, j = i-ys, t = (q==null)?nbi():q;
|
||
y.dlShiftTo(j,t);
|
||
if(r.compareTo(t) >= 0) {
|
||
r.data[r.t++] = 1;
|
||
r.subTo(t,r);
|
||
}
|
||
BigInteger.ONE.dlShiftTo(ys,t);
|
||
t.subTo(y,y); // "negative" y so we can replace sub with am later
|
||
while(y.t < ys) y.data[y.t++] = 0;
|
||
while(--j >= 0) {
|
||
// Estimate quotient digit
|
||
var qd = (r.data[--i]==y0)?this.DM:Math.floor(r.data[i]*d1+(r.data[i-1]+e)*d2);
|
||
if((r.data[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
|
||
y.dlShiftTo(j,t);
|
||
r.subTo(t,r);
|
||
while(r.data[i] < --qd) r.subTo(t,r);
|
||
}
|
||
}
|
||
if(q != null) {
|
||
r.drShiftTo(ys,q);
|
||
if(ts != ms) BigInteger.ZERO.subTo(q,q);
|
||
}
|
||
r.t = ys;
|
||
r.clamp();
|
||
if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
|
||
if(ts < 0) BigInteger.ZERO.subTo(r,r);
|
||
}
|
||
|
||
// (public) this mod a
|
||
function bnMod(a) {
|
||
var r = nbi();
|
||
this.abs().divRemTo(a,null,r);
|
||
if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
|
||
return r;
|
||
}
|
||
|
||
// Modular reduction using "classic" algorithm
|
||
function Classic(m) { this.m = m; }
|
||
function cConvert(x) {
|
||
if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
|
||
else return x;
|
||
}
|
||
function cRevert(x) { return x; }
|
||
function cReduce(x) { x.divRemTo(this.m,null,x); }
|
||
function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
|
||
function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
|
||
|
||
Classic.prototype.convert = cConvert;
|
||
Classic.prototype.revert = cRevert;
|
||
Classic.prototype.reduce = cReduce;
|
||
Classic.prototype.mulTo = cMulTo;
|
||
Classic.prototype.sqrTo = cSqrTo;
|
||
|
||
// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
|
||
// justification:
|
||
// xy == 1 (mod m)
|
||
// xy = 1+km
|
||
// xy(2-xy) = (1+km)(1-km)
|
||
// x[y(2-xy)] = 1-k^2m^2
|
||
// x[y(2-xy)] == 1 (mod m^2)
|
||
// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
|
||
// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
|
||
// JS multiply "overflows" differently from C/C++, so care is needed here.
|
||
function bnpInvDigit() {
|
||
if(this.t < 1) return 0;
|
||
var x = this.data[0];
|
||
if((x&1) == 0) return 0;
|
||
var y = x&3; // y == 1/x mod 2^2
|
||
y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
|
||
y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
|
||
y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
|
||
// last step - calculate inverse mod DV directly;
|
||
// assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
|
||
y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
|
||
// we really want the negative inverse, and -DV < y < DV
|
||
return (y>0)?this.DV-y:-y;
|
||
}
|
||
|
||
// Montgomery reduction
|
||
function Montgomery(m) {
|
||
this.m = m;
|
||
this.mp = m.invDigit();
|
||
this.mpl = this.mp&0x7fff;
|
||
this.mph = this.mp>>15;
|
||
this.um = (1<<(m.DB-15))-1;
|
||
this.mt2 = 2*m.t;
|
||
}
|
||
|
||
// xR mod m
|
||
function montConvert(x) {
|
||
var r = nbi();
|
||
x.abs().dlShiftTo(this.m.t,r);
|
||
r.divRemTo(this.m,null,r);
|
||
if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
|
||
return r;
|
||
}
|
||
|
||
// x/R mod m
|
||
function montRevert(x) {
|
||
var r = nbi();
|
||
x.copyTo(r);
|
||
this.reduce(r);
|
||
return r;
|
||
}
|
||
|
||
// x = x/R mod m (HAC 14.32)
|
||
function montReduce(x) {
|
||
while(x.t <= this.mt2) // pad x so am has enough room later
|
||
x.data[x.t++] = 0;
|
||
for(var i = 0; i < this.m.t; ++i) {
|
||
// faster way of calculating u0 = x.data[i]*mp mod DV
|
||
var j = x.data[i]&0x7fff;
|
||
var u0 = (j*this.mpl+(((j*this.mph+(x.data[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
|
||
// use am to combine the multiply-shift-add into one call
|
||
j = i+this.m.t;
|
||
x.data[j] += this.m.am(0,u0,x,i,0,this.m.t);
|
||
// propagate carry
|
||
while(x.data[j] >= x.DV) { x.data[j] -= x.DV; x.data[++j]++; }
|
||
}
|
||
x.clamp();
|
||
x.drShiftTo(this.m.t,x);
|
||
if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
|
||
}
|
||
|
||
// r = "x^2/R mod m"; x != r
|
||
function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
|
||
|
||
// r = "xy/R mod m"; x,y != r
|
||
function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
|
||
|
||
Montgomery.prototype.convert = montConvert;
|
||
Montgomery.prototype.revert = montRevert;
|
||
Montgomery.prototype.reduce = montReduce;
|
||
Montgomery.prototype.mulTo = montMulTo;
|
||
Montgomery.prototype.sqrTo = montSqrTo;
|
||
|
||
// (protected) true iff this is even
|
||
function bnpIsEven() { return ((this.t>0)?(this.data[0]&1):this.s) == 0; }
|
||
|
||
// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
|
||
function bnpExp(e,z) {
|
||
if(e > 0xffffffff || e < 1) return BigInteger.ONE;
|
||
var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
|
||
g.copyTo(r);
|
||
while(--i >= 0) {
|
||
z.sqrTo(r,r2);
|
||
if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
|
||
else { var t = r; r = r2; r2 = t; }
|
||
}
|
||
return z.revert(r);
|
||
}
|
||
|
||
// (public) this^e % m, 0 <= e < 2^32
|
||
function bnModPowInt(e,m) {
|
||
var z;
|
||
if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
|
||
return this.exp(e,z);
|
||
}
|
||
|
||
// protected
|
||
BigInteger.prototype.copyTo = bnpCopyTo;
|
||
BigInteger.prototype.fromInt = bnpFromInt;
|
||
BigInteger.prototype.fromString = bnpFromString;
|
||
BigInteger.prototype.clamp = bnpClamp;
|
||
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
|
||
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
|
||
BigInteger.prototype.lShiftTo = bnpLShiftTo;
|
||
BigInteger.prototype.rShiftTo = bnpRShiftTo;
|
||
BigInteger.prototype.subTo = bnpSubTo;
|
||
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
|
||
BigInteger.prototype.squareTo = bnpSquareTo;
|
||
BigInteger.prototype.divRemTo = bnpDivRemTo;
|
||
BigInteger.prototype.invDigit = bnpInvDigit;
|
||
BigInteger.prototype.isEven = bnpIsEven;
|
||
BigInteger.prototype.exp = bnpExp;
|
||
|
||
// public
|
||
BigInteger.prototype.toString = bnToString;
|
||
BigInteger.prototype.negate = bnNegate;
|
||
BigInteger.prototype.abs = bnAbs;
|
||
BigInteger.prototype.compareTo = bnCompareTo;
|
||
BigInteger.prototype.bitLength = bnBitLength;
|
||
BigInteger.prototype.mod = bnMod;
|
||
BigInteger.prototype.modPowInt = bnModPowInt;
|
||
|
||
// "constants"
|
||
BigInteger.ZERO = nbv(0);
|
||
BigInteger.ONE = nbv(1);
|
||
|
||
// jsbn2 lib
|
||
|
||
//Copyright (c) 2005-2009 Tom Wu
|
||
//All Rights Reserved.
|
||
//See "LICENSE" for details (See jsbn.js for LICENSE).
|
||
|
||
//Extended JavaScript BN functions, required for RSA private ops.
|
||
|
||
//Version 1.1: new BigInteger("0", 10) returns "proper" zero
|
||
|
||
//(public)
|
||
function bnClone() { var r = nbi(); this.copyTo(r); return r; }
|
||
|
||
//(public) return value as integer
|
||
function bnIntValue() {
|
||
if(this.s < 0) {
|
||
if(this.t == 1) return this.data[0]-this.DV;
|
||
else if(this.t == 0) return -1;
|
||
} else if(this.t == 1) return this.data[0];
|
||
else if(this.t == 0) return 0;
|
||
// assumes 16 < DB < 32
|
||
return ((this.data[1]&((1<<(32-this.DB))-1))<<this.DB)|this.data[0];
|
||
}
|
||
|
||
//(public) return value as byte
|
||
function bnByteValue() { return (this.t==0)?this.s:(this.data[0]<<24)>>24; }
|
||
|
||
//(public) return value as short (assumes DB>=16)
|
||
function bnShortValue() { return (this.t==0)?this.s:(this.data[0]<<16)>>16; }
|
||
|
||
//(protected) return x s.t. r^x < DV
|
||
function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); }
|
||
|
||
//(public) 0 if this == 0, 1 if this > 0
|
||
function bnSigNum() {
|
||
if(this.s < 0) return -1;
|
||
else if(this.t <= 0 || (this.t == 1 && this.data[0] <= 0)) return 0;
|
||
else return 1;
|
||
}
|
||
|
||
//(protected) convert to radix string
|
||
function bnpToRadix(b) {
|
||
if(b == null) b = 10;
|
||
if(this.signum() == 0 || b < 2 || b > 36) return "0";
|
||
var cs = this.chunkSize(b);
|
||
var a = Math.pow(b,cs);
|
||
var d = nbv(a), y = nbi(), z = nbi(), r = "";
|
||
this.divRemTo(d,y,z);
|
||
while(y.signum() > 0) {
|
||
r = (a+z.intValue()).toString(b).substr(1) + r;
|
||
y.divRemTo(d,y,z);
|
||
}
|
||
return z.intValue().toString(b) + r;
|
||
}
|
||
|
||
//(protected) convert from radix string
|
||
function bnpFromRadix(s,b) {
|
||
this.fromInt(0);
|
||
if(b == null) b = 10;
|
||
var cs = this.chunkSize(b);
|
||
var d = Math.pow(b,cs), mi = false, j = 0, w = 0;
|
||
for(var i = 0; i < s.length; ++i) {
|
||
var x = intAt(s,i);
|
||
if(x < 0) {
|
||
if(s.charAt(i) == "-" && this.signum() == 0) mi = true;
|
||
continue;
|
||
}
|
||
w = b*w+x;
|
||
if(++j >= cs) {
|
||
this.dMultiply(d);
|
||
this.dAddOffset(w,0);
|
||
j = 0;
|
||
w = 0;
|
||
}
|
||
}
|
||
if(j > 0) {
|
||
this.dMultiply(Math.pow(b,j));
|
||
this.dAddOffset(w,0);
|
||
}
|
||
if(mi) BigInteger.ZERO.subTo(this,this);
|
||
}
|
||
|
||
//(protected) alternate constructor
|
||
function bnpFromNumber(a,b,c) {
|
||
if("number" == typeof b) {
|
||
// new BigInteger(int,int,RNG)
|
||
if(a < 2) this.fromInt(1);
|
||
else {
|
||
this.fromNumber(a,c);
|
||
if(!this.testBit(a-1)) // force MSB set
|
||
this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);
|
||
if(this.isEven()) this.dAddOffset(1,0); // force odd
|
||
while(!this.isProbablePrime(b)) {
|
||
this.dAddOffset(2,0);
|
||
if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this);
|
||
}
|
||
}
|
||
} else {
|
||
// new BigInteger(int,RNG)
|
||
var x = new Array(), t = a&7;
|
||
x.length = (a>>3)+1;
|
||
b.nextBytes(x);
|
||
if(t > 0) x[0] &= ((1<<t)-1); else x[0] = 0;
|
||
this.fromString(x,256);
|
||
}
|
||
}
|
||
|
||
//(public) convert to bigendian byte array
|
||
function bnToByteArray() {
|
||
var i = this.t, r = new Array();
|
||
r[0] = this.s;
|
||
var p = this.DB-(i*this.DB)%8, d, k = 0;
|
||
if(i-- > 0) {
|
||
if(p < this.DB && (d = this.data[i]>>p) != (this.s&this.DM)>>p)
|
||
r[k++] = d|(this.s<<(this.DB-p));
|
||
while(i >= 0) {
|
||
if(p < 8) {
|
||
d = (this.data[i]&((1<<p)-1))<<(8-p);
|
||
d |= this.data[--i]>>(p+=this.DB-8);
|
||
} else {
|
||
d = (this.data[i]>>(p-=8))&0xff;
|
||
if(p <= 0) { p += this.DB; --i; }
|
||
}
|
||
if((d&0x80) != 0) d |= -256;
|
||
if(k == 0 && (this.s&0x80) != (d&0x80)) ++k;
|
||
if(k > 0 || d != this.s) r[k++] = d;
|
||
}
|
||
}
|
||
return r;
|
||
}
|
||
|
||
function bnEquals(a) { return(this.compareTo(a)==0); }
|
||
function bnMin(a) { return(this.compareTo(a)<0)?this:a; }
|
||
function bnMax(a) { return(this.compareTo(a)>0)?this:a; }
|
||
|
||
//(protected) r = this op a (bitwise)
|
||
function bnpBitwiseTo(a,op,r) {
|
||
var i, f, m = Math.min(a.t,this.t);
|
||
for(i = 0; i < m; ++i) r.data[i] = op(this.data[i],a.data[i]);
|
||
if(a.t < this.t) {
|
||
f = a.s&this.DM;
|
||
for(i = m; i < this.t; ++i) r.data[i] = op(this.data[i],f);
|
||
r.t = this.t;
|
||
} else {
|
||
f = this.s&this.DM;
|
||
for(i = m; i < a.t; ++i) r.data[i] = op(f,a.data[i]);
|
||
r.t = a.t;
|
||
}
|
||
r.s = op(this.s,a.s);
|
||
r.clamp();
|
||
}
|
||
|
||
//(public) this & a
|
||
function op_and(x,y) { return x&y; }
|
||
function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; }
|
||
|
||
//(public) this | a
|
||
function op_or(x,y) { return x|y; }
|
||
function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; }
|
||
|
||
//(public) this ^ a
|
||
function op_xor(x,y) { return x^y; }
|
||
function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; }
|
||
|
||
//(public) this & ~a
|
||
function op_andnot(x,y) { return x&~y; }
|
||
function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; }
|
||
|
||
//(public) ~this
|
||
function bnNot() {
|
||
var r = nbi();
|
||
for(var i = 0; i < this.t; ++i) r.data[i] = this.DM&~this.data[i];
|
||
r.t = this.t;
|
||
r.s = ~this.s;
|
||
return r;
|
||
}
|
||
|
||
//(public) this << n
|
||
function bnShiftLeft(n) {
|
||
var r = nbi();
|
||
if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r);
|
||
return r;
|
||
}
|
||
|
||
//(public) this >> n
|
||
function bnShiftRight(n) {
|
||
var r = nbi();
|
||
if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r);
|
||
return r;
|
||
}
|
||
|
||
//return index of lowest 1-bit in x, x < 2^31
|
||
function lbit(x) {
|
||
if(x == 0) return -1;
|
||
var r = 0;
|
||
if((x&0xffff) == 0) { x >>= 16; r += 16; }
|
||
if((x&0xff) == 0) { x >>= 8; r += 8; }
|
||
if((x&0xf) == 0) { x >>= 4; r += 4; }
|
||
if((x&3) == 0) { x >>= 2; r += 2; }
|
||
if((x&1) == 0) ++r;
|
||
return r;
|
||
}
|
||
|
||
//(public) returns index of lowest 1-bit (or -1 if none)
|
||
function bnGetLowestSetBit() {
|
||
for(var i = 0; i < this.t; ++i)
|
||
if(this.data[i] != 0) return i*this.DB+lbit(this.data[i]);
|
||
if(this.s < 0) return this.t*this.DB;
|
||
return -1;
|
||
}
|
||
|
||
//return number of 1 bits in x
|
||
function cbit(x) {
|
||
var r = 0;
|
||
while(x != 0) { x &= x-1; ++r; }
|
||
return r;
|
||
}
|
||
|
||
//(public) return number of set bits
|
||
function bnBitCount() {
|
||
var r = 0, x = this.s&this.DM;
|
||
for(var i = 0; i < this.t; ++i) r += cbit(this.data[i]^x);
|
||
return r;
|
||
}
|
||
|
||
//(public) true iff nth bit is set
|
||
function bnTestBit(n) {
|
||
var j = Math.floor(n/this.DB);
|
||
if(j >= this.t) return(this.s!=0);
|
||
return((this.data[j]&(1<<(n%this.DB)))!=0);
|
||
}
|
||
|
||
//(protected) this op (1<<n)
|
||
function bnpChangeBit(n,op) {
|
||
var r = BigInteger.ONE.shiftLeft(n);
|
||
this.bitwiseTo(r,op,r);
|
||
return r;
|
||
}
|
||
|
||
//(public) this | (1<<n)
|
||
function bnSetBit(n) { return this.changeBit(n,op_or); }
|
||
|
||
//(public) this & ~(1<<n)
|
||
function bnClearBit(n) { return this.changeBit(n,op_andnot); }
|
||
|
||
//(public) this ^ (1<<n)
|
||
function bnFlipBit(n) { return this.changeBit(n,op_xor); }
|
||
|
||
//(protected) r = this + a
|
||
function bnpAddTo(a,r) {
|
||
var i = 0, c = 0, m = Math.min(a.t,this.t);
|
||
while(i < m) {
|
||
c += this.data[i]+a.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
if(a.t < this.t) {
|
||
c += a.s;
|
||
while(i < this.t) {
|
||
c += this.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
c += this.s;
|
||
} else {
|
||
c += this.s;
|
||
while(i < a.t) {
|
||
c += a.data[i];
|
||
r.data[i++] = c&this.DM;
|
||
c >>= this.DB;
|
||
}
|
||
c += a.s;
|
||
}
|
||
r.s = (c<0)?-1:0;
|
||
if(c > 0) r.data[i++] = c;
|
||
else if(c < -1) r.data[i++] = this.DV+c;
|
||
r.t = i;
|
||
r.clamp();
|
||
}
|
||
|
||
//(public) this + a
|
||
function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; }
|
||
|
||
//(public) this - a
|
||
function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; }
|
||
|
||
//(public) this * a
|
||
function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; }
|
||
|
||
//(public) this / a
|
||
function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; }
|
||
|
||
//(public) this % a
|
||
function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; }
|
||
|
||
//(public) [this/a,this%a]
|
||
function bnDivideAndRemainder(a) {
|
||
var q = nbi(), r = nbi();
|
||
this.divRemTo(a,q,r);
|
||
return new Array(q,r);
|
||
}
|
||
|
||
//(protected) this *= n, this >= 0, 1 < n < DV
|
||
function bnpDMultiply(n) {
|
||
this.data[this.t] = this.am(0,n-1,this,0,0,this.t);
|
||
++this.t;
|
||
this.clamp();
|
||
}
|
||
|
||
//(protected) this += n << w words, this >= 0
|
||
function bnpDAddOffset(n,w) {
|
||
if(n == 0) return;
|
||
while(this.t <= w) this.data[this.t++] = 0;
|
||
this.data[w] += n;
|
||
while(this.data[w] >= this.DV) {
|
||
this.data[w] -= this.DV;
|
||
if(++w >= this.t) this.data[this.t++] = 0;
|
||
++this.data[w];
|
||
}
|
||
}
|
||
|
||
//A "null" reducer
|
||
function NullExp() {}
|
||
function nNop(x) { return x; }
|
||
function nMulTo(x,y,r) { x.multiplyTo(y,r); }
|
||
function nSqrTo(x,r) { x.squareTo(r); }
|
||
|
||
NullExp.prototype.convert = nNop;
|
||
NullExp.prototype.revert = nNop;
|
||
NullExp.prototype.mulTo = nMulTo;
|
||
NullExp.prototype.sqrTo = nSqrTo;
|
||
|
||
//(public) this^e
|
||
function bnPow(e) { return this.exp(e,new NullExp()); }
|
||
|
||
//(protected) r = lower n words of "this * a", a.t <= n
|
||
//"this" should be the larger one if appropriate.
|
||
function bnpMultiplyLowerTo(a,n,r) {
|
||
var i = Math.min(this.t+a.t,n);
|
||
r.s = 0; // assumes a,this >= 0
|
||
r.t = i;
|
||
while(i > 0) r.data[--i] = 0;
|
||
var j;
|
||
for(j = r.t-this.t; i < j; ++i) r.data[i+this.t] = this.am(0,a.data[i],r,i,0,this.t);
|
||
for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a.data[i],r,i,0,n-i);
|
||
r.clamp();
|
||
}
|
||
|
||
//(protected) r = "this * a" without lower n words, n > 0
|
||
//"this" should be the larger one if appropriate.
|
||
function bnpMultiplyUpperTo(a,n,r) {
|
||
--n;
|
||
var i = r.t = this.t+a.t-n;
|
||
r.s = 0; // assumes a,this >= 0
|
||
while(--i >= 0) r.data[i] = 0;
|
||
for(i = Math.max(n-this.t,0); i < a.t; ++i)
|
||
r.data[this.t+i-n] = this.am(n-i,a.data[i],r,0,0,this.t+i-n);
|
||
r.clamp();
|
||
r.drShiftTo(1,r);
|
||
}
|
||
|
||
//Barrett modular reduction
|
||
function Barrett(m) {
|
||
// setup Barrett
|
||
this.r2 = nbi();
|
||
this.q3 = nbi();
|
||
BigInteger.ONE.dlShiftTo(2*m.t,this.r2);
|
||
this.mu = this.r2.divide(m);
|
||
this.m = m;
|
||
}
|
||
|
||
function barrettConvert(x) {
|
||
if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m);
|
||
else if(x.compareTo(this.m) < 0) return x;
|
||
else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; }
|
||
}
|
||
|
||
function barrettRevert(x) { return x; }
|
||
|
||
//x = x mod m (HAC 14.42)
|
||
function barrettReduce(x) {
|
||
x.drShiftTo(this.m.t-1,this.r2);
|
||
if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); }
|
||
this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);
|
||
this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);
|
||
while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1);
|
||
x.subTo(this.r2,x);
|
||
while(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
|
||
}
|
||
|
||
//r = x^2 mod m; x != r
|
||
function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
|
||
|
||
//r = x*y mod m; x,y != r
|
||
function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
|
||
|
||
Barrett.prototype.convert = barrettConvert;
|
||
Barrett.prototype.revert = barrettRevert;
|
||
Barrett.prototype.reduce = barrettReduce;
|
||
Barrett.prototype.mulTo = barrettMulTo;
|
||
Barrett.prototype.sqrTo = barrettSqrTo;
|
||
|
||
//(public) this^e % m (HAC 14.85)
|
||
function bnModPow(e,m) {
|
||
var i = e.bitLength(), k, r = nbv(1), z;
|
||
if(i <= 0) return r;
|
||
else if(i < 18) k = 1;
|
||
else if(i < 48) k = 3;
|
||
else if(i < 144) k = 4;
|
||
else if(i < 768) k = 5;
|
||
else k = 6;
|
||
if(i < 8)
|
||
z = new Classic(m);
|
||
else if(m.isEven())
|
||
z = new Barrett(m);
|
||
else
|
||
z = new Montgomery(m);
|
||
|
||
// precomputation
|
||
var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
|
||
g[1] = z.convert(this);
|
||
if(k > 1) {
|
||
var g2 = nbi();
|
||
z.sqrTo(g[1],g2);
|
||
while(n <= km) {
|
||
g[n] = nbi();
|
||
z.mulTo(g2,g[n-2],g[n]);
|
||
n += 2;
|
||
}
|
||
}
|
||
|
||
var j = e.t-1, w, is1 = true, r2 = nbi(), t;
|
||
i = nbits(e.data[j])-1;
|
||
while(j >= 0) {
|
||
if(i >= k1) w = (e.data[j]>>(i-k1))&km;
|
||
else {
|
||
w = (e.data[j]&((1<<(i+1))-1))<<(k1-i);
|
||
if(j > 0) w |= e.data[j-1]>>(this.DB+i-k1);
|
||
}
|
||
|
||
n = k;
|
||
while((w&1) == 0) { w >>= 1; --n; }
|
||
if((i -= n) < 0) { i += this.DB; --j; }
|
||
if(is1) { // ret == 1, don't bother squaring or multiplying it
|
||
g[w].copyTo(r);
|
||
is1 = false;
|
||
} else {
|
||
while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; }
|
||
if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; }
|
||
z.mulTo(r2,g[w],r);
|
||
}
|
||
|
||
while(j >= 0 && (e.data[j]&(1<<i)) == 0) {
|
||
z.sqrTo(r,r2); t = r; r = r2; r2 = t;
|
||
if(--i < 0) { i = this.DB-1; --j; }
|
||
}
|
||
}
|
||
return z.revert(r);
|
||
}
|
||
|
||
//(public) gcd(this,a) (HAC 14.54)
|
||
function bnGCD(a) {
|
||
var x = (this.s<0)?this.negate():this.clone();
|
||
var y = (a.s<0)?a.negate():a.clone();
|
||
if(x.compareTo(y) < 0) { var t = x; x = y; y = t; }
|
||
var i = x.getLowestSetBit(), g = y.getLowestSetBit();
|
||
if(g < 0) return x;
|
||
if(i < g) g = i;
|
||
if(g > 0) {
|
||
x.rShiftTo(g,x);
|
||
y.rShiftTo(g,y);
|
||
}
|
||
while(x.signum() > 0) {
|
||
if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x);
|
||
if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y);
|
||
if(x.compareTo(y) >= 0) {
|
||
x.subTo(y,x);
|
||
x.rShiftTo(1,x);
|
||
} else {
|
||
y.subTo(x,y);
|
||
y.rShiftTo(1,y);
|
||
}
|
||
}
|
||
if(g > 0) y.lShiftTo(g,y);
|
||
return y;
|
||
}
|
||
|
||
//(protected) this % n, n < 2^26
|
||
function bnpModInt(n) {
|
||
if(n <= 0) return 0;
|
||
var d = this.DV%n, r = (this.s<0)?n-1:0;
|
||
if(this.t > 0)
|
||
if(d == 0) r = this.data[0]%n;
|
||
else for(var i = this.t-1; i >= 0; --i) r = (d*r+this.data[i])%n;
|
||
return r;
|
||
}
|
||
|
||
//(public) 1/this % m (HAC 14.61)
|
||
function bnModInverse(m) {
|
||
var ac = m.isEven();
|
||
if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
|
||
var u = m.clone(), v = this.clone();
|
||
var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1);
|
||
while(u.signum() != 0) {
|
||
while(u.isEven()) {
|
||
u.rShiftTo(1,u);
|
||
if(ac) {
|
||
if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); }
|
||
a.rShiftTo(1,a);
|
||
} else if(!b.isEven()) b.subTo(m,b);
|
||
b.rShiftTo(1,b);
|
||
}
|
||
while(v.isEven()) {
|
||
v.rShiftTo(1,v);
|
||
if(ac) {
|
||
if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); }
|
||
c.rShiftTo(1,c);
|
||
} else if(!d.isEven()) d.subTo(m,d);
|
||
d.rShiftTo(1,d);
|
||
}
|
||
if(u.compareTo(v) >= 0) {
|
||
u.subTo(v,u);
|
||
if(ac) a.subTo(c,a);
|
||
b.subTo(d,b);
|
||
} else {
|
||
v.subTo(u,v);
|
||
if(ac) c.subTo(a,c);
|
||
d.subTo(b,d);
|
||
}
|
||
}
|
||
if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO;
|
||
if(d.compareTo(m) >= 0) return d.subtract(m);
|
||
if(d.signum() < 0) d.addTo(m,d); else return d;
|
||
if(d.signum() < 0) return d.add(m); else return d;
|
||
}
|
||
|
||
var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509];
|
||
var lplim = (1<<26)/lowprimes[lowprimes.length-1];
|
||
|
||
//(public) test primality with certainty >= 1-.5^t
|
||
function bnIsProbablePrime(t) {
|
||
var i, x = this.abs();
|
||
if(x.t == 1 && x.data[0] <= lowprimes[lowprimes.length-1]) {
|
||
for(i = 0; i < lowprimes.length; ++i)
|
||
if(x.data[0] == lowprimes[i]) return true;
|
||
return false;
|
||
}
|
||
if(x.isEven()) return false;
|
||
i = 1;
|
||
while(i < lowprimes.length) {
|
||
var m = lowprimes[i], j = i+1;
|
||
while(j < lowprimes.length && m < lplim) m *= lowprimes[j++];
|
||
m = x.modInt(m);
|
||
while(i < j) if(m%lowprimes[i++] == 0) return false;
|
||
}
|
||
return x.millerRabin(t);
|
||
}
|
||
|
||
//(protected) true if probably prime (HAC 4.24, Miller-Rabin)
|
||
function bnpMillerRabin(t) {
|
||
var n1 = this.subtract(BigInteger.ONE);
|
||
var k = n1.getLowestSetBit();
|
||
if(k <= 0) return false;
|
||
var r = n1.shiftRight(k);
|
||
var prng = bnGetPrng();
|
||
var a;
|
||
for(var i = 0; i < t; ++i) {
|
||
// select witness 'a' at random from between 1 and n1
|
||
do {
|
||
a = new BigInteger(this.bitLength(), prng);
|
||
}
|
||
while(a.compareTo(BigInteger.ONE) <= 0 || a.compareTo(n1) >= 0);
|
||
var y = a.modPow(r,this);
|
||
if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) {
|
||
var j = 1;
|
||
while(j++ < k && y.compareTo(n1) != 0) {
|
||
y = y.modPowInt(2,this);
|
||
if(y.compareTo(BigInteger.ONE) == 0) return false;
|
||
}
|
||
if(y.compareTo(n1) != 0) return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// get pseudo random number generator
|
||
function bnGetPrng() {
|
||
// create prng with api that matches BigInteger secure random
|
||
return {
|
||
// x is an array to fill with bytes
|
||
nextBytes: function(x) {
|
||
for(var i = 0; i < x.length; ++i) {
|
||
x[i] = Math.floor(Math.random() * 0x0100);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
//protected
|
||
BigInteger.prototype.chunkSize = bnpChunkSize;
|
||
BigInteger.prototype.toRadix = bnpToRadix;
|
||
BigInteger.prototype.fromRadix = bnpFromRadix;
|
||
BigInteger.prototype.fromNumber = bnpFromNumber;
|
||
BigInteger.prototype.bitwiseTo = bnpBitwiseTo;
|
||
BigInteger.prototype.changeBit = bnpChangeBit;
|
||
BigInteger.prototype.addTo = bnpAddTo;
|
||
BigInteger.prototype.dMultiply = bnpDMultiply;
|
||
BigInteger.prototype.dAddOffset = bnpDAddOffset;
|
||
BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo;
|
||
BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
|
||
BigInteger.prototype.modInt = bnpModInt;
|
||
BigInteger.prototype.millerRabin = bnpMillerRabin;
|
||
|
||
//public
|
||
BigInteger.prototype.clone = bnClone;
|
||
BigInteger.prototype.intValue = bnIntValue;
|
||
BigInteger.prototype.byteValue = bnByteValue;
|
||
BigInteger.prototype.shortValue = bnShortValue;
|
||
BigInteger.prototype.signum = bnSigNum;
|
||
BigInteger.prototype.toByteArray = bnToByteArray;
|
||
BigInteger.prototype.equals = bnEquals;
|
||
BigInteger.prototype.min = bnMin;
|
||
BigInteger.prototype.max = bnMax;
|
||
BigInteger.prototype.and = bnAnd;
|
||
BigInteger.prototype.or = bnOr;
|
||
BigInteger.prototype.xor = bnXor;
|
||
BigInteger.prototype.andNot = bnAndNot;
|
||
BigInteger.prototype.not = bnNot;
|
||
BigInteger.prototype.shiftLeft = bnShiftLeft;
|
||
BigInteger.prototype.shiftRight = bnShiftRight;
|
||
BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit;
|
||
BigInteger.prototype.bitCount = bnBitCount;
|
||
BigInteger.prototype.testBit = bnTestBit;
|
||
BigInteger.prototype.setBit = bnSetBit;
|
||
BigInteger.prototype.clearBit = bnClearBit;
|
||
BigInteger.prototype.flipBit = bnFlipBit;
|
||
BigInteger.prototype.add = bnAdd;
|
||
BigInteger.prototype.subtract = bnSubtract;
|
||
BigInteger.prototype.multiply = bnMultiply;
|
||
BigInteger.prototype.divide = bnDivide;
|
||
BigInteger.prototype.remainder = bnRemainder;
|
||
BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder;
|
||
BigInteger.prototype.modPow = bnModPow;
|
||
BigInteger.prototype.modInverse = bnModInverse;
|
||
BigInteger.prototype.pow = bnPow;
|
||
BigInteger.prototype.gcd = bnGCD;
|
||
BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
|
||
|
||
//BigInteger interfaces not implemented in jsbn:
|
||
|
||
//BigInteger(int signum, byte[] magnitude)
|
||
//double doubleValue()
|
||
//float floatValue()
|
||
//int hashCode()
|
||
//long longValue()
|
||
//static BigInteger valueOf(long val)
|
||
|
||
forge.jsbn = forge.jsbn || {};
|
||
forge.jsbn.BigInteger = BigInteger;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'jsbn';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/jsbn',['require', 'module'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Partial implementation of PKCS#1 v2.2: RSA-OEAP
|
||
*
|
||
* Modified but based on the following MIT and BSD licensed code:
|
||
*
|
||
* https://github.com/kjur/jsjws/blob/master/rsa.js:
|
||
*
|
||
* The 'jsjws'(JSON Web Signature JavaScript Library) License
|
||
*
|
||
* Copyright (c) 2012 Kenji Urushima
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
* of this software and associated documentation files (the "Software"), to deal
|
||
* in the Software without restriction, including without limitation the rights
|
||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
* copies of the Software, and to permit persons to whom the Software is
|
||
* furnished to do so, subject to the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be included in
|
||
* all copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
* THE SOFTWARE.
|
||
*
|
||
* http://webrsa.cvs.sourceforge.net/viewvc/webrsa/Client/RSAES-OAEP.js?content-type=text%2Fplain:
|
||
*
|
||
* RSAES-OAEP.js
|
||
* $Id: RSAES-OAEP.js,v 1.1.1.1 2003/03/19 15:37:20 ellispritchard Exp $
|
||
* JavaScript Implementation of PKCS #1 v2.1 RSA CRYPTOGRAPHY STANDARD (RSA Laboratories, June 14, 2002)
|
||
* Copyright (C) Ellis Pritchard, Guardian Unlimited 2003.
|
||
* Contact: ellis@nukinetics.com
|
||
* Distributed under the BSD License.
|
||
*
|
||
* Official documentation: http://www.rsa.com/rsalabs/node.asp?id=2125
|
||
*
|
||
* @author Evan Jones (http://evanjones.ca/)
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2013-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for PKCS#1 API
|
||
var pkcs1 = forge.pkcs1 = forge.pkcs1 || {};
|
||
|
||
/**
|
||
* Encode the given RSAES-OAEP message (M) using key, with optional label (L)
|
||
* and seed.
|
||
*
|
||
* This method does not perform RSA encryption, it only encodes the message
|
||
* using RSAES-OAEP.
|
||
*
|
||
* @param key the RSA key to use.
|
||
* @param message the message to encode.
|
||
* @param options the options to use:
|
||
* label an optional label to use.
|
||
* seed the seed to use.
|
||
* md the message digest object to use, undefined for SHA-1.
|
||
* mgf1 optional mgf1 parameters:
|
||
* md the message digest object to use for MGF1.
|
||
*
|
||
* @return the encoded message bytes.
|
||
*/
|
||
pkcs1.encode_rsa_oaep = function(key, message, options) {
|
||
// parse arguments
|
||
var label;
|
||
var seed;
|
||
var md;
|
||
var mgf1Md;
|
||
// legacy args (label, seed, md)
|
||
if(typeof options === 'string') {
|
||
label = options;
|
||
seed = arguments[3] || undefined;
|
||
md = arguments[4] || undefined;
|
||
} else if(options) {
|
||
label = options.label || undefined;
|
||
seed = options.seed || undefined;
|
||
md = options.md || undefined;
|
||
if(options.mgf1 && options.mgf1.md) {
|
||
mgf1Md = options.mgf1.md;
|
||
}
|
||
}
|
||
|
||
// default OAEP to SHA-1 message digest
|
||
if(!md) {
|
||
md = forge.md.sha1.create();
|
||
} else {
|
||
md.start();
|
||
}
|
||
|
||
// default MGF-1 to same as OAEP
|
||
if(!mgf1Md) {
|
||
mgf1Md = md;
|
||
}
|
||
|
||
// compute length in bytes and check output
|
||
var keyLength = Math.ceil(key.n.bitLength() / 8);
|
||
var maxLength = keyLength - 2 * md.digestLength - 2;
|
||
if(message.length > maxLength) {
|
||
var error = new Error('RSAES-OAEP input message length is too long.');
|
||
error.length = message.length;
|
||
error.maxLength = maxLength;
|
||
throw error;
|
||
}
|
||
|
||
if(!label) {
|
||
label = '';
|
||
}
|
||
md.update(label, 'raw');
|
||
var lHash = md.digest();
|
||
|
||
var PS = '';
|
||
var PS_length = maxLength - message.length;
|
||
for (var i = 0; i < PS_length; i++) {
|
||
PS += '\x00';
|
||
}
|
||
|
||
var DB = lHash.getBytes() + PS + '\x01' + message;
|
||
|
||
if(!seed) {
|
||
seed = forge.random.getBytes(md.digestLength);
|
||
} else if(seed.length !== md.digestLength) {
|
||
var error = new Error('Invalid RSAES-OAEP seed. The seed length must ' +
|
||
'match the digest length.')
|
||
error.seedLength = seed.length;
|
||
error.digestLength = md.digestLength;
|
||
throw error;
|
||
}
|
||
|
||
var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
|
||
var maskedDB = forge.util.xorBytes(DB, dbMask, DB.length);
|
||
|
||
var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
|
||
var maskedSeed = forge.util.xorBytes(seed, seedMask, seed.length);
|
||
|
||
// return encoded message
|
||
return '\x00' + maskedSeed + maskedDB;
|
||
};
|
||
|
||
/**
|
||
* Decode the given RSAES-OAEP encoded message (EM) using key, with optional
|
||
* label (L).
|
||
*
|
||
* This method does not perform RSA decryption, it only decodes the message
|
||
* using RSAES-OAEP.
|
||
*
|
||
* @param key the RSA key to use.
|
||
* @param em the encoded message to decode.
|
||
* @param options the options to use:
|
||
* label an optional label to use.
|
||
* md the message digest object to use for OAEP, undefined for SHA-1.
|
||
* mgf1 optional mgf1 parameters:
|
||
* md the message digest object to use for MGF1.
|
||
*
|
||
* @return the decoded message bytes.
|
||
*/
|
||
pkcs1.decode_rsa_oaep = function(key, em, options) {
|
||
// parse args
|
||
var label;
|
||
var md;
|
||
var mgf1Md;
|
||
// legacy args
|
||
if(typeof options === 'string') {
|
||
label = options;
|
||
md = arguments[3] || undefined;
|
||
} else if(options) {
|
||
label = options.label || undefined;
|
||
md = options.md || undefined;
|
||
if(options.mgf1 && options.mgf1.md) {
|
||
mgf1Md = options.mgf1.md;
|
||
}
|
||
}
|
||
|
||
// compute length in bytes
|
||
var keyLength = Math.ceil(key.n.bitLength() / 8);
|
||
|
||
if(em.length !== keyLength) {
|
||
var error = new Error('RSAES-OAEP encoded message length is invalid.');
|
||
error.length = em.length;
|
||
error.expectedLength = keyLength;
|
||
throw error;
|
||
}
|
||
|
||
// default OAEP to SHA-1 message digest
|
||
if(md === undefined) {
|
||
md = forge.md.sha1.create();
|
||
} else {
|
||
md.start();
|
||
}
|
||
|
||
// default MGF-1 to same as OAEP
|
||
if(!mgf1Md) {
|
||
mgf1Md = md;
|
||
}
|
||
|
||
if(keyLength < 2 * md.digestLength + 2) {
|
||
throw new Error('RSAES-OAEP key is too short for the hash function.');
|
||
}
|
||
|
||
if(!label) {
|
||
label = '';
|
||
}
|
||
md.update(label, 'raw');
|
||
var lHash = md.digest().getBytes();
|
||
|
||
// split the message into its parts
|
||
var y = em.charAt(0);
|
||
var maskedSeed = em.substring(1, md.digestLength + 1);
|
||
var maskedDB = em.substring(1 + md.digestLength);
|
||
|
||
var seedMask = rsa_mgf1(maskedDB, md.digestLength, mgf1Md);
|
||
var seed = forge.util.xorBytes(maskedSeed, seedMask, maskedSeed.length);
|
||
|
||
var dbMask = rsa_mgf1(seed, keyLength - md.digestLength - 1, mgf1Md);
|
||
var db = forge.util.xorBytes(maskedDB, dbMask, maskedDB.length);
|
||
|
||
var lHashPrime = db.substring(0, md.digestLength);
|
||
|
||
// constant time check that all values match what is expected
|
||
var error = (y !== '\x00');
|
||
|
||
// constant time check lHash vs lHashPrime
|
||
for(var i = 0; i < md.digestLength; ++i) {
|
||
error |= (lHash.charAt(i) !== lHashPrime.charAt(i));
|
||
}
|
||
|
||
// "constant time" find the 0x1 byte separating the padding (zeros) from the
|
||
// message
|
||
// TODO: It must be possible to do this in a better/smarter way?
|
||
var in_ps = 1;
|
||
var index = md.digestLength;
|
||
for(var j = md.digestLength; j < db.length; j++) {
|
||
var code = db.charCodeAt(j);
|
||
|
||
var is_0 = (code & 0x1) ^ 0x1;
|
||
|
||
// non-zero if not 0 or 1 in the ps section
|
||
var error_mask = in_ps ? 0xfffe : 0x0000;
|
||
error |= (code & error_mask);
|
||
|
||
// latch in_ps to zero after we find 0x1
|
||
in_ps = in_ps & is_0;
|
||
index += in_ps;
|
||
}
|
||
|
||
if(error || db.charCodeAt(index) !== 0x1) {
|
||
throw new Error('Invalid RSAES-OAEP padding.');
|
||
}
|
||
|
||
return db.substring(index + 1);
|
||
};
|
||
|
||
function rsa_mgf1(seed, maskLength, hash) {
|
||
// default to SHA-1 message digest
|
||
if(!hash) {
|
||
hash = forge.md.sha1.create();
|
||
}
|
||
var t = '';
|
||
var count = Math.ceil(maskLength / hash.digestLength);
|
||
for(var i = 0; i < count; ++i) {
|
||
var c = String.fromCharCode(
|
||
(i >> 24) & 0xFF, (i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF);
|
||
hash.start();
|
||
hash.update(seed + c);
|
||
t += hash.digest().getBytes();
|
||
}
|
||
return t.substring(0, maskLength);
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pkcs1';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pkcs1',['require', 'module', './util', './random', './sha1'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Prime number generation API.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// forge.prime already defined
|
||
if(forge.prime) {
|
||
return;
|
||
}
|
||
|
||
/* PRIME API */
|
||
var prime = forge.prime = forge.prime || {};
|
||
|
||
var BigInteger = forge.jsbn.BigInteger;
|
||
|
||
// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
|
||
var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
|
||
var THIRTY = new BigInteger(null);
|
||
THIRTY.fromInt(30);
|
||
var op_or = function(x, y) {return x|y;};
|
||
|
||
/**
|
||
* Generates a random probable prime with the given number of bits.
|
||
*
|
||
* Alternative algorithms can be specified by name as a string or as an
|
||
* object with custom options like so:
|
||
*
|
||
* {
|
||
* name: 'PRIMEINC',
|
||
* options: {
|
||
* maxBlockTime: <the maximum amount of time to block the main
|
||
* thread before allowing I/O other JS to run>,
|
||
* millerRabinTests: <the number of miller-rabin tests to run>,
|
||
* workerScript: <the worker script URL>,
|
||
* workers: <the number of web workers (if supported) to use,
|
||
* -1 to use estimated cores minus one>.
|
||
* workLoad: the size of the work load, ie: number of possible prime
|
||
* numbers for each web worker to check per work assignment,
|
||
* (default: 100).
|
||
* }
|
||
* }
|
||
*
|
||
* @param bits the number of bits for the prime number.
|
||
* @param options the options to use.
|
||
* [algorithm] the algorithm to use (default: 'PRIMEINC').
|
||
* [prng] a custom crypto-secure pseudo-random number generator to use,
|
||
* that must define "getBytesSync".
|
||
*
|
||
* @return callback(err, num) called once the operation completes.
|
||
*/
|
||
prime.generateProbablePrime = function(bits, options, callback) {
|
||
if(typeof options === 'function') {
|
||
callback = options;
|
||
options = {};
|
||
}
|
||
options = options || {};
|
||
|
||
// default to PRIMEINC algorithm
|
||
var algorithm = options.algorithm || 'PRIMEINC';
|
||
if(typeof algorithm === 'string') {
|
||
algorithm = {name: algorithm};
|
||
}
|
||
algorithm.options = algorithm.options || {};
|
||
|
||
// create prng with api that matches BigInteger secure random
|
||
var prng = options.prng || forge.random;
|
||
var rng = {
|
||
// x is an array to fill with bytes
|
||
nextBytes: function(x) {
|
||
var b = prng.getBytesSync(x.length);
|
||
for(var i = 0; i < x.length; ++i) {
|
||
x[i] = b.charCodeAt(i);
|
||
}
|
||
}
|
||
};
|
||
|
||
if(algorithm.name === 'PRIMEINC') {
|
||
return primeincFindPrime(bits, rng, algorithm.options, callback);
|
||
}
|
||
|
||
throw new Error('Invalid prime generation algorithm: ' + algorithm.name);
|
||
};
|
||
|
||
function primeincFindPrime(bits, rng, options, callback) {
|
||
if('workers' in options) {
|
||
return primeincFindPrimeWithWorkers(bits, rng, options, callback);
|
||
}
|
||
return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
|
||
}
|
||
|
||
function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) {
|
||
// initialize random number
|
||
var num = generateRandom(bits, rng);
|
||
|
||
/* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
|
||
number we are given is always aligned at 30k + 1. Each time the number is
|
||
determined not to be prime we add to get to the next 'i', eg: if the number
|
||
was at 30k + 1 we add 6. */
|
||
var deltaIdx = 0;
|
||
|
||
// get required number of MR tests
|
||
var mrTests = getMillerRabinTests(num.bitLength());
|
||
if('millerRabinTests' in options) {
|
||
mrTests = options.millerRabinTests;
|
||
}
|
||
|
||
// find prime nearest to 'num' for maxBlockTime ms
|
||
// 10 ms gives 5ms of leeway for other calculations before dropping
|
||
// below 60fps (1000/60 == 16.67), but in reality, the number will
|
||
// likely be higher due to an 'atomic' big int modPow
|
||
var maxBlockTime = 10;
|
||
if('maxBlockTime' in options) {
|
||
maxBlockTime = options.maxBlockTime;
|
||
}
|
||
var start = +new Date();
|
||
do {
|
||
// overflow, regenerate random number
|
||
if(num.bitLength() > bits) {
|
||
num = generateRandom(bits, rng);
|
||
}
|
||
// do primality test
|
||
if(num.isProbablePrime(mrTests)) {
|
||
return callback(null, num);
|
||
}
|
||
// get next potential prime
|
||
num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
|
||
} while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime));
|
||
|
||
// keep trying (setImmediate would be better here)
|
||
forge.util.setImmediate(function() {
|
||
primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
|
||
});
|
||
}
|
||
|
||
function primeincFindPrimeWithWorkers(bits, rng, options, callback) {
|
||
// web workers unavailable
|
||
if(typeof Worker === 'undefined') {
|
||
return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
|
||
}
|
||
|
||
// initialize random number
|
||
var num = generateRandom(bits, rng);
|
||
|
||
// use web workers to generate keys
|
||
var numWorkers = options.workers;
|
||
var workLoad = options.workLoad || 100;
|
||
var range = workLoad * 30 / 8;
|
||
var workerScript = options.workerScript || 'forge/prime.worker.js';
|
||
if(numWorkers === -1) {
|
||
return forge.util.estimateCores(function(err, cores) {
|
||
if(err) {
|
||
// default to 2
|
||
cores = 2;
|
||
}
|
||
numWorkers = cores - 1;
|
||
generate();
|
||
});
|
||
}
|
||
generate();
|
||
|
||
function generate() {
|
||
// require at least 1 worker
|
||
numWorkers = Math.max(1, numWorkers);
|
||
|
||
// TODO: consider optimizing by starting workers outside getPrime() ...
|
||
// note that in order to clean up they will have to be made internally
|
||
// asynchronous which may actually be slower
|
||
|
||
// start workers immediately
|
||
var workers = [];
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
// FIXME: fix path or use blob URLs
|
||
workers[i] = new Worker(workerScript);
|
||
}
|
||
var running = numWorkers;
|
||
|
||
// listen for requests from workers and assign ranges to find prime
|
||
for(var i = 0; i < numWorkers; ++i) {
|
||
workers[i].addEventListener('message', workerMessage);
|
||
}
|
||
|
||
/* Note: The distribution of random numbers is unknown. Therefore, each
|
||
web worker is continuously allocated a range of numbers to check for a
|
||
random number until one is found.
|
||
|
||
Every 30 numbers will be checked just 8 times, because prime numbers
|
||
have the form:
|
||
|
||
30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this)
|
||
|
||
Therefore, if we want a web worker to run N checks before asking for
|
||
a new range of numbers, each range must contain N*30/8 numbers.
|
||
|
||
For 100 checks (workLoad), this is a range of 375. */
|
||
|
||
var found = false;
|
||
function workerMessage(e) {
|
||
// ignore message, prime already found
|
||
if(found) {
|
||
return;
|
||
}
|
||
|
||
--running;
|
||
var data = e.data;
|
||
if(data.found) {
|
||
// terminate all workers
|
||
for(var i = 0; i < workers.length; ++i) {
|
||
workers[i].terminate();
|
||
}
|
||
found = true;
|
||
return callback(null, new BigInteger(data.prime, 16));
|
||
}
|
||
|
||
// overflow, regenerate random number
|
||
if(num.bitLength() > bits) {
|
||
num = generateRandom(bits, rng);
|
||
}
|
||
|
||
// assign new range to check
|
||
var hex = num.toString(16);
|
||
|
||
// start prime search
|
||
e.target.postMessage({
|
||
hex: hex,
|
||
workLoad: workLoad
|
||
});
|
||
|
||
num.dAddOffset(range, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generates a random number using the given number of bits and RNG.
|
||
*
|
||
* @param bits the number of bits for the number.
|
||
* @param rng the random number generator to use.
|
||
*
|
||
* @return the random number.
|
||
*/
|
||
function generateRandom(bits, rng) {
|
||
var num = new BigInteger(bits, rng);
|
||
// force MSB set
|
||
var bits1 = bits - 1;
|
||
if(!num.testBit(bits1)) {
|
||
num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num);
|
||
}
|
||
// align number on 30k+1 boundary
|
||
num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0);
|
||
return num;
|
||
}
|
||
|
||
/**
|
||
* Returns the required number of Miller-Rabin tests to generate a
|
||
* prime with an error probability of (1/2)^80.
|
||
*
|
||
* See Handbook of Applied Cryptography Chapter 4, Table 4.4.
|
||
*
|
||
* @param bits the bit size.
|
||
*
|
||
* @return the required number of iterations.
|
||
*/
|
||
function getMillerRabinTests(bits) {
|
||
if(bits <= 100) return 27;
|
||
if(bits <= 150) return 18;
|
||
if(bits <= 200) return 15;
|
||
if(bits <= 250) return 12;
|
||
if(bits <= 300) return 9;
|
||
if(bits <= 350) return 8;
|
||
if(bits <= 400) return 7;
|
||
if(bits <= 500) return 6;
|
||
if(bits <= 600) return 5;
|
||
if(bits <= 800) return 4;
|
||
if(bits <= 1250) return 3;
|
||
return 2;
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'prime';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/prime',['require', 'module', './util', './jsbn', './random'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of basic RSA algorithms.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*
|
||
* The only algorithm currently supported for PKI is RSA.
|
||
*
|
||
* An RSA key is often stored in ASN.1 DER format. The SubjectPublicKeyInfo
|
||
* ASN.1 structure is composed of an algorithm of type AlgorithmIdentifier
|
||
* and a subjectPublicKey of type bit string.
|
||
*
|
||
* The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
|
||
* for the algorithm, if any. In the case of RSA, there aren't any.
|
||
*
|
||
* SubjectPublicKeyInfo ::= SEQUENCE {
|
||
* algorithm AlgorithmIdentifier,
|
||
* subjectPublicKey BIT STRING
|
||
* }
|
||
*
|
||
* AlgorithmIdentifer ::= SEQUENCE {
|
||
* algorithm OBJECT IDENTIFIER,
|
||
* parameters ANY DEFINED BY algorithm OPTIONAL
|
||
* }
|
||
*
|
||
* For an RSA public key, the subjectPublicKey is:
|
||
*
|
||
* RSAPublicKey ::= SEQUENCE {
|
||
* modulus INTEGER, -- n
|
||
* publicExponent INTEGER -- e
|
||
* }
|
||
*
|
||
* PrivateKeyInfo ::= SEQUENCE {
|
||
* version Version,
|
||
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
|
||
* privateKey PrivateKey,
|
||
* attributes [0] IMPLICIT Attributes OPTIONAL
|
||
* }
|
||
*
|
||
* Version ::= INTEGER
|
||
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
* PrivateKey ::= OCTET STRING
|
||
* Attributes ::= SET OF Attribute
|
||
*
|
||
* An RSA private key as the following structure:
|
||
*
|
||
* RSAPrivateKey ::= SEQUENCE {
|
||
* version Version,
|
||
* modulus INTEGER, -- n
|
||
* publicExponent INTEGER, -- e
|
||
* privateExponent INTEGER, -- d
|
||
* prime1 INTEGER, -- p
|
||
* prime2 INTEGER, -- q
|
||
* exponent1 INTEGER, -- d mod (p-1)
|
||
* exponent2 INTEGER, -- d mod (q-1)
|
||
* coefficient INTEGER -- (inverse of q) mod p
|
||
* }
|
||
*
|
||
* Version ::= INTEGER
|
||
*
|
||
* The OID for the RSA key algorithm is: 1.2.840.113549.1.1.1
|
||
*/
|
||
(function() {
|
||
function initModule(forge) {
|
||
/* ########## Begin module implementation ########## */
|
||
|
||
if(typeof BigInteger === 'undefined') {
|
||
var BigInteger = forge.jsbn.BigInteger;
|
||
}
|
||
|
||
// shortcut for asn.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
/*
|
||
* RSA encryption and decryption, see RFC 2313.
|
||
*/
|
||
forge.pki = forge.pki || {};
|
||
forge.pki.rsa = forge.rsa = forge.rsa || {};
|
||
var pki = forge.pki;
|
||
|
||
// for finding primes, which are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
|
||
var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
|
||
|
||
// validator for a PrivateKeyInfo structure
|
||
var privateKeyValidator = {
|
||
// PrivateKeyInfo
|
||
name: 'PrivateKeyInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
// Version (INTEGER)
|
||
name: 'PrivateKeyInfo.version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyVersion'
|
||
}, {
|
||
// privateKeyAlgorithm
|
||
name: 'PrivateKeyInfo.privateKeyAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'AlgorithmIdentifier.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'privateKeyOid'
|
||
}]
|
||
}, {
|
||
// PrivateKey
|
||
name: 'PrivateKeyInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'privateKey'
|
||
}]
|
||
};
|
||
|
||
// validator for an RSA private key
|
||
var rsaPrivateKeyValidator = {
|
||
// RSAPrivateKey
|
||
name: 'RSAPrivateKey',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
// Version (INTEGER)
|
||
name: 'RSAPrivateKey.version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyVersion'
|
||
}, {
|
||
// modulus (n)
|
||
name: 'RSAPrivateKey.modulus',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyModulus'
|
||
}, {
|
||
// publicExponent (e)
|
||
name: 'RSAPrivateKey.publicExponent',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyPublicExponent'
|
||
}, {
|
||
// privateExponent (d)
|
||
name: 'RSAPrivateKey.privateExponent',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyPrivateExponent'
|
||
}, {
|
||
// prime1 (p)
|
||
name: 'RSAPrivateKey.prime1',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyPrime1'
|
||
}, {
|
||
// prime2 (q)
|
||
name: 'RSAPrivateKey.prime2',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyPrime2'
|
||
}, {
|
||
// exponent1 (d mod (p-1))
|
||
name: 'RSAPrivateKey.exponent1',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyExponent1'
|
||
}, {
|
||
// exponent2 (d mod (q-1))
|
||
name: 'RSAPrivateKey.exponent2',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyExponent2'
|
||
}, {
|
||
// coefficient ((inverse of q) mod p)
|
||
name: 'RSAPrivateKey.coefficient',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'privateKeyCoefficient'
|
||
}]
|
||
};
|
||
|
||
// validator for an RSA public key
|
||
var rsaPublicKeyValidator = {
|
||
// RSAPublicKey
|
||
name: 'RSAPublicKey',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
// modulus (n)
|
||
name: 'RSAPublicKey.modulus',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'publicKeyModulus'
|
||
}, {
|
||
// publicExponent (e)
|
||
name: 'RSAPublicKey.exponent',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'publicKeyExponent'
|
||
}]
|
||
};
|
||
|
||
// validator for an SubjectPublicKeyInfo structure
|
||
// Note: Currently only works with an RSA public key
|
||
var publicKeyValidator = forge.pki.rsa.publicKeyValidator = {
|
||
name: 'SubjectPublicKeyInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'subjectPublicKeyInfo',
|
||
value: [{
|
||
name: 'SubjectPublicKeyInfo.AlgorithmIdentifier',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'AlgorithmIdentifier.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'publicKeyOid'
|
||
}]
|
||
}, {
|
||
// subjectPublicKey
|
||
name: 'SubjectPublicKeyInfo.subjectPublicKey',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.BITSTRING,
|
||
constructed: false,
|
||
value: [{
|
||
// RSAPublicKey
|
||
name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
optional: true,
|
||
captureAsn1: 'rsaPublicKey'
|
||
}]
|
||
}]
|
||
};
|
||
|
||
/**
|
||
* Wrap digest in DigestInfo object.
|
||
*
|
||
* This function implements EMSA-PKCS1-v1_5-ENCODE as per RFC 3447.
|
||
*
|
||
* DigestInfo ::= SEQUENCE {
|
||
* digestAlgorithm DigestAlgorithmIdentifier,
|
||
* digest Digest
|
||
* }
|
||
*
|
||
* DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
* Digest ::= OCTET STRING
|
||
*
|
||
* @param md the message digest object with the hash to sign.
|
||
*
|
||
* @return the encoded message (ready for RSA encrytion)
|
||
*/
|
||
var emsaPkcs1v15encode = function(md) {
|
||
// get the oid for the algorithm
|
||
var oid;
|
||
if(md.algorithm in pki.oids) {
|
||
oid = pki.oids[md.algorithm];
|
||
} else {
|
||
var error = new Error('Unknown message digest algorithm.');
|
||
error.algorithm = md.algorithm;
|
||
throw error;
|
||
}
|
||
var oidBytes = asn1.oidToDer(oid).getBytes();
|
||
|
||
// create the digest info
|
||
var digestInfo = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
var digestAlgorithm = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
digestAlgorithm.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OID, false, oidBytes));
|
||
digestAlgorithm.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.NULL, false, ''));
|
||
var digest = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
|
||
false, md.digest().getBytes());
|
||
digestInfo.value.push(digestAlgorithm);
|
||
digestInfo.value.push(digest);
|
||
|
||
// encode digest info
|
||
return asn1.toDer(digestInfo).getBytes();
|
||
};
|
||
|
||
/**
|
||
* Performs x^c mod n (RSA encryption or decryption operation).
|
||
*
|
||
* @param x the number to raise and mod.
|
||
* @param key the key to use.
|
||
* @param pub true if the key is public, false if private.
|
||
*
|
||
* @return the result of x^c mod n.
|
||
*/
|
||
var _modPow = function(x, key, pub) {
|
||
if(pub) {
|
||
return x.modPow(key.e, key.n);
|
||
}
|
||
|
||
if(!key.p || !key.q) {
|
||
// allow calculation without CRT params (slow)
|
||
return x.modPow(key.d, key.n);
|
||
}
|
||
|
||
// pre-compute dP, dQ, and qInv if necessary
|
||
if(!key.dP) {
|
||
key.dP = key.d.mod(key.p.subtract(BigInteger.ONE));
|
||
}
|
||
if(!key.dQ) {
|
||
key.dQ = key.d.mod(key.q.subtract(BigInteger.ONE));
|
||
}
|
||
if(!key.qInv) {
|
||
key.qInv = key.q.modInverse(key.p);
|
||
}
|
||
|
||
/* Chinese remainder theorem (CRT) states:
|
||
|
||
Suppose n1, n2, ..., nk are positive integers which are pairwise
|
||
coprime (n1 and n2 have no common factors other than 1). For any
|
||
integers x1, x2, ..., xk there exists an integer x solving the
|
||
system of simultaneous congruences (where ~= means modularly
|
||
congruent so a ~= b mod n means a mod n = b mod n):
|
||
|
||
x ~= x1 mod n1
|
||
x ~= x2 mod n2
|
||
...
|
||
x ~= xk mod nk
|
||
|
||
This system of congruences has a single simultaneous solution x
|
||
between 0 and n - 1. Furthermore, each xk solution and x itself
|
||
is congruent modulo the product n = n1*n2*...*nk.
|
||
So x1 mod n = x2 mod n = xk mod n = x mod n.
|
||
|
||
The single simultaneous solution x can be solved with the following
|
||
equation:
|
||
|
||
x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni.
|
||
|
||
Where x is less than n, xi = x mod ni.
|
||
|
||
For RSA we are only concerned with k = 2. The modulus n = pq, where
|
||
p and q are coprime. The RSA decryption algorithm is:
|
||
|
||
y = x^d mod n
|
||
|
||
Given the above:
|
||
|
||
x1 = x^d mod p
|
||
r1 = n/p = q
|
||
s1 = q^-1 mod p
|
||
x2 = x^d mod q
|
||
r2 = n/q = p
|
||
s2 = p^-1 mod q
|
||
|
||
So y = (x1r1s1 + x2r2s2) mod n
|
||
= ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n
|
||
|
||
According to Fermat's Little Theorem, if the modulus P is prime,
|
||
for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P.
|
||
Since A is not divisible by P it follows that if:
|
||
N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore:
|
||
|
||
A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort
|
||
to calculate). In order to calculate x^d mod p more quickly the
|
||
exponent d mod (p - 1) is stored in the RSA private key (the same
|
||
is done for x^d mod q). These values are referred to as dP and dQ
|
||
respectively. Therefore we now have:
|
||
|
||
y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n
|
||
|
||
Since we'll be reducing x^dP by modulo p (same for q) we can also
|
||
reduce x by p (and q respectively) before hand. Therefore, let
|
||
|
||
xp = ((x mod p)^dP mod p), and
|
||
xq = ((x mod q)^dQ mod q), yielding:
|
||
|
||
y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n
|
||
|
||
This can be further reduced to a simple algorithm that only
|
||
requires 1 inverse (the q inverse is used) to be used and stored.
|
||
The algorithm is called Garner's algorithm. If qInv is the
|
||
inverse of q, we simply calculate:
|
||
|
||
y = (qInv*(xp - xq) mod p) * q + xq
|
||
|
||
However, there are two further complications. First, we need to
|
||
ensure that xp > xq to prevent signed BigIntegers from being used
|
||
so we add p until this is true (since we will be mod'ing with
|
||
p anyway). Then, there is a known timing attack on algorithms
|
||
using the CRT. To mitigate this risk, "cryptographic blinding"
|
||
should be used. This requires simply generating a random number r
|
||
between 0 and n-1 and its inverse and multiplying x by r^e before
|
||
calculating y and then multiplying y by r^-1 afterwards. Note that
|
||
r must be coprime with n (gcd(r, n) === 1) in order to have an
|
||
inverse.
|
||
*/
|
||
|
||
// cryptographic blinding
|
||
var r;
|
||
do {
|
||
r = new BigInteger(
|
||
forge.util.bytesToHex(forge.random.getBytes(key.n.bitLength() / 8)),
|
||
16);
|
||
} while(r.compareTo(key.n) >= 0 || !r.gcd(key.n).equals(BigInteger.ONE));
|
||
x = x.multiply(r.modPow(key.e, key.n)).mod(key.n);
|
||
|
||
// calculate xp and xq
|
||
var xp = x.mod(key.p).modPow(key.dP, key.p);
|
||
var xq = x.mod(key.q).modPow(key.dQ, key.q);
|
||
|
||
// xp must be larger than xq to avoid signed bit usage
|
||
while(xp.compareTo(xq) < 0) {
|
||
xp = xp.add(key.p);
|
||
}
|
||
|
||
// do last step
|
||
var y = xp.subtract(xq)
|
||
.multiply(key.qInv).mod(key.p)
|
||
.multiply(key.q).add(xq);
|
||
|
||
// remove effect of random for cryptographic blinding
|
||
y = y.multiply(r.modInverse(key.n)).mod(key.n);
|
||
|
||
return y;
|
||
};
|
||
|
||
/**
|
||
* NOTE: THIS METHOD IS DEPRECATED, use 'sign' on a private key object or
|
||
* 'encrypt' on a public key object instead.
|
||
*
|
||
* Performs RSA encryption.
|
||
*
|
||
* The parameter bt controls whether to put padding bytes before the
|
||
* message passed in. Set bt to either true or false to disable padding
|
||
* completely (in order to handle e.g. EMSA-PSS encoding seperately before),
|
||
* signaling whether the encryption operation is a public key operation
|
||
* (i.e. encrypting data) or not, i.e. private key operation (data signing).
|
||
*
|
||
* For PKCS#1 v1.5 padding pass in the block type to use, i.e. either 0x01
|
||
* (for signing) or 0x02 (for encryption). The key operation mode (private
|
||
* or public) is derived from this flag in that case).
|
||
*
|
||
* @param m the message to encrypt as a byte string.
|
||
* @param key the RSA key to use.
|
||
* @param bt for PKCS#1 v1.5 padding, the block type to use
|
||
* (0x01 for private key, 0x02 for public),
|
||
* to disable padding: true = public key, false = private key.
|
||
*
|
||
* @return the encrypted bytes as a string.
|
||
*/
|
||
pki.rsa.encrypt = function(m, key, bt) {
|
||
var pub = bt;
|
||
var eb;
|
||
|
||
// get the length of the modulus in bytes
|
||
var k = Math.ceil(key.n.bitLength() / 8);
|
||
|
||
if(bt !== false && bt !== true) {
|
||
// legacy, default to PKCS#1 v1.5 padding
|
||
pub = (bt === 0x02);
|
||
eb = _encodePkcs1_v1_5(m, key, bt);
|
||
} else {
|
||
eb = forge.util.createBuffer();
|
||
eb.putBytes(m);
|
||
}
|
||
|
||
// load encryption block as big integer 'x'
|
||
// FIXME: hex conversion inefficient, get BigInteger w/byte strings
|
||
var x = new BigInteger(eb.toHex(), 16);
|
||
|
||
// do RSA encryption
|
||
var y = _modPow(x, key, pub);
|
||
|
||
// convert y into the encrypted data byte string, if y is shorter in
|
||
// bytes than k, then prepend zero bytes to fill up ed
|
||
// FIXME: hex conversion inefficient, get BigInteger w/byte strings
|
||
var yhex = y.toString(16);
|
||
var ed = forge.util.createBuffer();
|
||
var zeros = k - Math.ceil(yhex.length / 2);
|
||
while(zeros > 0) {
|
||
ed.putByte(0x00);
|
||
--zeros;
|
||
}
|
||
ed.putBytes(forge.util.hexToBytes(yhex));
|
||
return ed.getBytes();
|
||
};
|
||
|
||
/**
|
||
* NOTE: THIS METHOD IS DEPRECATED, use 'decrypt' on a private key object or
|
||
* 'verify' on a public key object instead.
|
||
*
|
||
* Performs RSA decryption.
|
||
*
|
||
* The parameter ml controls whether to apply PKCS#1 v1.5 padding
|
||
* or not. Set ml = false to disable padding removal completely
|
||
* (in order to handle e.g. EMSA-PSS later on) and simply pass back
|
||
* the RSA encryption block.
|
||
*
|
||
* @param ed the encrypted data to decrypt in as a byte string.
|
||
* @param key the RSA key to use.
|
||
* @param pub true for a public key operation, false for private.
|
||
* @param ml the message length, if known, false to disable padding.
|
||
*
|
||
* @return the decrypted message as a byte string.
|
||
*/
|
||
pki.rsa.decrypt = function(ed, key, pub, ml) {
|
||
// get the length of the modulus in bytes
|
||
var k = Math.ceil(key.n.bitLength() / 8);
|
||
|
||
// error if the length of the encrypted data ED is not k
|
||
if(ed.length !== k) {
|
||
var error = new Error('Encrypted message length is invalid.');
|
||
error.length = ed.length;
|
||
error.expected = k;
|
||
throw error;
|
||
}
|
||
|
||
// convert encrypted data into a big integer
|
||
// FIXME: hex conversion inefficient, get BigInteger w/byte strings
|
||
var y = new BigInteger(forge.util.createBuffer(ed).toHex(), 16);
|
||
|
||
// y must be less than the modulus or it wasn't the result of
|
||
// a previous mod operation (encryption) using that modulus
|
||
if(y.compareTo(key.n) >= 0) {
|
||
throw new Error('Encrypted message is invalid.');
|
||
}
|
||
|
||
// do RSA decryption
|
||
var x = _modPow(y, key, pub);
|
||
|
||
// create the encryption block, if x is shorter in bytes than k, then
|
||
// prepend zero bytes to fill up eb
|
||
// FIXME: hex conversion inefficient, get BigInteger w/byte strings
|
||
var xhex = x.toString(16);
|
||
var eb = forge.util.createBuffer();
|
||
var zeros = k - Math.ceil(xhex.length / 2);
|
||
while(zeros > 0) {
|
||
eb.putByte(0x00);
|
||
--zeros;
|
||
}
|
||
eb.putBytes(forge.util.hexToBytes(xhex));
|
||
|
||
if(ml !== false) {
|
||
// legacy, default to PKCS#1 v1.5 padding
|
||
return _decodePkcs1_v1_5(eb.getBytes(), key, pub);
|
||
}
|
||
|
||
// return message
|
||
return eb.getBytes();
|
||
};
|
||
|
||
/**
|
||
* Creates an RSA key-pair generation state object. It is used to allow
|
||
* key-generation to be performed in steps. It also allows for a UI to
|
||
* display progress updates.
|
||
*
|
||
* @param bits the size for the private key in bits, defaults to 2048.
|
||
* @param e the public exponent to use, defaults to 65537 (0x10001).
|
||
* @param [options] the options to use.
|
||
* prng a custom crypto-secure pseudo-random number generator to use,
|
||
* that must define "getBytesSync".
|
||
* algorithm the algorithm to use (default: 'PRIMEINC').
|
||
*
|
||
* @return the state object to use to generate the key-pair.
|
||
*/
|
||
pki.rsa.createKeyPairGenerationState = function(bits, e, options) {
|
||
// TODO: migrate step-based prime generation code to forge.prime
|
||
|
||
// set default bits
|
||
if(typeof(bits) === 'string') {
|
||
bits = parseInt(bits, 10);
|
||
}
|
||
bits = bits || 2048;
|
||
|
||
// create prng with api that matches BigInteger secure random
|
||
options = options || {};
|
||
var prng = options.prng || forge.random;
|
||
var rng = {
|
||
// x is an array to fill with bytes
|
||
nextBytes: function(x) {
|
||
var b = prng.getBytesSync(x.length);
|
||
for(var i = 0; i < x.length; ++i) {
|
||
x[i] = b.charCodeAt(i);
|
||
}
|
||
}
|
||
};
|
||
|
||
var algorithm = options.algorithm || 'PRIMEINC';
|
||
|
||
// create PRIMEINC algorithm state
|
||
var rval;
|
||
if(algorithm === 'PRIMEINC') {
|
||
rval = {
|
||
algorithm: algorithm,
|
||
state: 0,
|
||
bits: bits,
|
||
rng: rng,
|
||
eInt: e || 65537,
|
||
e: new BigInteger(null),
|
||
p: null,
|
||
q: null,
|
||
qBits: bits >> 1,
|
||
pBits: bits - (bits >> 1),
|
||
pqState: 0,
|
||
num: null,
|
||
keys: null
|
||
};
|
||
rval.e.fromInt(rval.eInt);
|
||
} else {
|
||
throw new Error('Invalid key generation algorithm: ' + algorithm);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Attempts to runs the key-generation algorithm for at most n seconds
|
||
* (approximately) using the given state. When key-generation has completed,
|
||
* the keys will be stored in state.keys.
|
||
*
|
||
* To use this function to update a UI while generating a key or to prevent
|
||
* causing browser lockups/warnings, set "n" to a value other than 0. A
|
||
* simple pattern for generating a key and showing a progress indicator is:
|
||
*
|
||
* var state = pki.rsa.createKeyPairGenerationState(2048);
|
||
* var step = function() {
|
||
* // step key-generation, run algorithm for 100 ms, repeat
|
||
* if(!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
|
||
* setTimeout(step, 1);
|
||
* } else {
|
||
* // key-generation complete
|
||
* // TODO: turn off progress indicator here
|
||
* // TODO: use the generated key-pair in "state.keys"
|
||
* }
|
||
* };
|
||
* // TODO: turn on progress indicator here
|
||
* setTimeout(step, 0);
|
||
*
|
||
* @param state the state to use.
|
||
* @param n the maximum number of milliseconds to run the algorithm for, 0
|
||
* to run the algorithm to completion.
|
||
*
|
||
* @return true if the key-generation completed, false if not.
|
||
*/
|
||
pki.rsa.stepKeyPairGenerationState = function(state, n) {
|
||
// set default algorithm if not set
|
||
if(!('algorithm' in state)) {
|
||
state.algorithm = 'PRIMEINC';
|
||
}
|
||
|
||
// TODO: migrate step-based prime generation code to forge.prime
|
||
// TODO: abstract as PRIMEINC algorithm
|
||
|
||
// do key generation (based on Tom Wu's rsa.js, see jsbn.js license)
|
||
// with some minor optimizations and designed to run in steps
|
||
|
||
// local state vars
|
||
var THIRTY = new BigInteger(null);
|
||
THIRTY.fromInt(30);
|
||
var deltaIdx = 0;
|
||
var op_or = function(x,y) { return x|y; };
|
||
|
||
// keep stepping until time limit is reached or done
|
||
var t1 = +new Date();
|
||
var t2;
|
||
var total = 0;
|
||
while(state.keys === null && (n <= 0 || total < n)) {
|
||
// generate p or q
|
||
if(state.state === 0) {
|
||
/* Note: All primes are of the form:
|
||
|
||
30k+i, for i < 30 and gcd(30, i)=1, where there are 8 values for i
|
||
|
||
When we generate a random number, we always align it at 30k + 1. Each
|
||
time the number is determined not to be prime we add to get to the
|
||
next 'i', eg: if the number was at 30k + 1 we add 6. */
|
||
var bits = (state.p === null) ? state.pBits : state.qBits;
|
||
var bits1 = bits - 1;
|
||
|
||
// get a random number
|
||
if(state.pqState === 0) {
|
||
state.num = new BigInteger(bits, state.rng);
|
||
// force MSB set
|
||
if(!state.num.testBit(bits1)) {
|
||
state.num.bitwiseTo(
|
||
BigInteger.ONE.shiftLeft(bits1), op_or, state.num);
|
||
}
|
||
// align number on 30k+1 boundary
|
||
state.num.dAddOffset(31 - state.num.mod(THIRTY).byteValue(), 0);
|
||
deltaIdx = 0;
|
||
|
||
++state.pqState;
|
||
} else if(state.pqState === 1) {
|
||
// try to make the number a prime
|
||
if(state.num.bitLength() > bits) {
|
||
// overflow, try again
|
||
state.pqState = 0;
|
||
// do primality test
|
||
} else if(state.num.isProbablePrime(
|
||
_getMillerRabinTests(state.num.bitLength()))) {
|
||
++state.pqState;
|
||
} else {
|
||
// get next potential prime
|
||
state.num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
|
||
}
|
||
} else if(state.pqState === 2) {
|
||
// ensure number is coprime with e
|
||
state.pqState =
|
||
(state.num.subtract(BigInteger.ONE).gcd(state.e)
|
||
.compareTo(BigInteger.ONE) === 0) ? 3 : 0;
|
||
} else if(state.pqState === 3) {
|
||
// store p or q
|
||
state.pqState = 0;
|
||
if(state.p === null) {
|
||
state.p = state.num;
|
||
} else {
|
||
state.q = state.num;
|
||
}
|
||
|
||
// advance state if both p and q are ready
|
||
if(state.p !== null && state.q !== null) {
|
||
++state.state;
|
||
}
|
||
state.num = null;
|
||
}
|
||
} else if(state.state === 1) {
|
||
// ensure p is larger than q (swap them if not)
|
||
if(state.p.compareTo(state.q) < 0) {
|
||
state.num = state.p;
|
||
state.p = state.q;
|
||
state.q = state.num;
|
||
}
|
||
++state.state;
|
||
} else if(state.state === 2) {
|
||
// compute phi: (p - 1)(q - 1) (Euler's totient function)
|
||
state.p1 = state.p.subtract(BigInteger.ONE);
|
||
state.q1 = state.q.subtract(BigInteger.ONE);
|
||
state.phi = state.p1.multiply(state.q1);
|
||
++state.state;
|
||
} else if(state.state === 3) {
|
||
// ensure e and phi are coprime
|
||
if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) === 0) {
|
||
// phi and e are coprime, advance
|
||
++state.state;
|
||
} else {
|
||
// phi and e aren't coprime, so generate a new p and q
|
||
state.p = null;
|
||
state.q = null;
|
||
state.state = 0;
|
||
}
|
||
} else if(state.state === 4) {
|
||
// create n, ensure n is has the right number of bits
|
||
state.n = state.p.multiply(state.q);
|
||
|
||
// ensure n is right number of bits
|
||
if(state.n.bitLength() === state.bits) {
|
||
// success, advance
|
||
++state.state;
|
||
} else {
|
||
// failed, get new q
|
||
state.q = null;
|
||
state.state = 0;
|
||
}
|
||
} else if(state.state === 5) {
|
||
// set keys
|
||
var d = state.e.modInverse(state.phi);
|
||
state.keys = {
|
||
privateKey: pki.rsa.setPrivateKey(
|
||
state.n, state.e, d, state.p, state.q,
|
||
d.mod(state.p1), d.mod(state.q1),
|
||
state.q.modInverse(state.p)),
|
||
publicKey: pki.rsa.setPublicKey(state.n, state.e)
|
||
};
|
||
}
|
||
|
||
// update timing
|
||
t2 = +new Date();
|
||
total += t2 - t1;
|
||
t1 = t2;
|
||
}
|
||
|
||
return state.keys !== null;
|
||
};
|
||
|
||
/**
|
||
* Generates an RSA public-private key pair in a single call.
|
||
*
|
||
* To generate a key-pair in steps (to allow for progress updates and to
|
||
* prevent blocking or warnings in slow browsers) then use the key-pair
|
||
* generation state functions.
|
||
*
|
||
* To generate a key-pair asynchronously (either through web-workers, if
|
||
* available, or by breaking up the work on the main thread), pass a
|
||
* callback function.
|
||
*
|
||
* @param [bits] the size for the private key in bits, defaults to 2048.
|
||
* @param [e] the public exponent to use, defaults to 65537.
|
||
* @param [options] options for key-pair generation, if given then 'bits'
|
||
* and 'e' must *not* be given:
|
||
* bits the size for the private key in bits, (default: 2048).
|
||
* e the public exponent to use, (default: 65537 (0x10001)).
|
||
* workerScript the worker script URL.
|
||
* workers the number of web workers (if supported) to use,
|
||
* (default: 2).
|
||
* workLoad the size of the work load, ie: number of possible prime
|
||
* numbers for each web worker to check per work assignment,
|
||
* (default: 100).
|
||
* e the public exponent to use, defaults to 65537.
|
||
* prng a custom crypto-secure pseudo-random number generator to use,
|
||
* that must define "getBytesSync".
|
||
* algorithm the algorithm to use (default: 'PRIMEINC').
|
||
* @param [callback(err, keypair)] called once the operation completes.
|
||
*
|
||
* @return an object with privateKey and publicKey properties.
|
||
*/
|
||
pki.rsa.generateKeyPair = function(bits, e, options, callback) {
|
||
// (bits), (options), (callback)
|
||
if(arguments.length === 1) {
|
||
if(typeof bits === 'object') {
|
||
options = bits;
|
||
bits = undefined;
|
||
} else if(typeof bits === 'function') {
|
||
callback = bits;
|
||
bits = undefined;
|
||
}
|
||
} else if(arguments.length === 2) {
|
||
// (bits, e), (bits, options), (bits, callback), (options, callback)
|
||
if(typeof bits === 'number') {
|
||
if(typeof e === 'function') {
|
||
callback = e;
|
||
e = undefined;
|
||
} else if(typeof e !== 'number') {
|
||
options = e;
|
||
e = undefined;
|
||
}
|
||
} else {
|
||
options = bits;
|
||
callback = e;
|
||
bits = undefined;
|
||
e = undefined;
|
||
}
|
||
} else if(arguments.length === 3) {
|
||
// (bits, e, options), (bits, e, callback), (bits, options, callback)
|
||
if(typeof e === 'number') {
|
||
if(typeof options === 'function') {
|
||
callback = options;
|
||
options = undefined;
|
||
}
|
||
} else {
|
||
callback = options;
|
||
options = e;
|
||
e = undefined;
|
||
}
|
||
}
|
||
options = options || {};
|
||
if(bits === undefined) {
|
||
bits = options.bits || 2048;
|
||
}
|
||
if(e === undefined) {
|
||
e = options.e || 0x10001;
|
||
}
|
||
var state = pki.rsa.createKeyPairGenerationState(bits, e, options);
|
||
if(!callback) {
|
||
pki.rsa.stepKeyPairGenerationState(state, 0);
|
||
return state.keys;
|
||
}
|
||
_generateKeyPair(state, options, callback);
|
||
};
|
||
|
||
/**
|
||
* Sets an RSA public key from BigIntegers modulus and exponent.
|
||
*
|
||
* @param n the modulus.
|
||
* @param e the exponent.
|
||
*
|
||
* @return the public key.
|
||
*/
|
||
pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
|
||
var key = {
|
||
n: n,
|
||
e: e
|
||
};
|
||
|
||
/**
|
||
* Encrypts the given data with this public key. Newer applications
|
||
* should use the 'RSA-OAEP' decryption scheme, 'RSAES-PKCS1-V1_5' is for
|
||
* legacy applications.
|
||
*
|
||
* @param data the byte string to encrypt.
|
||
* @param scheme the encryption scheme to use:
|
||
* 'RSAES-PKCS1-V1_5' (default),
|
||
* 'RSA-OAEP',
|
||
* 'RAW', 'NONE', or null to perform raw RSA encryption,
|
||
* an object with an 'encode' property set to a function
|
||
* with the signature 'function(data, key)' that returns
|
||
* a binary-encoded string representing the encoded data.
|
||
* @param schemeOptions any scheme-specific options.
|
||
*
|
||
* @return the encrypted byte string.
|
||
*/
|
||
key.encrypt = function(data, scheme, schemeOptions) {
|
||
if(typeof scheme === 'string') {
|
||
scheme = scheme.toUpperCase();
|
||
} else if(scheme === undefined) {
|
||
scheme = 'RSAES-PKCS1-V1_5';
|
||
}
|
||
|
||
if(scheme === 'RSAES-PKCS1-V1_5') {
|
||
scheme = {
|
||
encode: function(m, key, pub) {
|
||
return _encodePkcs1_v1_5(m, key, 0x02).getBytes();
|
||
}
|
||
};
|
||
} else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
|
||
scheme = {
|
||
encode: function(m, key) {
|
||
return forge.pkcs1.encode_rsa_oaep(key, m, schemeOptions);
|
||
}
|
||
};
|
||
} else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
|
||
scheme = { encode: function(e) { return e; } };
|
||
} else if(typeof scheme === 'string') {
|
||
throw new Error('Unsupported encryption scheme: "' + scheme + '".');
|
||
}
|
||
|
||
// do scheme-based encoding then rsa encryption
|
||
var e = scheme.encode(data, key, true);
|
||
return pki.rsa.encrypt(e, key, true);
|
||
};
|
||
|
||
/**
|
||
* Verifies the given signature against the given digest.
|
||
*
|
||
* PKCS#1 supports multiple (currently two) signature schemes:
|
||
* RSASSA-PKCS1-V1_5 and RSASSA-PSS.
|
||
*
|
||
* By default this implementation uses the "old scheme", i.e.
|
||
* RSASSA-PKCS1-V1_5, in which case once RSA-decrypted, the
|
||
* signature is an OCTET STRING that holds a DigestInfo.
|
||
*
|
||
* DigestInfo ::= SEQUENCE {
|
||
* digestAlgorithm DigestAlgorithmIdentifier,
|
||
* digest Digest
|
||
* }
|
||
* DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
* Digest ::= OCTET STRING
|
||
*
|
||
* To perform PSS signature verification, provide an instance
|
||
* of Forge PSS object as the scheme parameter.
|
||
*
|
||
* @param digest the message digest hash to compare against the signature,
|
||
* as a binary-encoded string.
|
||
* @param signature the signature to verify, as a binary-encoded string.
|
||
* @param scheme signature verification scheme to use:
|
||
* 'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
|
||
* a Forge PSS object for RSASSA-PSS,
|
||
* 'NONE' or null for none, DigestInfo will not be expected, but
|
||
* PKCS#1 v1.5 padding will still be used.
|
||
*
|
||
* @return true if the signature was verified, false if not.
|
||
*/
|
||
key.verify = function(digest, signature, scheme) {
|
||
if(typeof scheme === 'string') {
|
||
scheme = scheme.toUpperCase();
|
||
} else if(scheme === undefined) {
|
||
scheme = 'RSASSA-PKCS1-V1_5';
|
||
}
|
||
|
||
if(scheme === 'RSASSA-PKCS1-V1_5') {
|
||
scheme = {
|
||
verify: function(digest, d) {
|
||
// remove padding
|
||
d = _decodePkcs1_v1_5(d, key, true);
|
||
// d is ASN.1 BER-encoded DigestInfo
|
||
var obj = asn1.fromDer(d);
|
||
// compare the given digest to the decrypted one
|
||
return digest === obj.value[1].value;
|
||
}
|
||
};
|
||
} else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
|
||
scheme = {
|
||
verify: function(digest, d) {
|
||
// remove padding
|
||
d = _decodePkcs1_v1_5(d, key, true);
|
||
return digest === d;
|
||
}
|
||
};
|
||
}
|
||
|
||
// do rsa decryption w/o any decoding, then verify -- which does decoding
|
||
var d = pki.rsa.decrypt(signature, key, true, false);
|
||
return scheme.verify(digest, d, key.n.bitLength());
|
||
};
|
||
|
||
return key;
|
||
};
|
||
|
||
/**
|
||
* Sets an RSA private key from BigIntegers modulus, exponent, primes,
|
||
* prime exponents, and modular multiplicative inverse.
|
||
*
|
||
* @param n the modulus.
|
||
* @param e the public exponent.
|
||
* @param d the private exponent ((inverse of e) mod n).
|
||
* @param p the first prime.
|
||
* @param q the second prime.
|
||
* @param dP exponent1 (d mod (p-1)).
|
||
* @param dQ exponent2 (d mod (q-1)).
|
||
* @param qInv ((inverse of q) mod p)
|
||
*
|
||
* @return the private key.
|
||
*/
|
||
pki.setRsaPrivateKey = pki.rsa.setPrivateKey = function(
|
||
n, e, d, p, q, dP, dQ, qInv) {
|
||
var key = {
|
||
n: n,
|
||
e: e,
|
||
d: d,
|
||
p: p,
|
||
q: q,
|
||
dP: dP,
|
||
dQ: dQ,
|
||
qInv: qInv
|
||
};
|
||
|
||
/**
|
||
* Decrypts the given data with this private key. The decryption scheme
|
||
* must match the one used to encrypt the data.
|
||
*
|
||
* @param data the byte string to decrypt.
|
||
* @param scheme the decryption scheme to use:
|
||
* 'RSAES-PKCS1-V1_5' (default),
|
||
* 'RSA-OAEP',
|
||
* 'RAW', 'NONE', or null to perform raw RSA decryption.
|
||
* @param schemeOptions any scheme-specific options.
|
||
*
|
||
* @return the decrypted byte string.
|
||
*/
|
||
key.decrypt = function(data, scheme, schemeOptions) {
|
||
if(typeof scheme === 'string') {
|
||
scheme = scheme.toUpperCase();
|
||
} else if(scheme === undefined) {
|
||
scheme = 'RSAES-PKCS1-V1_5';
|
||
}
|
||
|
||
// do rsa decryption w/o any decoding
|
||
var d = pki.rsa.decrypt(data, key, false, false);
|
||
|
||
if(scheme === 'RSAES-PKCS1-V1_5') {
|
||
scheme = { decode: _decodePkcs1_v1_5 };
|
||
} else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') {
|
||
scheme = {
|
||
decode: function(d, key) {
|
||
return forge.pkcs1.decode_rsa_oaep(key, d, schemeOptions);
|
||
}
|
||
};
|
||
} else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) {
|
||
scheme = { decode: function(d) { return d; } };
|
||
} else {
|
||
throw new Error('Unsupported encryption scheme: "' + scheme + '".');
|
||
}
|
||
|
||
// decode according to scheme
|
||
return scheme.decode(d, key, false);
|
||
};
|
||
|
||
/**
|
||
* Signs the given digest, producing a signature.
|
||
*
|
||
* PKCS#1 supports multiple (currently two) signature schemes:
|
||
* RSASSA-PKCS1-V1_5 and RSASSA-PSS.
|
||
*
|
||
* By default this implementation uses the "old scheme", i.e.
|
||
* RSASSA-PKCS1-V1_5. In order to generate a PSS signature, provide
|
||
* an instance of Forge PSS object as the scheme parameter.
|
||
*
|
||
* @param md the message digest object with the hash to sign.
|
||
* @param scheme the signature scheme to use:
|
||
* 'RSASSA-PKCS1-V1_5' or undefined for RSASSA PKCS#1 v1.5,
|
||
* a Forge PSS object for RSASSA-PSS,
|
||
* 'NONE' or null for none, DigestInfo will not be used but
|
||
* PKCS#1 v1.5 padding will still be used.
|
||
*
|
||
* @return the signature as a byte string.
|
||
*/
|
||
key.sign = function(md, scheme) {
|
||
/* Note: The internal implementation of RSA operations is being
|
||
transitioned away from a PKCS#1 v1.5 hard-coded scheme. Some legacy
|
||
code like the use of an encoding block identifier 'bt' will eventually
|
||
be removed. */
|
||
|
||
// private key operation
|
||
var bt = false;
|
||
|
||
if(typeof scheme === 'string') {
|
||
scheme = scheme.toUpperCase();
|
||
}
|
||
|
||
if(scheme === undefined || scheme === 'RSASSA-PKCS1-V1_5') {
|
||
scheme = { encode: emsaPkcs1v15encode };
|
||
bt = 0x01;
|
||
} else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) {
|
||
scheme = { encode: function() { return md; } };
|
||
bt = 0x01;
|
||
}
|
||
|
||
// encode and then encrypt
|
||
var d = scheme.encode(md, key.n.bitLength());
|
||
return pki.rsa.encrypt(d, key, bt);
|
||
};
|
||
|
||
return key;
|
||
};
|
||
|
||
/**
|
||
* Wraps an RSAPrivateKey ASN.1 object in an ASN.1 PrivateKeyInfo object.
|
||
*
|
||
* @param rsaKey the ASN.1 RSAPrivateKey.
|
||
*
|
||
* @return the ASN.1 PrivateKeyInfo.
|
||
*/
|
||
pki.wrapRsaPrivateKey = function(rsaKey) {
|
||
// PrivateKeyInfo
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version (0)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(0).getBytes()),
|
||
// privateKeyAlgorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]),
|
||
// PrivateKey
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
asn1.toDer(rsaKey).getBytes())
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Converts a private key from an ASN.1 object.
|
||
*
|
||
* @param obj the ASN.1 representation of a PrivateKeyInfo containing an
|
||
* RSAPrivateKey or an RSAPrivateKey.
|
||
*
|
||
* @return the private key.
|
||
*/
|
||
pki.privateKeyFromAsn1 = function(obj) {
|
||
// get PrivateKeyInfo
|
||
var capture = {};
|
||
var errors = [];
|
||
if(asn1.validate(obj, privateKeyValidator, capture, errors)) {
|
||
obj = asn1.fromDer(forge.util.createBuffer(capture.privateKey));
|
||
}
|
||
|
||
// get RSAPrivateKey
|
||
capture = {};
|
||
errors = [];
|
||
if(!asn1.validate(obj, rsaPrivateKeyValidator, capture, errors)) {
|
||
var error = new Error('Cannot read private key. ' +
|
||
'ASN.1 object does not contain an RSAPrivateKey.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// Note: Version is currently ignored.
|
||
// capture.privateKeyVersion
|
||
// FIXME: inefficient, get a BigInteger that uses byte strings
|
||
var n, e, d, p, q, dP, dQ, qInv;
|
||
n = forge.util.createBuffer(capture.privateKeyModulus).toHex();
|
||
e = forge.util.createBuffer(capture.privateKeyPublicExponent).toHex();
|
||
d = forge.util.createBuffer(capture.privateKeyPrivateExponent).toHex();
|
||
p = forge.util.createBuffer(capture.privateKeyPrime1).toHex();
|
||
q = forge.util.createBuffer(capture.privateKeyPrime2).toHex();
|
||
dP = forge.util.createBuffer(capture.privateKeyExponent1).toHex();
|
||
dQ = forge.util.createBuffer(capture.privateKeyExponent2).toHex();
|
||
qInv = forge.util.createBuffer(capture.privateKeyCoefficient).toHex();
|
||
|
||
// set private key
|
||
return pki.setRsaPrivateKey(
|
||
new BigInteger(n, 16),
|
||
new BigInteger(e, 16),
|
||
new BigInteger(d, 16),
|
||
new BigInteger(p, 16),
|
||
new BigInteger(q, 16),
|
||
new BigInteger(dP, 16),
|
||
new BigInteger(dQ, 16),
|
||
new BigInteger(qInv, 16));
|
||
};
|
||
|
||
/**
|
||
* Converts a private key to an ASN.1 RSAPrivateKey.
|
||
*
|
||
* @param key the private key.
|
||
*
|
||
* @return the ASN.1 representation of an RSAPrivateKey.
|
||
*/
|
||
pki.privateKeyToAsn1 = pki.privateKeyToRSAPrivateKey = function(key) {
|
||
// RSAPrivateKey
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version (0 = only 2 primes, 1 multiple primes)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(0).getBytes()),
|
||
// modulus (n)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.n)),
|
||
// publicExponent (e)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.e)),
|
||
// privateExponent (d)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.d)),
|
||
// privateKeyPrime1 (p)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.p)),
|
||
// privateKeyPrime2 (q)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.q)),
|
||
// privateKeyExponent1 (dP)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.dP)),
|
||
// privateKeyExponent2 (dQ)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.dQ)),
|
||
// coefficient (qInv)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.qInv))
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Converts a public key from an ASN.1 SubjectPublicKeyInfo or RSAPublicKey.
|
||
*
|
||
* @param obj the asn1 representation of a SubjectPublicKeyInfo or RSAPublicKey.
|
||
*
|
||
* @return the public key.
|
||
*/
|
||
pki.publicKeyFromAsn1 = function(obj) {
|
||
// get SubjectPublicKeyInfo
|
||
var capture = {};
|
||
var errors = [];
|
||
if(asn1.validate(obj, publicKeyValidator, capture, errors)) {
|
||
// get oid
|
||
var oid = asn1.derToOid(capture.publicKeyOid);
|
||
if(oid !== pki.oids.rsaEncryption) {
|
||
var error = new Error('Cannot read public key. Unknown OID.');
|
||
error.oid = oid;
|
||
throw error;
|
||
}
|
||
obj = capture.rsaPublicKey;
|
||
}
|
||
|
||
// get RSA params
|
||
errors = [];
|
||
if(!asn1.validate(obj, rsaPublicKeyValidator, capture, errors)) {
|
||
var error = new Error('Cannot read public key. ' +
|
||
'ASN.1 object does not contain an RSAPublicKey.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// FIXME: inefficient, get a BigInteger that uses byte strings
|
||
var n = forge.util.createBuffer(capture.publicKeyModulus).toHex();
|
||
var e = forge.util.createBuffer(capture.publicKeyExponent).toHex();
|
||
|
||
// set public key
|
||
return pki.setRsaPublicKey(
|
||
new BigInteger(n, 16),
|
||
new BigInteger(e, 16));
|
||
};
|
||
|
||
/**
|
||
* Converts a public key to an ASN.1 SubjectPublicKeyInfo.
|
||
*
|
||
* @param key the public key.
|
||
*
|
||
* @return the asn1 representation of a SubjectPublicKeyInfo.
|
||
*/
|
||
pki.publicKeyToAsn1 = pki.publicKeyToSubjectPublicKeyInfo = function(key) {
|
||
// SubjectPublicKeyInfo
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// AlgorithmIdentifier
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.rsaEncryption).getBytes()),
|
||
// parameters (null)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]),
|
||
// subjectPublicKey
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [
|
||
pki.publicKeyToRSAPublicKey(key)
|
||
])
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Converts a public key to an ASN.1 RSAPublicKey.
|
||
*
|
||
* @param key the public key.
|
||
*
|
||
* @return the asn1 representation of a RSAPublicKey.
|
||
*/
|
||
pki.publicKeyToRSAPublicKey = function(key) {
|
||
// RSAPublicKey
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// modulus (n)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.n)),
|
||
// publicExponent (e)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
_bnToBytes(key.e))
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Encodes a message using PKCS#1 v1.5 padding.
|
||
*
|
||
* @param m the message to encode.
|
||
* @param key the RSA key to use.
|
||
* @param bt the block type to use, i.e. either 0x01 (for signing) or 0x02
|
||
* (for encryption).
|
||
*
|
||
* @return the padded byte buffer.
|
||
*/
|
||
function _encodePkcs1_v1_5(m, key, bt) {
|
||
var eb = forge.util.createBuffer();
|
||
|
||
// get the length of the modulus in bytes
|
||
var k = Math.ceil(key.n.bitLength() / 8);
|
||
|
||
/* use PKCS#1 v1.5 padding */
|
||
if(m.length > (k - 11)) {
|
||
var error = new Error('Message is too long for PKCS#1 v1.5 padding.');
|
||
error.length = m.length;
|
||
error.max = k - 11;
|
||
throw error;
|
||
}
|
||
|
||
/* A block type BT, a padding string PS, and the data D shall be
|
||
formatted into an octet string EB, the encryption block:
|
||
|
||
EB = 00 || BT || PS || 00 || D
|
||
|
||
The block type BT shall be a single octet indicating the structure of
|
||
the encryption block. For this version of the document it shall have
|
||
value 00, 01, or 02. For a private-key operation, the block type
|
||
shall be 00 or 01. For a public-key operation, it shall be 02.
|
||
|
||
The padding string PS shall consist of k-3-||D|| octets. For block
|
||
type 00, the octets shall have value 00; for block type 01, they
|
||
shall have value FF; and for block type 02, they shall be
|
||
pseudorandomly generated and nonzero. This makes the length of the
|
||
encryption block EB equal to k. */
|
||
|
||
// build the encryption block
|
||
eb.putByte(0x00);
|
||
eb.putByte(bt);
|
||
|
||
// create the padding
|
||
var padNum = k - 3 - m.length;
|
||
var padByte;
|
||
// private key op
|
||
if(bt === 0x00 || bt === 0x01) {
|
||
padByte = (bt === 0x00) ? 0x00 : 0xFF;
|
||
for(var i = 0; i < padNum; ++i) {
|
||
eb.putByte(padByte);
|
||
}
|
||
} else {
|
||
// public key op
|
||
// pad with random non-zero values
|
||
while(padNum > 0) {
|
||
var numZeros = 0;
|
||
var padBytes = forge.random.getBytes(padNum);
|
||
for(var i = 0; i < padNum; ++i) {
|
||
padByte = padBytes.charCodeAt(i);
|
||
if(padByte === 0) {
|
||
++numZeros;
|
||
} else {
|
||
eb.putByte(padByte);
|
||
}
|
||
}
|
||
padNum = numZeros;
|
||
}
|
||
}
|
||
|
||
// zero followed by message
|
||
eb.putByte(0x00);
|
||
eb.putBytes(m);
|
||
|
||
return eb;
|
||
}
|
||
|
||
/**
|
||
* Decodes a message using PKCS#1 v1.5 padding.
|
||
*
|
||
* @param em the message to decode.
|
||
* @param key the RSA key to use.
|
||
* @param pub true if the key is a public key, false if it is private.
|
||
* @param ml the message length, if specified.
|
||
*
|
||
* @return the decoded bytes.
|
||
*/
|
||
function _decodePkcs1_v1_5(em, key, pub, ml) {
|
||
// get the length of the modulus in bytes
|
||
var k = Math.ceil(key.n.bitLength() / 8);
|
||
|
||
/* It is an error if any of the following conditions occurs:
|
||
|
||
1. The encryption block EB cannot be parsed unambiguously.
|
||
2. The padding string PS consists of fewer than eight octets
|
||
or is inconsisent with the block type BT.
|
||
3. The decryption process is a public-key operation and the block
|
||
type BT is not 00 or 01, or the decryption process is a
|
||
private-key operation and the block type is not 02.
|
||
*/
|
||
|
||
// parse the encryption block
|
||
var eb = forge.util.createBuffer(em);
|
||
var first = eb.getByte();
|
||
var bt = eb.getByte();
|
||
if(first !== 0x00 ||
|
||
(pub && bt !== 0x00 && bt !== 0x01) ||
|
||
(!pub && bt != 0x02) ||
|
||
(pub && bt === 0x00 && typeof(ml) === 'undefined')) {
|
||
throw new Error('Encryption block is invalid.');
|
||
}
|
||
|
||
var padNum = 0;
|
||
if(bt === 0x00) {
|
||
// check all padding bytes for 0x00
|
||
padNum = k - 3 - ml;
|
||
for(var i = 0; i < padNum; ++i) {
|
||
if(eb.getByte() !== 0x00) {
|
||
throw new Error('Encryption block is invalid.');
|
||
}
|
||
}
|
||
} else if(bt === 0x01) {
|
||
// find the first byte that isn't 0xFF, should be after all padding
|
||
padNum = 0;
|
||
while(eb.length() > 1) {
|
||
if(eb.getByte() !== 0xFF) {
|
||
--eb.read;
|
||
break;
|
||
}
|
||
++padNum;
|
||
}
|
||
} else if(bt === 0x02) {
|
||
// look for 0x00 byte
|
||
padNum = 0;
|
||
while(eb.length() > 1) {
|
||
if(eb.getByte() === 0x00) {
|
||
--eb.read;
|
||
break;
|
||
}
|
||
++padNum;
|
||
}
|
||
}
|
||
|
||
// zero must be 0x00 and padNum must be (k - 3 - message length)
|
||
var zero = eb.getByte();
|
||
if(zero !== 0x00 || padNum !== (k - 3 - eb.length())) {
|
||
throw new Error('Encryption block is invalid.');
|
||
}
|
||
|
||
return eb.getBytes();
|
||
}
|
||
|
||
/**
|
||
* Runs the key-generation algorithm asynchronously, either in the background
|
||
* via Web Workers, or using the main thread and setImmediate.
|
||
*
|
||
* @param state the key-pair generation state.
|
||
* @param [options] options for key-pair generation:
|
||
* workerScript the worker script URL.
|
||
* workers the number of web workers (if supported) to use,
|
||
* (default: 2, -1 to use estimated cores minus one).
|
||
* workLoad the size of the work load, ie: number of possible prime
|
||
* numbers for each web worker to check per work assignment,
|
||
* (default: 100).
|
||
* @param callback(err, keypair) called once the operation completes.
|
||
*/
|
||
function _generateKeyPair(state, options, callback) {
|
||
if(typeof options === 'function') {
|
||
callback = options;
|
||
options = {};
|
||
}
|
||
options = options || {};
|
||
|
||
var opts = {
|
||
algorithm: {
|
||
name: options.algorithm || 'PRIMEINC',
|
||
options: {
|
||
workers: options.workers || 2,
|
||
workLoad: options.workLoad || 100,
|
||
workerScript: options.workerScript
|
||
}
|
||
}
|
||
};
|
||
if('prng' in options) {
|
||
opts.prng = options.prng;
|
||
}
|
||
|
||
generate();
|
||
|
||
function generate() {
|
||
// find p and then q (done in series to simplify)
|
||
getPrime(state.pBits, function(err, num) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
state.p = num;
|
||
if(state.q !== null) {
|
||
return finish(err, state.q);
|
||
}
|
||
getPrime(state.qBits, finish);
|
||
});
|
||
}
|
||
|
||
function getPrime(bits, callback) {
|
||
forge.prime.generateProbablePrime(bits, opts, callback);
|
||
}
|
||
|
||
function finish(err, num) {
|
||
if(err) {
|
||
return callback(err);
|
||
}
|
||
|
||
// set q
|
||
state.q = num;
|
||
|
||
// ensure p is larger than q (swap them if not)
|
||
if(state.p.compareTo(state.q) < 0) {
|
||
var tmp = state.p;
|
||
state.p = state.q;
|
||
state.q = tmp;
|
||
}
|
||
|
||
// ensure p is coprime with e
|
||
if(state.p.subtract(BigInteger.ONE).gcd(state.e)
|
||
.compareTo(BigInteger.ONE) !== 0) {
|
||
state.p = null;
|
||
generate();
|
||
return;
|
||
}
|
||
|
||
// ensure q is coprime with e
|
||
if(state.q.subtract(BigInteger.ONE).gcd(state.e)
|
||
.compareTo(BigInteger.ONE) !== 0) {
|
||
state.q = null;
|
||
getPrime(state.qBits, finish);
|
||
return;
|
||
}
|
||
|
||
// compute phi: (p - 1)(q - 1) (Euler's totient function)
|
||
state.p1 = state.p.subtract(BigInteger.ONE);
|
||
state.q1 = state.q.subtract(BigInteger.ONE);
|
||
state.phi = state.p1.multiply(state.q1);
|
||
|
||
// ensure e and phi are coprime
|
||
if(state.phi.gcd(state.e).compareTo(BigInteger.ONE) !== 0) {
|
||
// phi and e aren't coprime, so generate a new p and q
|
||
state.p = state.q = null;
|
||
generate();
|
||
return;
|
||
}
|
||
|
||
// create n, ensure n is has the right number of bits
|
||
state.n = state.p.multiply(state.q);
|
||
if(state.n.bitLength() !== state.bits) {
|
||
// failed, get new q
|
||
state.q = null;
|
||
getPrime(state.qBits, finish);
|
||
return;
|
||
}
|
||
|
||
// set keys
|
||
var d = state.e.modInverse(state.phi);
|
||
state.keys = {
|
||
privateKey: pki.rsa.setPrivateKey(
|
||
state.n, state.e, d, state.p, state.q,
|
||
d.mod(state.p1), d.mod(state.q1),
|
||
state.q.modInverse(state.p)),
|
||
publicKey: pki.rsa.setPublicKey(state.n, state.e)
|
||
};
|
||
|
||
callback(null, state.keys);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Converts a positive BigInteger into 2's-complement big-endian bytes.
|
||
*
|
||
* @param b the big integer to convert.
|
||
*
|
||
* @return the bytes.
|
||
*/
|
||
function _bnToBytes(b) {
|
||
// prepend 0x00 if first byte >= 0x80
|
||
var hex = b.toString(16);
|
||
if(hex[0] >= '8') {
|
||
hex = '00' + hex;
|
||
}
|
||
return forge.util.hexToBytes(hex);
|
||
}
|
||
|
||
/**
|
||
* Returns the required number of Miller-Rabin tests to generate a
|
||
* prime with an error probability of (1/2)^80.
|
||
*
|
||
* See Handbook of Applied Cryptography Chapter 4, Table 4.4.
|
||
*
|
||
* @param bits the bit size.
|
||
*
|
||
* @return the required number of iterations.
|
||
*/
|
||
function _getMillerRabinTests(bits) {
|
||
if(bits <= 100) return 27;
|
||
if(bits <= 150) return 18;
|
||
if(bits <= 200) return 15;
|
||
if(bits <= 250) return 12;
|
||
if(bits <= 300) return 9;
|
||
if(bits <= 350) return 8;
|
||
if(bits <= 400) return 7;
|
||
if(bits <= 500) return 6;
|
||
if(bits <= 600) return 5;
|
||
if(bits <= 800) return 4;
|
||
if(bits <= 1250) return 3;
|
||
return 2;
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'rsa';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/rsa',[
|
||
'require',
|
||
'module',
|
||
'./asn1',
|
||
'./jsbn',
|
||
'./oids',
|
||
'./pkcs1',
|
||
'./prime',
|
||
'./random',
|
||
'./util'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Password-based encryption functions.
|
||
*
|
||
* @author Dave Longley
|
||
* @author Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* An EncryptedPrivateKeyInfo:
|
||
*
|
||
* EncryptedPrivateKeyInfo ::= SEQUENCE {
|
||
* encryptionAlgorithm EncryptionAlgorithmIdentifier,
|
||
* encryptedData EncryptedData }
|
||
*
|
||
* EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
*
|
||
* EncryptedData ::= OCTET STRING
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
if(typeof BigInteger === 'undefined') {
|
||
var BigInteger = forge.jsbn.BigInteger;
|
||
}
|
||
|
||
// shortcut for asn.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
/* Password-based encryption implementation. */
|
||
var pki = forge.pki = forge.pki || {};
|
||
pki.pbe = forge.pbe = forge.pbe || {};
|
||
var oids = pki.oids;
|
||
|
||
// validator for an EncryptedPrivateKeyInfo structure
|
||
// Note: Currently only works w/algorithm params
|
||
var encryptedPrivateKeyValidator = {
|
||
name: 'EncryptedPrivateKeyInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'EncryptedPrivateKeyInfo.encryptionAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'AlgorithmIdentifier.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'encryptionOid'
|
||
}, {
|
||
name: 'AlgorithmIdentifier.parameters',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'encryptionParams'
|
||
}]
|
||
}, {
|
||
// encryptedData
|
||
name: 'EncryptedPrivateKeyInfo.encryptedData',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'encryptedData'
|
||
}]
|
||
};
|
||
|
||
// validator for a PBES2Algorithms structure
|
||
// Note: Currently only works w/PBKDF2 + AES encryption schemes
|
||
var PBES2AlgorithmsValidator = {
|
||
name: 'PBES2Algorithms',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PBES2Algorithms.keyDerivationFunc',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PBES2Algorithms.keyDerivationFunc.oid',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'kdfOid'
|
||
}, {
|
||
name: 'PBES2Algorithms.params',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PBES2Algorithms.params.salt',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'kdfSalt'
|
||
}, {
|
||
name: 'PBES2Algorithms.params.iterationCount',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
onstructed: true,
|
||
capture: 'kdfIterationCount'
|
||
}]
|
||
}]
|
||
}, {
|
||
name: 'PBES2Algorithms.encryptionScheme',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PBES2Algorithms.encryptionScheme.oid',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'encOid'
|
||
}, {
|
||
name: 'PBES2Algorithms.encryptionScheme.iv',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'encIv'
|
||
}]
|
||
}]
|
||
};
|
||
|
||
var pkcs12PbeParamsValidator = {
|
||
name: 'pkcs-12PbeParams',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'pkcs-12PbeParams.salt',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'salt'
|
||
}, {
|
||
name: 'pkcs-12PbeParams.iterations',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'iterations'
|
||
}]
|
||
};
|
||
|
||
/**
|
||
* Encrypts a ASN.1 PrivateKeyInfo object, producing an EncryptedPrivateKeyInfo.
|
||
*
|
||
* PBES2Algorithms ALGORITHM-IDENTIFIER ::=
|
||
* { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
|
||
*
|
||
* id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
|
||
*
|
||
* PBES2-params ::= SEQUENCE {
|
||
* keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
|
||
* encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
|
||
* }
|
||
*
|
||
* PBES2-KDFs ALGORITHM-IDENTIFIER ::=
|
||
* { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
|
||
*
|
||
* PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
|
||
*
|
||
* PBKDF2-params ::= SEQUENCE {
|
||
* salt CHOICE {
|
||
* specified OCTET STRING,
|
||
* otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
|
||
* },
|
||
* iterationCount INTEGER (1..MAX),
|
||
* keyLength INTEGER (1..MAX) OPTIONAL,
|
||
* prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
|
||
* }
|
||
*
|
||
* @param obj the ASN.1 PrivateKeyInfo object.
|
||
* @param password the password to encrypt with.
|
||
* @param options:
|
||
* algorithm the encryption algorithm to use
|
||
* ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
|
||
* count the iteration count to use.
|
||
* saltSize the salt size to use.
|
||
*
|
||
* @return the ASN.1 EncryptedPrivateKeyInfo.
|
||
*/
|
||
pki.encryptPrivateKeyInfo = function(obj, password, options) {
|
||
// set default options
|
||
options = options || {};
|
||
options.saltSize = options.saltSize || 8;
|
||
options.count = options.count || 2048;
|
||
options.algorithm = options.algorithm || 'aes128';
|
||
|
||
// generate PBE params
|
||
var salt = forge.random.getBytesSync(options.saltSize);
|
||
var count = options.count;
|
||
var countBytes = asn1.integerToDer(count);
|
||
var dkLen;
|
||
var encryptionAlgorithm;
|
||
var encryptedData;
|
||
if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
|
||
// Do PBES2
|
||
var ivLen, encOid, cipherFn;
|
||
switch(options.algorithm) {
|
||
case 'aes128':
|
||
dkLen = 16;
|
||
ivLen = 16;
|
||
encOid = oids['aes128-CBC'];
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case 'aes192':
|
||
dkLen = 24;
|
||
ivLen = 16;
|
||
encOid = oids['aes192-CBC'];
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case 'aes256':
|
||
dkLen = 32;
|
||
ivLen = 16;
|
||
encOid = oids['aes256-CBC'];
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case 'des':
|
||
dkLen = 8;
|
||
ivLen = 8;
|
||
encOid = oids['desCBC'];
|
||
cipherFn = forge.des.createEncryptionCipher;
|
||
break;
|
||
default:
|
||
var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
|
||
error.algorithm = options.algorithm;
|
||
throw error;
|
||
}
|
||
|
||
// encrypt private key using pbe SHA-1 and AES/DES
|
||
var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
|
||
var iv = forge.random.getBytesSync(ivLen);
|
||
var cipher = cipherFn(dk);
|
||
cipher.start(iv);
|
||
cipher.update(asn1.toDer(obj));
|
||
cipher.finish();
|
||
encryptedData = cipher.output.getBytes();
|
||
|
||
encryptionAlgorithm = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// keyDerivationFunc
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
|
||
// PBKDF2-params
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// salt
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
|
||
// iteration count
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
countBytes.getBytes())
|
||
])
|
||
]),
|
||
// encryptionScheme
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(encOid).getBytes()),
|
||
// iv
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
|
||
])
|
||
])
|
||
]);
|
||
} else if(options.algorithm === '3des') {
|
||
// Do PKCS12 PBE
|
||
dkLen = 24;
|
||
|
||
var saltBytes = new forge.util.ByteBuffer(salt);
|
||
var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
|
||
var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
|
||
var cipher = forge.des.createEncryptionCipher(dk);
|
||
cipher.start(iv);
|
||
cipher.update(asn1.toDer(obj));
|
||
cipher.finish();
|
||
encryptedData = cipher.output.getBytes();
|
||
|
||
encryptionAlgorithm = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
|
||
// pkcs-12PbeParams
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// salt
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
|
||
// iteration count
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
countBytes.getBytes())
|
||
])
|
||
]);
|
||
} else {
|
||
var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
|
||
error.algorithm = options.algorithm;
|
||
throw error;
|
||
}
|
||
|
||
// EncryptedPrivateKeyInfo
|
||
var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// encryptionAlgorithm
|
||
encryptionAlgorithm,
|
||
// encryptedData
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData)
|
||
]);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Decrypts a ASN.1 PrivateKeyInfo object.
|
||
*
|
||
* @param obj the ASN.1 EncryptedPrivateKeyInfo object.
|
||
* @param password the password to decrypt with.
|
||
*
|
||
* @return the ASN.1 PrivateKeyInfo on success, null on failure.
|
||
*/
|
||
pki.decryptPrivateKeyInfo = function(obj, password) {
|
||
var rval = null;
|
||
|
||
// get PBE params
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, encryptedPrivateKeyValidator, capture, errors)) {
|
||
var error = new Error('Cannot read encrypted private key. ' +
|
||
'ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// get cipher
|
||
var oid = asn1.derToOid(capture.encryptionOid);
|
||
var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
|
||
|
||
// get encrypted data
|
||
var encrypted = forge.util.createBuffer(capture.encryptedData);
|
||
|
||
cipher.update(encrypted);
|
||
if(cipher.finish()) {
|
||
rval = asn1.fromDer(cipher.output);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts a EncryptedPrivateKeyInfo to PEM format.
|
||
*
|
||
* @param epki the EncryptedPrivateKeyInfo.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted encrypted private key.
|
||
*/
|
||
pki.encryptedPrivateKeyToPem = function(epki, maxline) {
|
||
// convert to DER, then PEM-encode
|
||
var msg = {
|
||
type: 'ENCRYPTED PRIVATE KEY',
|
||
body: asn1.toDer(epki).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format. Decryption
|
||
* is not performed.
|
||
*
|
||
* @param pem the EncryptedPrivateKeyInfo in PEM-format.
|
||
*
|
||
* @return the ASN.1 EncryptedPrivateKeyInfo.
|
||
*/
|
||
pki.encryptedPrivateKeyFromPem = function(pem) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'ENCRYPTED PRIVATE KEY') {
|
||
var error = new Error('Could not convert encrypted private key from PEM; ' +
|
||
'PEM header type is "ENCRYPTED PRIVATE KEY".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert encrypted private key from PEM; ' +
|
||
'PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
return asn1.fromDer(msg.body);
|
||
};
|
||
|
||
/**
|
||
* Encrypts an RSA private key. By default, the key will be wrapped in
|
||
* a PrivateKeyInfo and encrypted to produce a PKCS#8 EncryptedPrivateKeyInfo.
|
||
* This is the standard, preferred way to encrypt a private key.
|
||
*
|
||
* To produce a non-standard PEM-encrypted private key that uses encapsulated
|
||
* headers to indicate the encryption algorithm (old-style non-PKCS#8 OpenSSL
|
||
* private key encryption), set the 'legacy' option to true. Note: Using this
|
||
* option will cause the iteration count to be forced to 1.
|
||
*
|
||
* Note: The 'des' algorithm is supported, but it is not considered to be
|
||
* secure because it only uses a single 56-bit key. If possible, it is highly
|
||
* recommended that a different algorithm be used.
|
||
*
|
||
* @param rsaKey the RSA key to encrypt.
|
||
* @param password the password to use.
|
||
* @param options:
|
||
* algorithm: the encryption algorithm to use
|
||
* ('aes128', 'aes192', 'aes256', '3des', 'des').
|
||
* count: the iteration count to use.
|
||
* saltSize: the salt size to use.
|
||
* legacy: output an old non-PKCS#8 PEM-encrypted+encapsulated
|
||
* headers (DEK-Info) private key.
|
||
*
|
||
* @return the PEM-encoded ASN.1 EncryptedPrivateKeyInfo.
|
||
*/
|
||
pki.encryptRsaPrivateKey = function(rsaKey, password, options) {
|
||
// standard PKCS#8
|
||
options = options || {};
|
||
if(!options.legacy) {
|
||
// encrypt PrivateKeyInfo
|
||
var rval = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(rsaKey));
|
||
rval = pki.encryptPrivateKeyInfo(rval, password, options);
|
||
return pki.encryptedPrivateKeyToPem(rval);
|
||
}
|
||
|
||
// legacy non-PKCS#8
|
||
var algorithm;
|
||
var iv;
|
||
var dkLen;
|
||
var cipherFn;
|
||
switch(options.algorithm) {
|
||
case 'aes128':
|
||
algorithm = 'AES-128-CBC';
|
||
dkLen = 16;
|
||
iv = forge.random.getBytesSync(16);
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case 'aes192':
|
||
algorithm = 'AES-192-CBC';
|
||
dkLen = 24;
|
||
iv = forge.random.getBytesSync(16);
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case 'aes256':
|
||
algorithm = 'AES-256-CBC';
|
||
dkLen = 32;
|
||
iv = forge.random.getBytesSync(16);
|
||
cipherFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
case '3des':
|
||
algorithm = 'DES-EDE3-CBC';
|
||
dkLen = 24;
|
||
iv = forge.random.getBytesSync(8);
|
||
cipherFn = forge.des.createEncryptionCipher;
|
||
break;
|
||
case 'des':
|
||
algorithm = 'DES-CBC';
|
||
dkLen = 8;
|
||
iv = forge.random.getBytesSync(8);
|
||
cipherFn = forge.des.createEncryptionCipher;
|
||
break;
|
||
default:
|
||
var error = new Error('Could not encrypt RSA private key; unsupported ' +
|
||
'encryption algorithm "' + options.algorithm + '".');
|
||
error.algorithm = options.algorithm;
|
||
throw error;
|
||
}
|
||
|
||
// encrypt private key using OpenSSL legacy key derivation
|
||
var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
|
||
var cipher = cipherFn(dk);
|
||
cipher.start(iv);
|
||
cipher.update(asn1.toDer(pki.privateKeyToAsn1(rsaKey)));
|
||
cipher.finish();
|
||
|
||
var msg = {
|
||
type: 'RSA PRIVATE KEY',
|
||
procType: {
|
||
version: '4',
|
||
type: 'ENCRYPTED'
|
||
},
|
||
dekInfo: {
|
||
algorithm: algorithm,
|
||
parameters: forge.util.bytesToHex(iv).toUpperCase()
|
||
},
|
||
body: cipher.output.getBytes()
|
||
};
|
||
return forge.pem.encode(msg);
|
||
};
|
||
|
||
/**
|
||
* Decrypts an RSA private key.
|
||
*
|
||
* @param pem the PEM-formatted EncryptedPrivateKeyInfo to decrypt.
|
||
* @param password the password to use.
|
||
*
|
||
* @return the RSA key on success, null on failure.
|
||
*/
|
||
pki.decryptRsaPrivateKey = function(pem, password) {
|
||
var rval = null;
|
||
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'ENCRYPTED PRIVATE KEY' &&
|
||
msg.type !== 'PRIVATE KEY' &&
|
||
msg.type !== 'RSA PRIVATE KEY') {
|
||
var error = new Error('Could not convert private key from PEM; PEM header type ' +
|
||
'is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".');
|
||
error.headerType = error;
|
||
throw error;
|
||
}
|
||
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
var dkLen;
|
||
var cipherFn;
|
||
switch(msg.dekInfo.algorithm) {
|
||
case 'DES-CBC':
|
||
dkLen = 8;
|
||
cipherFn = forge.des.createDecryptionCipher;
|
||
break;
|
||
case 'DES-EDE3-CBC':
|
||
dkLen = 24;
|
||
cipherFn = forge.des.createDecryptionCipher;
|
||
break;
|
||
case 'AES-128-CBC':
|
||
dkLen = 16;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'AES-192-CBC':
|
||
dkLen = 24;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'AES-256-CBC':
|
||
dkLen = 32;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'RC2-40-CBC':
|
||
dkLen = 5;
|
||
cipherFn = function(key) {
|
||
return forge.rc2.createDecryptionCipher(key, 40);
|
||
};
|
||
break;
|
||
case 'RC2-64-CBC':
|
||
dkLen = 8;
|
||
cipherFn = function(key) {
|
||
return forge.rc2.createDecryptionCipher(key, 64);
|
||
};
|
||
break;
|
||
case 'RC2-128-CBC':
|
||
dkLen = 16;
|
||
cipherFn = function(key) {
|
||
return forge.rc2.createDecryptionCipher(key, 128);
|
||
};
|
||
break;
|
||
default:
|
||
var error = new Error('Could not decrypt private key; unsupported ' +
|
||
'encryption algorithm "' + msg.dekInfo.algorithm + '".');
|
||
error.algorithm = msg.dekInfo.algorithm;
|
||
throw error;
|
||
}
|
||
|
||
// use OpenSSL legacy key derivation
|
||
var iv = forge.util.hexToBytes(msg.dekInfo.parameters);
|
||
var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
|
||
var cipher = cipherFn(dk);
|
||
cipher.start(iv);
|
||
cipher.update(forge.util.createBuffer(msg.body));
|
||
if(cipher.finish()) {
|
||
rval = cipher.output.getBytes();
|
||
} else {
|
||
return rval;
|
||
}
|
||
} else {
|
||
rval = msg.body;
|
||
}
|
||
|
||
if(msg.type === 'ENCRYPTED PRIVATE KEY') {
|
||
rval = pki.decryptPrivateKeyInfo(asn1.fromDer(rval), password);
|
||
} else {
|
||
// decryption already performed above
|
||
rval = asn1.fromDer(rval);
|
||
}
|
||
|
||
if(rval !== null) {
|
||
rval = pki.privateKeyFromAsn1(rval);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Derives a PKCS#12 key.
|
||
*
|
||
* @param password the password to derive the key material from, null or
|
||
* undefined for none.
|
||
* @param salt the salt, as a ByteBuffer, to use.
|
||
* @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
|
||
* @param iter the iteration count.
|
||
* @param n the number of bytes to derive from the password.
|
||
* @param md the message digest to use, defaults to SHA-1.
|
||
*
|
||
* @return a ByteBuffer with the bytes derived from the password.
|
||
*/
|
||
pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) {
|
||
var j, l;
|
||
|
||
if(typeof md === 'undefined' || md === null) {
|
||
md = forge.md.sha1.create();
|
||
}
|
||
|
||
var u = md.digestLength;
|
||
var v = md.blockLength;
|
||
var result = new forge.util.ByteBuffer();
|
||
|
||
/* Convert password to Unicode byte buffer + trailing 0-byte. */
|
||
var passBuf = new forge.util.ByteBuffer();
|
||
if(password !== null && password !== undefined) {
|
||
for(l = 0; l < password.length; l++) {
|
||
passBuf.putInt16(password.charCodeAt(l));
|
||
}
|
||
passBuf.putInt16(0);
|
||
}
|
||
|
||
/* Length of salt and password in BYTES. */
|
||
var p = passBuf.length();
|
||
var s = salt.length();
|
||
|
||
/* 1. Construct a string, D (the "diversifier"), by concatenating
|
||
v copies of ID. */
|
||
var D = new forge.util.ByteBuffer();
|
||
D.fillWithByte(id, v);
|
||
|
||
/* 2. Concatenate copies of the salt together to create a string S of length
|
||
v * ceil(s / v) bytes (the final copy of the salt may be trunacted
|
||
to create S).
|
||
Note that if the salt is the empty string, then so is S. */
|
||
var Slen = v * Math.ceil(s / v);
|
||
var S = new forge.util.ByteBuffer();
|
||
for(l = 0; l < Slen; l ++) {
|
||
S.putByte(salt.at(l % s));
|
||
}
|
||
|
||
/* 3. Concatenate copies of the password together to create a string P of
|
||
length v * ceil(p / v) bytes (the final copy of the password may be
|
||
truncated to create P).
|
||
Note that if the password is the empty string, then so is P. */
|
||
var Plen = v * Math.ceil(p / v);
|
||
var P = new forge.util.ByteBuffer();
|
||
for(l = 0; l < Plen; l ++) {
|
||
P.putByte(passBuf.at(l % p));
|
||
}
|
||
|
||
/* 4. Set I=S||P to be the concatenation of S and P. */
|
||
var I = S;
|
||
I.putBuffer(P);
|
||
|
||
/* 5. Set c=ceil(n / u). */
|
||
var c = Math.ceil(n / u);
|
||
|
||
/* 6. For i=1, 2, ..., c, do the following: */
|
||
for(var i = 1; i <= c; i ++) {
|
||
/* a) Set Ai=H^r(D||I). (l.e. the rth hash of D||I, H(H(H(...H(D||I)))) */
|
||
var buf = new forge.util.ByteBuffer();
|
||
buf.putBytes(D.bytes());
|
||
buf.putBytes(I.bytes());
|
||
for(var round = 0; round < iter; round ++) {
|
||
md.start();
|
||
md.update(buf.getBytes());
|
||
buf = md.digest();
|
||
}
|
||
|
||
/* b) Concatenate copies of Ai to create a string B of length v bytes (the
|
||
final copy of Ai may be truncated to create B). */
|
||
var B = new forge.util.ByteBuffer();
|
||
for(l = 0; l < v; l ++) {
|
||
B.putByte(buf.at(l % u));
|
||
}
|
||
|
||
/* c) Treating I as a concatenation I0, I1, ..., Ik-1 of v-byte blocks,
|
||
where k=ceil(s / v) + ceil(p / v), modify I by setting
|
||
Ij=(Ij+B+1) mod 2v for each j. */
|
||
var k = Math.ceil(s / v) + Math.ceil(p / v);
|
||
var Inew = new forge.util.ByteBuffer();
|
||
for(j = 0; j < k; j ++) {
|
||
var chunk = new forge.util.ByteBuffer(I.getBytes(v));
|
||
var x = 0x1ff;
|
||
for(l = B.length() - 1; l >= 0; l --) {
|
||
x = x >> 8;
|
||
x += B.at(l) + chunk.at(l);
|
||
chunk.setAt(l, x & 0xff);
|
||
}
|
||
Inew.putBuffer(chunk);
|
||
}
|
||
I = Inew;
|
||
|
||
/* Add Ai to A. */
|
||
result.putBuffer(buf);
|
||
}
|
||
|
||
result.truncate(result.length() - n);
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* Get new Forge cipher object instance.
|
||
*
|
||
* @param oid the OID (in string notation).
|
||
* @param params the ASN.1 params object.
|
||
* @param password the password to decrypt with.
|
||
*
|
||
* @return new cipher object instance.
|
||
*/
|
||
pki.pbe.getCipher = function(oid, params, password) {
|
||
switch(oid) {
|
||
case pki.oids['pkcs5PBES2']:
|
||
return pki.pbe.getCipherForPBES2(oid, params, password);
|
||
|
||
case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
|
||
case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
|
||
return pki.pbe.getCipherForPKCS12PBE(oid, params, password);
|
||
|
||
default:
|
||
var error = new Error('Cannot read encrypted PBE data block. Unsupported OID.');
|
||
error.oid = oid;
|
||
error.supportedOids = [
|
||
'pkcs5PBES2',
|
||
'pbeWithSHAAnd3-KeyTripleDES-CBC',
|
||
'pbewithSHAAnd40BitRC2-CBC'
|
||
];
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Get new Forge cipher object instance according to PBES2 params block.
|
||
*
|
||
* The returned cipher instance is already started using the IV
|
||
* from PBES2 parameter block.
|
||
*
|
||
* @param oid the PKCS#5 PBKDF2 OID (in string notation).
|
||
* @param params the ASN.1 PBES2-params object.
|
||
* @param password the password to decrypt with.
|
||
*
|
||
* @return new cipher object instance.
|
||
*/
|
||
pki.pbe.getCipherForPBES2 = function(oid, params, password) {
|
||
// get PBE params
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(params, PBES2AlgorithmsValidator, capture, errors)) {
|
||
var error = new Error('Cannot read password-based-encryption algorithm ' +
|
||
'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// check oids
|
||
oid = asn1.derToOid(capture.kdfOid);
|
||
if(oid !== pki.oids['pkcs5PBKDF2']) {
|
||
var error = new Error('Cannot read encrypted private key. ' +
|
||
'Unsupported key derivation function OID.');
|
||
error.oid = oid;
|
||
error.supportedOids = ['pkcs5PBKDF2'];
|
||
throw error;
|
||
}
|
||
oid = asn1.derToOid(capture.encOid);
|
||
if(oid !== pki.oids['aes128-CBC'] &&
|
||
oid !== pki.oids['aes192-CBC'] &&
|
||
oid !== pki.oids['aes256-CBC'] &&
|
||
oid !== pki.oids['des-EDE3-CBC'] &&
|
||
oid !== pki.oids['desCBC']) {
|
||
var error = new Error('Cannot read encrypted private key. ' +
|
||
'Unsupported encryption scheme OID.');
|
||
error.oid = oid;
|
||
error.supportedOids = [
|
||
'aes128-CBC', 'aes192-CBC', 'aes256-CBC', 'des-EDE3-CBC', 'desCBC'];
|
||
throw error;
|
||
}
|
||
|
||
// set PBE params
|
||
var salt = capture.kdfSalt;
|
||
var count = forge.util.createBuffer(capture.kdfIterationCount);
|
||
count = count.getInt(count.length() << 3);
|
||
var dkLen;
|
||
var cipherFn;
|
||
switch(pki.oids[oid]) {
|
||
case 'aes128-CBC':
|
||
dkLen = 16;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'aes192-CBC':
|
||
dkLen = 24;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'aes256-CBC':
|
||
dkLen = 32;
|
||
cipherFn = forge.aes.createDecryptionCipher;
|
||
break;
|
||
case 'des-EDE3-CBC':
|
||
dkLen = 24;
|
||
cipherFn = forge.des.createDecryptionCipher;
|
||
break;
|
||
case 'desCBC':
|
||
dkLen = 8;
|
||
cipherFn = forge.des.createDecryptionCipher;
|
||
break;
|
||
}
|
||
|
||
// decrypt private key using pbe SHA-1 and AES/DES
|
||
var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen);
|
||
var iv = capture.encIv;
|
||
var cipher = cipherFn(dk);
|
||
cipher.start(iv);
|
||
|
||
return cipher;
|
||
};
|
||
|
||
/**
|
||
* Get new Forge cipher object instance for PKCS#12 PBE.
|
||
*
|
||
* The returned cipher instance is already started using the key & IV
|
||
* derived from the provided password and PKCS#12 PBE salt.
|
||
*
|
||
* @param oid The PKCS#12 PBE OID (in string notation).
|
||
* @param params The ASN.1 PKCS#12 PBE-params object.
|
||
* @param password The password to decrypt with.
|
||
*
|
||
* @return the new cipher object instance.
|
||
*/
|
||
pki.pbe.getCipherForPKCS12PBE = function(oid, params, password) {
|
||
// get PBE params
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(params, pkcs12PbeParamsValidator, capture, errors)) {
|
||
var error = new Error('Cannot read password-based-encryption algorithm ' +
|
||
'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var salt = forge.util.createBuffer(capture.salt);
|
||
var count = forge.util.createBuffer(capture.iterations);
|
||
count = count.getInt(count.length() << 3);
|
||
|
||
var dkLen, dIvLen, cipherFn;
|
||
switch(oid) {
|
||
case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
|
||
dkLen = 24;
|
||
dIvLen = 8;
|
||
cipherFn = forge.des.startDecrypting;
|
||
break;
|
||
|
||
case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
|
||
dkLen = 5;
|
||
dIvLen = 8;
|
||
cipherFn = function(key, iv) {
|
||
var cipher = forge.rc2.createDecryptionCipher(key, 40);
|
||
cipher.start(iv, null);
|
||
return cipher;
|
||
};
|
||
break;
|
||
|
||
default:
|
||
var error = new Error('Cannot read PKCS #12 PBE data block. Unsupported OID.');
|
||
error.oid = oid;
|
||
throw error;
|
||
}
|
||
|
||
var key = pki.pbe.generatePkcs12Key(password, salt, 1, count, dkLen);
|
||
var iv = pki.pbe.generatePkcs12Key(password, salt, 2, count, dIvLen);
|
||
|
||
return cipherFn(key, iv);
|
||
};
|
||
|
||
/**
|
||
* OpenSSL's legacy key derivation function.
|
||
*
|
||
* See: http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
|
||
*
|
||
* @param password the password to derive the key from.
|
||
* @param salt the salt to use, null for none.
|
||
* @param dkLen the number of bytes needed for the derived key.
|
||
* @param [options] the options to use:
|
||
* [md] an optional message digest object to use.
|
||
*/
|
||
pki.pbe.opensslDeriveBytes = function(password, salt, dkLen, md) {
|
||
if(typeof md === 'undefined' || md === null) {
|
||
md = forge.md.md5.create();
|
||
}
|
||
if(salt === null) {
|
||
salt = '';
|
||
}
|
||
var digests = [hash(md, password + salt)];
|
||
for(var length = 16, i = 1; length < dkLen; ++i, length += 16) {
|
||
digests.push(hash(md, digests[i - 1] + password + salt));
|
||
}
|
||
return digests.join('').substr(0, dkLen);
|
||
};
|
||
|
||
function hash(md, bytes) {
|
||
return md.start().update(bytes).digest().getBytes();
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pbe';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pbe',[
|
||
'require',
|
||
'module',
|
||
'./aes',
|
||
'./asn1',
|
||
'./des',
|
||
'./md',
|
||
'./oids',
|
||
'./pem',
|
||
'./pbkdf2',
|
||
'./random',
|
||
'./rc2',
|
||
'./rsa',
|
||
'./util'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of ASN.1 validators for PKCS#7 v1.5.
|
||
*
|
||
* @author Dave Longley
|
||
* @author Stefan Siegl
|
||
*
|
||
* Copyright (c) 2012-2015 Digital Bazaar, Inc.
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* The ASN.1 representation of PKCS#7 is as follows
|
||
* (see RFC #2315 for details, http://www.ietf.org/rfc/rfc2315.txt):
|
||
*
|
||
* A PKCS#7 message consists of a ContentInfo on root level, which may
|
||
* contain any number of further ContentInfo nested into it.
|
||
*
|
||
* ContentInfo ::= SEQUENCE {
|
||
* contentType ContentType,
|
||
* content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
|
||
* }
|
||
*
|
||
* ContentType ::= OBJECT IDENTIFIER
|
||
*
|
||
* EnvelopedData ::= SEQUENCE {
|
||
* version Version,
|
||
* recipientInfos RecipientInfos,
|
||
* encryptedContentInfo EncryptedContentInfo
|
||
* }
|
||
*
|
||
* EncryptedData ::= SEQUENCE {
|
||
* version Version,
|
||
* encryptedContentInfo EncryptedContentInfo
|
||
* }
|
||
*
|
||
* id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
||
* us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
|
||
*
|
||
* SignedData ::= SEQUENCE {
|
||
* version INTEGER,
|
||
* digestAlgorithms DigestAlgorithmIdentifiers,
|
||
* contentInfo ContentInfo,
|
||
* certificates [0] IMPLICIT Certificates OPTIONAL,
|
||
* crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
|
||
* signerInfos SignerInfos
|
||
* }
|
||
*
|
||
* SignerInfos ::= SET OF SignerInfo
|
||
*
|
||
* SignerInfo ::= SEQUENCE {
|
||
* version Version,
|
||
* issuerAndSerialNumber IssuerAndSerialNumber,
|
||
* digestAlgorithm DigestAlgorithmIdentifier,
|
||
* authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
|
||
* digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
|
||
* encryptedDigest EncryptedDigest,
|
||
* unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
|
||
* }
|
||
*
|
||
* EncryptedDigest ::= OCTET STRING
|
||
*
|
||
* Attributes ::= SET OF Attribute
|
||
*
|
||
* Attribute ::= SEQUENCE {
|
||
* attrType OBJECT IDENTIFIER,
|
||
* attrValues SET OF AttributeValue
|
||
* }
|
||
*
|
||
* AttributeValue ::= ANY
|
||
*
|
||
* Version ::= INTEGER
|
||
*
|
||
* RecipientInfos ::= SET OF RecipientInfo
|
||
*
|
||
* EncryptedContentInfo ::= SEQUENCE {
|
||
* contentType ContentType,
|
||
* contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
|
||
* encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
|
||
* }
|
||
*
|
||
* ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
*
|
||
* The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
|
||
* for the algorithm, if any. In the case of AES and DES3, there is only one,
|
||
* the IV.
|
||
*
|
||
* AlgorithmIdentifer ::= SEQUENCE {
|
||
* algorithm OBJECT IDENTIFIER,
|
||
* parameters ANY DEFINED BY algorithm OPTIONAL
|
||
* }
|
||
*
|
||
* EncryptedContent ::= OCTET STRING
|
||
*
|
||
* RecipientInfo ::= SEQUENCE {
|
||
* version Version,
|
||
* issuerAndSerialNumber IssuerAndSerialNumber,
|
||
* keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
|
||
* encryptedKey EncryptedKey
|
||
* }
|
||
*
|
||
* IssuerAndSerialNumber ::= SEQUENCE {
|
||
* issuer Name,
|
||
* serialNumber CertificateSerialNumber
|
||
* }
|
||
*
|
||
* CertificateSerialNumber ::= INTEGER
|
||
*
|
||
* KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
*
|
||
* EncryptedKey ::= OCTET STRING
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for ASN.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
// shortcut for PKCS#7 API
|
||
var p7v = forge.pkcs7asn1 = forge.pkcs7asn1 || {};
|
||
forge.pkcs7 = forge.pkcs7 || {};
|
||
forge.pkcs7.asn1 = p7v;
|
||
|
||
var contentInfoValidator = {
|
||
name: 'ContentInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'ContentInfo.ContentType',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'contentType'
|
||
}, {
|
||
name: 'ContentInfo.content',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
constructed: true,
|
||
optional: true,
|
||
captureAsn1: 'content'
|
||
}]
|
||
};
|
||
p7v.contentInfoValidator = contentInfoValidator;
|
||
|
||
var encryptedContentInfoValidator = {
|
||
name: 'EncryptedContentInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'EncryptedContentInfo.contentType',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'contentType'
|
||
}, {
|
||
name: 'EncryptedContentInfo.contentEncryptionAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'EncryptedContentInfo.contentEncryptionAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'encAlgorithm'
|
||
}, {
|
||
name: 'EncryptedContentInfo.contentEncryptionAlgorithm.parameter',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
captureAsn1: 'encParameter'
|
||
}]
|
||
}, {
|
||
name: 'EncryptedContentInfo.encryptedContent',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
/* The PKCS#7 structure output by OpenSSL somewhat differs from what
|
||
* other implementations do generate.
|
||
*
|
||
* OpenSSL generates a structure like this:
|
||
* SEQUENCE {
|
||
* ...
|
||
* [0]
|
||
* 26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
|
||
* C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
|
||
* ...
|
||
* }
|
||
*
|
||
* Whereas other implementations (and this PKCS#7 module) generate:
|
||
* SEQUENCE {
|
||
* ...
|
||
* [0] {
|
||
* OCTET STRING
|
||
* 26 DA 67 D2 17 9C 45 3C B1 2A A8 59 2F 29 33 38
|
||
* C3 C3 DF 86 71 74 7A 19 9F 40 D0 29 BE 85 90 45
|
||
* ...
|
||
* }
|
||
* }
|
||
*
|
||
* In order to support both, we just capture the context specific
|
||
* field here. The OCTET STRING bit is removed below.
|
||
*/
|
||
capture: 'encryptedContent',
|
||
captureAsn1: 'encryptedContentAsn1'
|
||
}]
|
||
};
|
||
|
||
p7v.envelopedDataValidator = {
|
||
name: 'EnvelopedData',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'EnvelopedData.Version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'version'
|
||
}, {
|
||
name: 'EnvelopedData.RecipientInfos',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
constructed: true,
|
||
captureAsn1: 'recipientInfos'
|
||
}].concat(encryptedContentInfoValidator)
|
||
};
|
||
|
||
p7v.encryptedDataValidator = {
|
||
name: 'EncryptedData',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'EncryptedData.Version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'version'
|
||
}].concat(encryptedContentInfoValidator)
|
||
};
|
||
|
||
var signerValidator = {
|
||
name: 'SignerInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'SignerInfo.version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false
|
||
}, {
|
||
name: 'SignerInfo.issuerAndSerialNumber',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'SignerInfo.issuerAndSerialNumber.issuer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'issuer'
|
||
}, {
|
||
name: 'SignerInfo.issuerAndSerialNumber.serialNumber',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'serial'
|
||
}]
|
||
}, {
|
||
name: 'SignerInfo.digestAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'SignerInfo.digestAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'digestAlgorithm'
|
||
}, {
|
||
name: 'SignerInfo.digestAlgorithm.parameter',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
constructed: false,
|
||
captureAsn1: 'digestParameter',
|
||
optional: true
|
||
}]
|
||
}, {
|
||
name: 'SignerInfo.authenticatedAttributes',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
constructed: true,
|
||
optional: true,
|
||
capture: 'authenticatedAttributes'
|
||
}, {
|
||
name: 'SignerInfo.digestEncryptionAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
capture: 'signatureAlgorithm'
|
||
}, {
|
||
name: 'SignerInfo.encryptedDigest',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'signature'
|
||
}, {
|
||
name: 'SignerInfo.unauthenticatedAttributes',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 1,
|
||
constructed: true,
|
||
optional: true,
|
||
capture: 'unauthenticatedAttributes'
|
||
}]
|
||
};
|
||
|
||
p7v.signedDataValidator = {
|
||
name: 'SignedData',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'SignedData.Version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'version'
|
||
}, {
|
||
name: 'SignedData.DigestAlgorithms',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
constructed: true,
|
||
captureAsn1: 'digestAlgorithms'
|
||
},
|
||
contentInfoValidator,
|
||
{
|
||
name: 'SignedData.Certificates',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
optional: true,
|
||
captureAsn1: 'certificates'
|
||
}, {
|
||
name: 'SignedData.CertificateRevocationLists',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 1,
|
||
optional: true,
|
||
captureAsn1: 'crls'
|
||
}, {
|
||
name: 'SignedData.SignerInfos',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
capture: 'signerInfos',
|
||
optional: true,
|
||
value: [signerValidator]
|
||
}]
|
||
};
|
||
|
||
p7v.recipientInfoValidator = {
|
||
name: 'RecipientInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'RecipientInfo.version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'version'
|
||
}, {
|
||
name: 'RecipientInfo.issuerAndSerial',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'RecipientInfo.issuerAndSerial.issuer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'issuer'
|
||
}, {
|
||
name: 'RecipientInfo.issuerAndSerial.serialNumber',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'serial'
|
||
}]
|
||
}, {
|
||
name: 'RecipientInfo.keyEncryptionAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'RecipientInfo.keyEncryptionAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'encAlgorithm'
|
||
}, {
|
||
name: 'RecipientInfo.keyEncryptionAlgorithm.parameter',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
constructed: false,
|
||
captureAsn1: 'encParameter'
|
||
}]
|
||
}, {
|
||
name: 'RecipientInfo.encryptedKey',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'encKey'
|
||
}]
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pkcs7asn1';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pkcs7asn1',['require', 'module', './asn1', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of mask generation function MGF1.
|
||
*
|
||
* @author Stefan Siegl
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
* Copyright (c) 2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.mgf = forge.mgf || {};
|
||
var mgf1 = forge.mgf.mgf1 = forge.mgf1 = forge.mgf1 || {};
|
||
|
||
/**
|
||
* Creates a MGF1 mask generation function object.
|
||
*
|
||
* @param md the message digest API to use (eg: forge.md.sha1.create()).
|
||
*
|
||
* @return a mask generation function object.
|
||
*/
|
||
mgf1.create = function(md) {
|
||
var mgf = {
|
||
/**
|
||
* Generate mask of specified length.
|
||
*
|
||
* @param {String} seed The seed for mask generation.
|
||
* @param maskLen Number of bytes to generate.
|
||
* @return {String} The generated mask.
|
||
*/
|
||
generate: function(seed, maskLen) {
|
||
/* 2. Let T be the empty octet string. */
|
||
var t = new forge.util.ByteBuffer();
|
||
|
||
/* 3. For counter from 0 to ceil(maskLen / hLen), do the following: */
|
||
var len = Math.ceil(maskLen / md.digestLength);
|
||
for(var i = 0; i < len; i++) {
|
||
/* a. Convert counter to an octet string C of length 4 octets */
|
||
var c = new forge.util.ByteBuffer();
|
||
c.putInt32(i);
|
||
|
||
/* b. Concatenate the hash of the seed mgfSeed and C to the octet
|
||
* string T: */
|
||
md.start();
|
||
md.update(seed + c.getBytes());
|
||
t.putBuffer(md.digest());
|
||
}
|
||
|
||
/* Output the leading maskLen octets of T as the octet string mask. */
|
||
t.truncate(t.length() - maskLen);
|
||
return t.getBytes();
|
||
}
|
||
};
|
||
|
||
return mgf;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'mgf1';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/mgf1',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Node.js module for Forge mask generation functions.
|
||
*
|
||
* @author Stefan Siegl
|
||
*
|
||
* Copyright 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.mgf = forge.mgf || {};
|
||
forge.mgf.mgf1 = forge.mgf1;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'mgf';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/mgf',['require', 'module', './mgf1'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of PKCS#1 PSS signature padding.
|
||
*
|
||
* @author Stefan Siegl
|
||
*
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for PSS API
|
||
var pss = forge.pss = forge.pss || {};
|
||
|
||
/**
|
||
* Creates a PSS signature scheme object.
|
||
*
|
||
* There are several ways to provide a salt for encoding:
|
||
*
|
||
* 1. Specify the saltLength only and the built-in PRNG will generate it.
|
||
* 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
|
||
* will be used.
|
||
* 3. Specify the salt itself as a forge.util.ByteBuffer.
|
||
*
|
||
* @param options the options to use:
|
||
* md the message digest object to use, a forge md instance.
|
||
* mgf the mask generation function to use, a forge mgf instance.
|
||
* [saltLength] the length of the salt in octets.
|
||
* [prng] the pseudo-random number generator to use to produce a salt.
|
||
* [salt] the salt to use when encoding.
|
||
*
|
||
* @return a signature scheme object.
|
||
*/
|
||
pss.create = function(options) {
|
||
// backwards compatibility w/legacy args: hash, mgf, sLen
|
||
if(arguments.length === 3) {
|
||
options = {
|
||
md: arguments[0],
|
||
mgf: arguments[1],
|
||
saltLength: arguments[2]
|
||
};
|
||
}
|
||
|
||
var hash = options.md;
|
||
var mgf = options.mgf;
|
||
var hLen = hash.digestLength;
|
||
|
||
var salt_ = options.salt || null;
|
||
if(typeof salt_ === 'string') {
|
||
// assume binary-encoded string
|
||
salt_ = forge.util.createBuffer(salt_);
|
||
}
|
||
|
||
var sLen;
|
||
if('saltLength' in options) {
|
||
sLen = options.saltLength;
|
||
} else if(salt_ !== null) {
|
||
sLen = salt_.length();
|
||
} else {
|
||
throw new Error('Salt length not specified or specific salt not given.');
|
||
}
|
||
|
||
if(salt_ !== null && salt_.length() !== sLen) {
|
||
throw new Error('Given salt length does not match length of given salt.');
|
||
}
|
||
|
||
var prng = options.prng || forge.random;
|
||
|
||
var pssobj = {};
|
||
|
||
/**
|
||
* Encodes a PSS signature.
|
||
*
|
||
* This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
|
||
*
|
||
* @param md the message digest object with the hash to sign.
|
||
* @param modsBits the length of the RSA modulus in bits.
|
||
*
|
||
* @return the encoded message as a binary-encoded string of length
|
||
* ceil((modBits - 1) / 8).
|
||
*/
|
||
pssobj.encode = function(md, modBits) {
|
||
var i;
|
||
var emBits = modBits - 1;
|
||
var emLen = Math.ceil(emBits / 8);
|
||
|
||
/* 2. Let mHash = Hash(M), an octet string of length hLen. */
|
||
var mHash = md.digest().getBytes();
|
||
|
||
/* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
|
||
if(emLen < hLen + sLen + 2) {
|
||
throw new Error('Message is too long to encrypt.');
|
||
}
|
||
|
||
/* 4. Generate a random octet string salt of length sLen; if sLen = 0,
|
||
* then salt is the empty string. */
|
||
var salt;
|
||
if(salt_ === null) {
|
||
salt = prng.getBytesSync(sLen);
|
||
} else {
|
||
salt = salt_.bytes();
|
||
}
|
||
|
||
/* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
|
||
var m_ = new forge.util.ByteBuffer();
|
||
m_.fillWithByte(0, 8);
|
||
m_.putBytes(mHash);
|
||
m_.putBytes(salt);
|
||
|
||
/* 6. Let H = Hash(M'), an octet string of length hLen. */
|
||
hash.start();
|
||
hash.update(m_.getBytes());
|
||
var h = hash.digest().getBytes();
|
||
|
||
/* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
|
||
* zero octets. The length of PS may be 0. */
|
||
var ps = new forge.util.ByteBuffer();
|
||
ps.fillWithByte(0, emLen - sLen - hLen - 2);
|
||
|
||
/* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
|
||
* emLen - hLen - 1. */
|
||
ps.putByte(0x01);
|
||
ps.putBytes(salt);
|
||
var db = ps.getBytes();
|
||
|
||
/* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
|
||
var maskLen = emLen - hLen - 1;
|
||
var dbMask = mgf.generate(h, maskLen);
|
||
|
||
/* 10. Let maskedDB = DB \xor dbMask. */
|
||
var maskedDB = '';
|
||
for(i = 0; i < maskLen; i ++) {
|
||
maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
|
||
}
|
||
|
||
/* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
|
||
* maskedDB to zero. */
|
||
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
|
||
maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
|
||
maskedDB.substr(1);
|
||
|
||
/* 12. Let EM = maskedDB || H || 0xbc.
|
||
* 13. Output EM. */
|
||
return maskedDB + h + String.fromCharCode(0xbc);
|
||
};
|
||
|
||
/**
|
||
* Verifies a PSS signature.
|
||
*
|
||
* This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
|
||
*
|
||
* @param mHash the message digest hash, as a binary-encoded string, to
|
||
* compare against the signature.
|
||
* @param em the encoded message, as a binary-encoded string
|
||
* (RSA decryption result).
|
||
* @param modsBits the length of the RSA modulus in bits.
|
||
*
|
||
* @return true if the signature was verified, false if not.
|
||
*/
|
||
pssobj.verify = function(mHash, em, modBits) {
|
||
var i;
|
||
var emBits = modBits - 1;
|
||
var emLen = Math.ceil(emBits / 8);
|
||
|
||
/* c. Convert the message representative m to an encoded message EM
|
||
* of length emLen = ceil((modBits - 1) / 8) octets, where modBits
|
||
* is the length in bits of the RSA modulus n */
|
||
em = em.substr(-emLen);
|
||
|
||
/* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
|
||
if(emLen < hLen + sLen + 2) {
|
||
throw new Error('Inconsistent parameters to PSS signature verification.');
|
||
}
|
||
|
||
/* 4. If the rightmost octet of EM does not have hexadecimal value
|
||
* 0xbc, output "inconsistent" and stop. */
|
||
if(em.charCodeAt(emLen - 1) !== 0xbc) {
|
||
throw new Error('Encoded message does not end in 0xBC.');
|
||
}
|
||
|
||
/* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
|
||
* let H be the next hLen octets. */
|
||
var maskLen = emLen - hLen - 1;
|
||
var maskedDB = em.substr(0, maskLen);
|
||
var h = em.substr(maskLen, hLen);
|
||
|
||
/* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
|
||
* maskedDB are not all equal to zero, output "inconsistent" and stop. */
|
||
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
|
||
if((maskedDB.charCodeAt(0) & mask) !== 0) {
|
||
throw new Error('Bits beyond keysize not zero as expected.');
|
||
}
|
||
|
||
/* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
|
||
var dbMask = mgf.generate(h, maskLen);
|
||
|
||
/* 8. Let DB = maskedDB \xor dbMask. */
|
||
var db = '';
|
||
for(i = 0; i < maskLen; i ++) {
|
||
db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
|
||
}
|
||
|
||
/* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
|
||
* in DB to zero. */
|
||
db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
|
||
|
||
/* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
|
||
* or if the octet at position emLen - hLen - sLen - 1 (the leftmost
|
||
* position is "position 1") does not have hexadecimal value 0x01,
|
||
* output "inconsistent" and stop. */
|
||
var checkLen = emLen - hLen - sLen - 2;
|
||
for(i = 0; i < checkLen; i ++) {
|
||
if(db.charCodeAt(i) !== 0x00) {
|
||
throw new Error('Leftmost octets not zero as expected');
|
||
}
|
||
}
|
||
|
||
if(db.charCodeAt(checkLen) !== 0x01) {
|
||
throw new Error('Inconsistent PSS signature, 0x01 marker not found');
|
||
}
|
||
|
||
/* 11. Let salt be the last sLen octets of DB. */
|
||
var salt = db.substr(-sLen);
|
||
|
||
/* 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
|
||
var m_ = new forge.util.ByteBuffer();
|
||
m_.fillWithByte(0, 8);
|
||
m_.putBytes(mHash);
|
||
m_.putBytes(salt);
|
||
|
||
/* 13. Let H' = Hash(M'), an octet string of length hLen. */
|
||
hash.start();
|
||
hash.update(m_.getBytes());
|
||
var h_ = hash.digest().getBytes();
|
||
|
||
/* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
|
||
return h === h_;
|
||
};
|
||
|
||
return pssobj;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pss';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pss',['require', 'module', './random', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of X.509 and related components (such as
|
||
* Certification Signing Requests) of a Public Key Infrastructure.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
*
|
||
* The ASN.1 representation of an X.509v3 certificate is as follows
|
||
* (see RFC 2459):
|
||
*
|
||
* Certificate ::= SEQUENCE {
|
||
* tbsCertificate TBSCertificate,
|
||
* signatureAlgorithm AlgorithmIdentifier,
|
||
* signatureValue BIT STRING
|
||
* }
|
||
*
|
||
* TBSCertificate ::= SEQUENCE {
|
||
* version [0] EXPLICIT Version DEFAULT v1,
|
||
* serialNumber CertificateSerialNumber,
|
||
* signature AlgorithmIdentifier,
|
||
* issuer Name,
|
||
* validity Validity,
|
||
* subject Name,
|
||
* subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||
* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||
* -- If present, version shall be v2 or v3
|
||
* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||
* -- If present, version shall be v2 or v3
|
||
* extensions [3] EXPLICIT Extensions OPTIONAL
|
||
* -- If present, version shall be v3
|
||
* }
|
||
*
|
||
* Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
||
*
|
||
* CertificateSerialNumber ::= INTEGER
|
||
*
|
||
* Name ::= CHOICE {
|
||
* // only one possible choice for now
|
||
* RDNSequence
|
||
* }
|
||
*
|
||
* RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
|
||
*
|
||
* RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
|
||
*
|
||
* AttributeTypeAndValue ::= SEQUENCE {
|
||
* type AttributeType,
|
||
* value AttributeValue
|
||
* }
|
||
* AttributeType ::= OBJECT IDENTIFIER
|
||
* AttributeValue ::= ANY DEFINED BY AttributeType
|
||
*
|
||
* Validity ::= SEQUENCE {
|
||
* notBefore Time,
|
||
* notAfter Time
|
||
* }
|
||
*
|
||
* Time ::= CHOICE {
|
||
* utcTime UTCTime,
|
||
* generalTime GeneralizedTime
|
||
* }
|
||
*
|
||
* UniqueIdentifier ::= BIT STRING
|
||
*
|
||
* SubjectPublicKeyInfo ::= SEQUENCE {
|
||
* algorithm AlgorithmIdentifier,
|
||
* subjectPublicKey BIT STRING
|
||
* }
|
||
*
|
||
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||
*
|
||
* Extension ::= SEQUENCE {
|
||
* extnID OBJECT IDENTIFIER,
|
||
* critical BOOLEAN DEFAULT FALSE,
|
||
* extnValue OCTET STRING
|
||
* }
|
||
*
|
||
* The only key algorithm currently supported for PKI is RSA.
|
||
*
|
||
* RSASSA-PSS signatures are described in RFC 3447 and RFC 4055.
|
||
*
|
||
* PKCS#10 v1.7 describes certificate signing requests:
|
||
*
|
||
* CertificationRequestInfo:
|
||
*
|
||
* CertificationRequestInfo ::= SEQUENCE {
|
||
* version INTEGER { v1(0) } (v1,...),
|
||
* subject Name,
|
||
* subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
|
||
* attributes [0] Attributes{{ CRIAttributes }}
|
||
* }
|
||
*
|
||
* Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
|
||
*
|
||
* CRIAttributes ATTRIBUTE ::= {
|
||
* ... -- add any locally defined attributes here -- }
|
||
*
|
||
* Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
|
||
* type ATTRIBUTE.&id({IOSet}),
|
||
* values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
|
||
* }
|
||
*
|
||
* CertificationRequest ::= SEQUENCE {
|
||
* certificationRequestInfo CertificationRequestInfo,
|
||
* signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
|
||
* signature BIT STRING
|
||
* }
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for asn.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
/* Public Key Infrastructure (PKI) implementation. */
|
||
var pki = forge.pki = forge.pki || {};
|
||
var oids = pki.oids;
|
||
|
||
// short name OID mappings
|
||
var _shortNames = {};
|
||
_shortNames['CN'] = oids['commonName'];
|
||
_shortNames['commonName'] = 'CN';
|
||
_shortNames['C'] = oids['countryName'];
|
||
_shortNames['countryName'] = 'C';
|
||
_shortNames['L'] = oids['localityName'];
|
||
_shortNames['localityName'] = 'L';
|
||
_shortNames['ST'] = oids['stateOrProvinceName'];
|
||
_shortNames['stateOrProvinceName'] = 'ST';
|
||
_shortNames['O'] = oids['organizationName'];
|
||
_shortNames['organizationName'] = 'O';
|
||
_shortNames['OU'] = oids['organizationalUnitName'];
|
||
_shortNames['organizationalUnitName'] = 'OU';
|
||
_shortNames['E'] = oids['emailAddress'];
|
||
_shortNames['emailAddress'] = 'E';
|
||
|
||
// validator for an SubjectPublicKeyInfo structure
|
||
// Note: Currently only works with an RSA public key
|
||
var publicKeyValidator = forge.pki.rsa.publicKeyValidator;
|
||
|
||
// validator for an X.509v3 certificate
|
||
var x509CertificateValidator = {
|
||
name: 'Certificate',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'tbsCertificate',
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate.version',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
constructed: true,
|
||
optional: true,
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate.version.integer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'certVersion'
|
||
}]
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.serialNumber',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'certSerialNumber'
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.signature',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate.signature.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'certinfoSignatureOid'
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.signature.parameters',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
optional: true,
|
||
captureAsn1: 'certinfoSignatureParams'
|
||
}]
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.issuer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'certIssuer'
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.validity',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
// Note: UTC and generalized times may both appear so the capture
|
||
// names are based on their detected order, the names used below
|
||
// are only for the common case, which validity time really means
|
||
// "notBefore" and which means "notAfter" will be determined by order
|
||
value: [{
|
||
// notBefore (Time) (UTC time case)
|
||
name: 'Certificate.TBSCertificate.validity.notBefore (utc)',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.UTCTIME,
|
||
constructed: false,
|
||
optional: true,
|
||
capture: 'certValidity1UTCTime'
|
||
}, {
|
||
// notBefore (Time) (generalized time case)
|
||
name: 'Certificate.TBSCertificate.validity.notBefore (generalized)',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.GENERALIZEDTIME,
|
||
constructed: false,
|
||
optional: true,
|
||
capture: 'certValidity2GeneralizedTime'
|
||
}, {
|
||
// notAfter (Time) (only UTC time is supported)
|
||
name: 'Certificate.TBSCertificate.validity.notAfter (utc)',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.UTCTIME,
|
||
constructed: false,
|
||
optional: true,
|
||
capture: 'certValidity3UTCTime'
|
||
}, {
|
||
// notAfter (Time) (only UTC time is supported)
|
||
name: 'Certificate.TBSCertificate.validity.notAfter (generalized)',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.GENERALIZEDTIME,
|
||
constructed: false,
|
||
optional: true,
|
||
capture: 'certValidity4GeneralizedTime'
|
||
}]
|
||
}, {
|
||
// Name (subject) (RDNSequence)
|
||
name: 'Certificate.TBSCertificate.subject',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'certSubject'
|
||
},
|
||
// SubjectPublicKeyInfo
|
||
publicKeyValidator,
|
||
{
|
||
// issuerUniqueID (optional)
|
||
name: 'Certificate.TBSCertificate.issuerUniqueID',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 1,
|
||
constructed: true,
|
||
optional: true,
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate.issuerUniqueID.id',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.BITSTRING,
|
||
constructed: false,
|
||
capture: 'certIssuerUniqueId'
|
||
}]
|
||
}, {
|
||
// subjectUniqueID (optional)
|
||
name: 'Certificate.TBSCertificate.subjectUniqueID',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 2,
|
||
constructed: true,
|
||
optional: true,
|
||
value: [{
|
||
name: 'Certificate.TBSCertificate.subjectUniqueID.id',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.BITSTRING,
|
||
constructed: false,
|
||
capture: 'certSubjectUniqueId'
|
||
}]
|
||
}, {
|
||
// Extensions (optional)
|
||
name: 'Certificate.TBSCertificate.extensions',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 3,
|
||
constructed: true,
|
||
captureAsn1: 'certExtensions',
|
||
optional: true
|
||
}]
|
||
}, {
|
||
// AlgorithmIdentifier (signature algorithm)
|
||
name: 'Certificate.signatureAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
// algorithm
|
||
name: 'Certificate.signatureAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'certSignatureOid'
|
||
}, {
|
||
name: 'Certificate.TBSCertificate.signature.parameters',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
optional: true,
|
||
captureAsn1: 'certSignatureParams'
|
||
}]
|
||
}, {
|
||
// SignatureValue
|
||
name: 'Certificate.signatureValue',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.BITSTRING,
|
||
constructed: false,
|
||
capture: 'certSignature'
|
||
}]
|
||
};
|
||
|
||
var rsassaPssParameterValidator = {
|
||
name: 'rsapss',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'rsapss.hashAlgorithm',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'rsapss.hashAlgorithm.AlgorithmIdentifier',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Class.SEQUENCE,
|
||
constructed: true,
|
||
optional: true,
|
||
value: [{
|
||
name: 'rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'hashOid'
|
||
/* parameter block omitted, for SHA1 NULL anyhow. */
|
||
}]
|
||
}]
|
||
}, {
|
||
name: 'rsapss.maskGenAlgorithm',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 1,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Class.SEQUENCE,
|
||
constructed: true,
|
||
optional: true,
|
||
value: [{
|
||
name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'maskGenOid'
|
||
}, {
|
||
name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'maskGenHashOid'
|
||
/* parameter block omitted, for SHA1 NULL anyhow. */
|
||
}]
|
||
}]
|
||
}]
|
||
}, {
|
||
name: 'rsapss.saltLength',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 2,
|
||
optional: true,
|
||
value: [{
|
||
name: 'rsapss.saltLength.saltLength',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Class.INTEGER,
|
||
constructed: false,
|
||
capture: 'saltLength'
|
||
}]
|
||
}, {
|
||
name: 'rsapss.trailerField',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 3,
|
||
optional: true,
|
||
value: [{
|
||
name: 'rsapss.trailer.trailer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Class.INTEGER,
|
||
constructed: false,
|
||
capture: 'trailer'
|
||
}]
|
||
}]
|
||
};
|
||
|
||
// validator for a CertificationRequestInfo structure
|
||
var certificationRequestInfoValidator = {
|
||
name: 'CertificationRequestInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'certificationRequestInfo',
|
||
value: [{
|
||
name: 'CertificationRequestInfo.integer',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'certificationRequestInfoVersion'
|
||
}, {
|
||
// Name (subject) (RDNSequence)
|
||
name: 'CertificationRequestInfo.subject',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'certificationRequestInfoSubject'
|
||
},
|
||
// SubjectPublicKeyInfo
|
||
publicKeyValidator,
|
||
{
|
||
name: 'CertificationRequestInfo.attributes',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
type: 0,
|
||
constructed: true,
|
||
optional: true,
|
||
capture: 'certificationRequestInfoAttributes',
|
||
value: [{
|
||
name: 'CertificationRequestInfo.attributes',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'CertificationRequestInfo.attributes.type',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false
|
||
}, {
|
||
name: 'CertificationRequestInfo.attributes.value',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
constructed: true
|
||
}]
|
||
}]
|
||
}]
|
||
};
|
||
|
||
// validator for a CertificationRequest structure
|
||
var certificationRequestValidator = {
|
||
name: 'CertificationRequest',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
captureAsn1: 'csr',
|
||
value: [
|
||
certificationRequestInfoValidator, {
|
||
// AlgorithmIdentifier (signature algorithm)
|
||
name: 'CertificationRequest.signatureAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
// algorithm
|
||
name: 'CertificationRequest.signatureAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'csrSignatureOid'
|
||
}, {
|
||
name: 'CertificationRequest.signatureAlgorithm.parameters',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
optional: true,
|
||
captureAsn1: 'csrSignatureParams'
|
||
}]
|
||
}, {
|
||
// signature
|
||
name: 'CertificationRequest.signature',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.BITSTRING,
|
||
constructed: false,
|
||
capture: 'csrSignature'
|
||
}]
|
||
};
|
||
|
||
/**
|
||
* Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName
|
||
* sets into an array with objects that have type and value properties.
|
||
*
|
||
* @param rdn the RDNSequence to convert.
|
||
* @param md a message digest to append type and value to if provided.
|
||
*/
|
||
pki.RDNAttributesAsArray = function(rdn, md) {
|
||
var rval = [];
|
||
|
||
// each value in 'rdn' in is a SET of RelativeDistinguishedName
|
||
var set, attr, obj;
|
||
for(var si = 0; si < rdn.value.length; ++si) {
|
||
// get the RelativeDistinguishedName set
|
||
set = rdn.value[si];
|
||
|
||
// each value in the SET is an AttributeTypeAndValue sequence
|
||
// containing first a type (an OID) and second a value (defined by
|
||
// the OID)
|
||
for(var i = 0; i < set.value.length; ++i) {
|
||
obj = {};
|
||
attr = set.value[i];
|
||
obj.type = asn1.derToOid(attr.value[0].value);
|
||
obj.value = attr.value[1].value;
|
||
obj.valueTagClass = attr.value[1].type;
|
||
// if the OID is known, get its name and short name
|
||
if(obj.type in oids) {
|
||
obj.name = oids[obj.type];
|
||
if(obj.name in _shortNames) {
|
||
obj.shortName = _shortNames[obj.name];
|
||
}
|
||
}
|
||
if(md) {
|
||
md.update(obj.type);
|
||
md.update(obj.value);
|
||
}
|
||
rval.push(obj);
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts ASN.1 CRIAttributes into an array with objects that have type and
|
||
* value properties.
|
||
*
|
||
* @param attributes the CRIAttributes to convert.
|
||
*/
|
||
pki.CRIAttributesAsArray = function(attributes) {
|
||
var rval = [];
|
||
|
||
// each value in 'attributes' in is a SEQUENCE with an OID and a SET
|
||
for(var si = 0; si < attributes.length; ++si) {
|
||
// get the attribute sequence
|
||
var seq = attributes[si];
|
||
|
||
// each value in the SEQUENCE containing first a type (an OID) and
|
||
// second a set of values (defined by the OID)
|
||
var type = asn1.derToOid(seq.value[0].value);
|
||
var values = seq.value[1].value;
|
||
for(var vi = 0; vi < values.length; ++vi) {
|
||
var obj = {};
|
||
obj.type = type;
|
||
obj.value = values[vi].value;
|
||
obj.valueTagClass = values[vi].type;
|
||
// if the OID is known, get its name and short name
|
||
if(obj.type in oids) {
|
||
obj.name = oids[obj.type];
|
||
if(obj.name in _shortNames) {
|
||
obj.shortName = _shortNames[obj.name];
|
||
}
|
||
}
|
||
// parse extensions
|
||
if(obj.type === oids.extensionRequest) {
|
||
obj.extensions = [];
|
||
for(var ei = 0; ei < obj.value.length; ++ei) {
|
||
obj.extensions.push(pki.certificateExtensionFromAsn1(obj.value[ei]));
|
||
}
|
||
}
|
||
rval.push(obj);
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets an issuer or subject attribute from its name, type, or short name.
|
||
*
|
||
* @param obj the issuer or subject object.
|
||
* @param options a short name string or an object with:
|
||
* shortName the short name for the attribute.
|
||
* name the name for the attribute.
|
||
* type the type for the attribute.
|
||
*
|
||
* @return the attribute.
|
||
*/
|
||
function _getAttribute(obj, options) {
|
||
if(typeof options === 'string') {
|
||
options = {shortName: options};
|
||
}
|
||
|
||
var rval = null;
|
||
var attr;
|
||
for(var i = 0; rval === null && i < obj.attributes.length; ++i) {
|
||
attr = obj.attributes[i];
|
||
if(options.type && options.type === attr.type) {
|
||
rval = attr;
|
||
} else if(options.name && options.name === attr.name) {
|
||
rval = attr;
|
||
} else if(options.shortName && options.shortName === attr.shortName) {
|
||
rval = attr;
|
||
}
|
||
}
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Converts signature parameters from ASN.1 structure.
|
||
*
|
||
* Currently only RSASSA-PSS supported. The PKCS#1 v1.5 signature scheme had
|
||
* no parameters.
|
||
*
|
||
* RSASSA-PSS-params ::= SEQUENCE {
|
||
* hashAlgorithm [0] HashAlgorithm DEFAULT
|
||
* sha1Identifier,
|
||
* maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT
|
||
* mgf1SHA1Identifier,
|
||
* saltLength [2] INTEGER DEFAULT 20,
|
||
* trailerField [3] INTEGER DEFAULT 1
|
||
* }
|
||
*
|
||
* HashAlgorithm ::= AlgorithmIdentifier
|
||
*
|
||
* MaskGenAlgorithm ::= AlgorithmIdentifier
|
||
*
|
||
* AlgorithmIdentifer ::= SEQUENCE {
|
||
* algorithm OBJECT IDENTIFIER,
|
||
* parameters ANY DEFINED BY algorithm OPTIONAL
|
||
* }
|
||
*
|
||
* @param oid The OID specifying the signature algorithm
|
||
* @param obj The ASN.1 structure holding the parameters
|
||
* @param fillDefaults Whether to use return default values where omitted
|
||
* @return signature parameter object
|
||
*/
|
||
var _readSignatureParameters = function(oid, obj, fillDefaults) {
|
||
var params = {};
|
||
|
||
if(oid !== oids['RSASSA-PSS']) {
|
||
return params;
|
||
}
|
||
|
||
if(fillDefaults) {
|
||
params = {
|
||
hash: {
|
||
algorithmOid: oids['sha1']
|
||
},
|
||
mgf: {
|
||
algorithmOid: oids['mgf1'],
|
||
hash: {
|
||
algorithmOid: oids['sha1']
|
||
}
|
||
},
|
||
saltLength: 20
|
||
};
|
||
}
|
||
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, rsassaPssParameterValidator, capture, errors)) {
|
||
var error = new Error('Cannot read RSASSA-PSS parameter block.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
if(capture.hashOid !== undefined) {
|
||
params.hash = params.hash || {};
|
||
params.hash.algorithmOid = asn1.derToOid(capture.hashOid);
|
||
}
|
||
|
||
if(capture.maskGenOid !== undefined) {
|
||
params.mgf = params.mgf || {};
|
||
params.mgf.algorithmOid = asn1.derToOid(capture.maskGenOid);
|
||
params.mgf.hash = params.mgf.hash || {};
|
||
params.mgf.hash.algorithmOid = asn1.derToOid(capture.maskGenHashOid);
|
||
}
|
||
|
||
if(capture.saltLength !== undefined) {
|
||
params.saltLength = capture.saltLength.charCodeAt(0);
|
||
}
|
||
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* Converts an X.509 certificate from PEM format.
|
||
*
|
||
* Note: If the certificate is to be verified then compute hash should
|
||
* be set to true. This will scan the TBSCertificate part of the ASN.1
|
||
* object while it is converted so it doesn't need to be converted back
|
||
* to ASN.1-DER-encoding later.
|
||
*
|
||
* @param pem the PEM-formatted certificate.
|
||
* @param computeHash true to compute the hash for verification.
|
||
* @param strict true to be strict when checking ASN.1 value lengths, false to
|
||
* allow truncated values (default: true).
|
||
*
|
||
* @return the certificate.
|
||
*/
|
||
pki.certificateFromPem = function(pem, computeHash, strict) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'CERTIFICATE' &&
|
||
msg.type !== 'X509 CERTIFICATE' &&
|
||
msg.type !== 'TRUSTED CERTIFICATE') {
|
||
var error = new Error('Could not convert certificate from PEM; PEM header type ' +
|
||
'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
var obj = asn1.fromDer(msg.body, strict);
|
||
|
||
return pki.certificateFromAsn1(obj, computeHash);
|
||
};
|
||
|
||
/**
|
||
* Converts an X.509 certificate to PEM format.
|
||
*
|
||
* @param cert the certificate.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted certificate.
|
||
*/
|
||
pki.certificateToPem = function(cert, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var msg = {
|
||
type: 'CERTIFICATE',
|
||
body: asn1.toDer(pki.certificateToAsn1(cert)).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Converts an RSA public key from PEM format.
|
||
*
|
||
* @param pem the PEM-formatted public key.
|
||
*
|
||
* @return the public key.
|
||
*/
|
||
pki.publicKeyFromPem = function(pem) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'PUBLIC KEY' && msg.type !== 'RSA PUBLIC KEY') {
|
||
var error = new Error('Could not convert public key from PEM; PEM header ' +
|
||
'type is not "PUBLIC KEY" or "RSA PUBLIC KEY".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert public key from PEM; PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
var obj = asn1.fromDer(msg.body);
|
||
|
||
return pki.publicKeyFromAsn1(obj);
|
||
};
|
||
|
||
/**
|
||
* Converts an RSA public key to PEM format (using a SubjectPublicKeyInfo).
|
||
*
|
||
* @param key the public key.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted public key.
|
||
*/
|
||
pki.publicKeyToPem = function(key, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var msg = {
|
||
type: 'PUBLIC KEY',
|
||
body: asn1.toDer(pki.publicKeyToAsn1(key)).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Converts an RSA public key to PEM format (using an RSAPublicKey).
|
||
*
|
||
* @param key the public key.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted public key.
|
||
*/
|
||
pki.publicKeyToRSAPublicKeyPem = function(key, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var msg = {
|
||
type: 'RSA PUBLIC KEY',
|
||
body: asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Gets a fingerprint for the given public key.
|
||
*
|
||
* @param options the options to use.
|
||
* [md] the message digest object to use (defaults to forge.md.sha1).
|
||
* [type] the type of fingerprint, such as 'RSAPublicKey',
|
||
* 'SubjectPublicKeyInfo' (defaults to 'RSAPublicKey').
|
||
* [encoding] an alternative output encoding, such as 'hex'
|
||
* (defaults to none, outputs a byte buffer).
|
||
* [delimiter] the delimiter to use between bytes for 'hex' encoded
|
||
* output, eg: ':' (defaults to none).
|
||
*
|
||
* @return the fingerprint as a byte buffer or other encoding based on options.
|
||
*/
|
||
pki.getPublicKeyFingerprint = function(key, options) {
|
||
options = options || {};
|
||
var md = options.md || forge.md.sha1.create();
|
||
var type = options.type || 'RSAPublicKey';
|
||
|
||
var bytes;
|
||
switch(type) {
|
||
case 'RSAPublicKey':
|
||
bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes();
|
||
break;
|
||
case 'SubjectPublicKeyInfo':
|
||
bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes();
|
||
break;
|
||
default:
|
||
throw new Error('Unknown fingerprint type "' + options.type + '".');
|
||
}
|
||
|
||
// hash public key bytes
|
||
md.start();
|
||
md.update(bytes);
|
||
var digest = md.digest();
|
||
if(options.encoding === 'hex') {
|
||
var hex = digest.toHex();
|
||
if(options.delimiter) {
|
||
return hex.match(/.{2}/g).join(options.delimiter);
|
||
}
|
||
return hex;
|
||
} else if(options.encoding === 'binary') {
|
||
return digest.getBytes();
|
||
} else if(options.encoding) {
|
||
throw new Error('Unknown encoding "' + options.encoding + '".');
|
||
}
|
||
return digest;
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#10 certification request (CSR) from PEM format.
|
||
*
|
||
* Note: If the certification request is to be verified then compute hash
|
||
* should be set to true. This will scan the CertificationRequestInfo part of
|
||
* the ASN.1 object while it is converted so it doesn't need to be converted
|
||
* back to ASN.1-DER-encoding later.
|
||
*
|
||
* @param pem the PEM-formatted certificate.
|
||
* @param computeHash true to compute the hash for verification.
|
||
* @param strict true to be strict when checking ASN.1 value lengths, false to
|
||
* allow truncated values (default: true).
|
||
*
|
||
* @return the certification request (CSR).
|
||
*/
|
||
pki.certificationRequestFromPem = function(pem, computeHash, strict) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'CERTIFICATE REQUEST') {
|
||
var error = new Error('Could not convert certification request from PEM; ' +
|
||
'PEM header type is not "CERTIFICATE REQUEST".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert certification request from PEM; ' +
|
||
'PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
var obj = asn1.fromDer(msg.body, strict);
|
||
|
||
return pki.certificationRequestFromAsn1(obj, computeHash);
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#10 certification request (CSR) to PEM format.
|
||
*
|
||
* @param csr the certification request.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted certification request.
|
||
*/
|
||
pki.certificationRequestToPem = function(csr, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var msg = {
|
||
type: 'CERTIFICATE REQUEST',
|
||
body: asn1.toDer(pki.certificationRequestToAsn1(csr)).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Creates an empty X.509v3 RSA certificate.
|
||
*
|
||
* @return the certificate.
|
||
*/
|
||
pki.createCertificate = function() {
|
||
var cert = {};
|
||
cert.version = 0x02;
|
||
cert.serialNumber = '00';
|
||
cert.signatureOid = null;
|
||
cert.signature = null;
|
||
cert.siginfo = {};
|
||
cert.siginfo.algorithmOid = null;
|
||
cert.validity = {};
|
||
cert.validity.notBefore = new Date();
|
||
cert.validity.notAfter = new Date();
|
||
|
||
cert.issuer = {};
|
||
cert.issuer.getField = function(sn) {
|
||
return _getAttribute(cert.issuer, sn);
|
||
};
|
||
cert.issuer.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
cert.issuer.attributes.push(attr);
|
||
};
|
||
cert.issuer.attributes = [];
|
||
cert.issuer.hash = null;
|
||
|
||
cert.subject = {};
|
||
cert.subject.getField = function(sn) {
|
||
return _getAttribute(cert.subject, sn);
|
||
};
|
||
cert.subject.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
cert.subject.attributes.push(attr);
|
||
};
|
||
cert.subject.attributes = [];
|
||
cert.subject.hash = null;
|
||
|
||
cert.extensions = [];
|
||
cert.publicKey = null;
|
||
cert.md = null;
|
||
|
||
/**
|
||
* Sets the subject of this certificate.
|
||
*
|
||
* @param attrs the array of subject attributes to use.
|
||
* @param uniqueId an optional a unique ID to use.
|
||
*/
|
||
cert.setSubject = function(attrs, uniqueId) {
|
||
// set new attributes, clear hash
|
||
_fillMissingFields(attrs);
|
||
cert.subject.attributes = attrs;
|
||
delete cert.subject.uniqueId;
|
||
if(uniqueId) {
|
||
cert.subject.uniqueId = uniqueId;
|
||
}
|
||
cert.subject.hash = null;
|
||
};
|
||
|
||
/**
|
||
* Sets the issuer of this certificate.
|
||
*
|
||
* @param attrs the array of issuer attributes to use.
|
||
* @param uniqueId an optional a unique ID to use.
|
||
*/
|
||
cert.setIssuer = function(attrs, uniqueId) {
|
||
// set new attributes, clear hash
|
||
_fillMissingFields(attrs);
|
||
cert.issuer.attributes = attrs;
|
||
delete cert.issuer.uniqueId;
|
||
if(uniqueId) {
|
||
cert.issuer.uniqueId = uniqueId;
|
||
}
|
||
cert.issuer.hash = null;
|
||
};
|
||
|
||
/**
|
||
* Sets the extensions of this certificate.
|
||
*
|
||
* @param exts the array of extensions to use.
|
||
*/
|
||
cert.setExtensions = function(exts) {
|
||
for(var i = 0; i < exts.length; ++i) {
|
||
_fillMissingExtensionFields(exts[i], {cert: cert});
|
||
}
|
||
// set new extensions
|
||
cert.extensions = exts;
|
||
};
|
||
|
||
/**
|
||
* Gets an extension by its name or id.
|
||
*
|
||
* @param options the name to use or an object with:
|
||
* name the name to use.
|
||
* id the id to use.
|
||
*
|
||
* @return the extension or null if not found.
|
||
*/
|
||
cert.getExtension = function(options) {
|
||
if(typeof options === 'string') {
|
||
options = {name: options};
|
||
}
|
||
|
||
var rval = null;
|
||
var ext;
|
||
for(var i = 0; rval === null && i < cert.extensions.length; ++i) {
|
||
ext = cert.extensions[i];
|
||
if(options.id && ext.id === options.id) {
|
||
rval = ext;
|
||
} else if(options.name && ext.name === options.name) {
|
||
rval = ext;
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Signs this certificate using the given private key.
|
||
*
|
||
* @param key the private key to sign with.
|
||
* @param md the message digest object to use (defaults to forge.md.sha1).
|
||
*/
|
||
cert.sign = function(key, md) {
|
||
// TODO: get signature OID from private key
|
||
cert.md = md || forge.md.sha1.create();
|
||
var algorithmOid = oids[cert.md.algorithm + 'WithRSAEncryption'];
|
||
if(!algorithmOid) {
|
||
var error = new Error('Could not compute certificate digest. ' +
|
||
'Unknown message digest algorithm OID.');
|
||
error.algorithm = cert.md.algorithm;
|
||
throw error;
|
||
}
|
||
cert.signatureOid = cert.siginfo.algorithmOid = algorithmOid;
|
||
|
||
// get TBSCertificate, convert to DER
|
||
cert.tbsCertificate = pki.getTBSCertificate(cert);
|
||
var bytes = asn1.toDer(cert.tbsCertificate);
|
||
|
||
// digest and sign
|
||
cert.md.update(bytes.getBytes());
|
||
cert.signature = key.sign(cert.md);
|
||
};
|
||
|
||
/**
|
||
* Attempts verify the signature on the passed certificate using this
|
||
* certificate's public key.
|
||
*
|
||
* @param child the certificate to verify.
|
||
*
|
||
* @return true if verified, false if not.
|
||
*/
|
||
cert.verify = function(child) {
|
||
var rval = false;
|
||
|
||
if(!cert.issued(child)) {
|
||
var issuer = child.issuer;
|
||
var subject = cert.subject;
|
||
var error = new Error('The parent certificate did not issue the given child ' +
|
||
'certificate; the child certificate\'s issuer does not match the ' +
|
||
'parent\'s subject.');
|
||
error.expectedIssuer = issuer.attributes;
|
||
error.actualIssuer = subject.attributes;
|
||
throw error;
|
||
}
|
||
|
||
var md = child.md;
|
||
if(md === null) {
|
||
// check signature OID for supported signature types
|
||
if(child.signatureOid in oids) {
|
||
var oid = oids[child.signatureOid];
|
||
switch(oid) {
|
||
case 'sha1WithRSAEncryption':
|
||
md = forge.md.sha1.create();
|
||
break;
|
||
case 'md5WithRSAEncryption':
|
||
md = forge.md.md5.create();
|
||
break;
|
||
case 'sha256WithRSAEncryption':
|
||
md = forge.md.sha256.create();
|
||
break;
|
||
case 'sha512WithRSAEncryption':
|
||
md = forge.md.sha512.create();
|
||
break;
|
||
case 'RSASSA-PSS':
|
||
md = forge.md.sha256.create();
|
||
break;
|
||
}
|
||
}
|
||
if(md === null) {
|
||
var error = new Error('Could not compute certificate digest. ' +
|
||
'Unknown signature OID.');
|
||
error.signatureOid = child.signatureOid;
|
||
throw error;
|
||
}
|
||
|
||
// produce DER formatted TBSCertificate and digest it
|
||
var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child);
|
||
var bytes = asn1.toDer(tbsCertificate);
|
||
md.update(bytes.getBytes());
|
||
}
|
||
|
||
if(md !== null) {
|
||
var scheme;
|
||
|
||
switch(child.signatureOid) {
|
||
case oids.sha1WithRSAEncryption:
|
||
scheme = undefined; /* use PKCS#1 v1.5 padding scheme */
|
||
break;
|
||
case oids['RSASSA-PSS']:
|
||
var hash, mgf;
|
||
|
||
/* initialize mgf */
|
||
hash = oids[child.signatureParameters.mgf.hash.algorithmOid];
|
||
if(hash === undefined || forge.md[hash] === undefined) {
|
||
var error = new Error('Unsupported MGF hash function.');
|
||
error.oid = child.signatureParameters.mgf.hash.algorithmOid;
|
||
error.name = hash;
|
||
throw error;
|
||
}
|
||
|
||
mgf = oids[child.signatureParameters.mgf.algorithmOid];
|
||
if(mgf === undefined || forge.mgf[mgf] === undefined) {
|
||
var error = new Error('Unsupported MGF function.');
|
||
error.oid = child.signatureParameters.mgf.algorithmOid;
|
||
error.name = mgf;
|
||
throw error;
|
||
}
|
||
|
||
mgf = forge.mgf[mgf].create(forge.md[hash].create());
|
||
|
||
/* initialize hash function */
|
||
hash = oids[child.signatureParameters.hash.algorithmOid];
|
||
if(hash === undefined || forge.md[hash] === undefined) {
|
||
throw {
|
||
message: 'Unsupported RSASSA-PSS hash function.',
|
||
oid: child.signatureParameters.hash.algorithmOid,
|
||
name: hash
|
||
};
|
||
}
|
||
|
||
scheme = forge.pss.create(forge.md[hash].create(), mgf,
|
||
child.signatureParameters.saltLength);
|
||
break;
|
||
}
|
||
|
||
// verify signature on cert using public key
|
||
rval = cert.publicKey.verify(
|
||
md.digest().getBytes(), child.signature, scheme);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Returns true if this certificate's issuer matches the passed
|
||
* certificate's subject. Note that no signature check is performed.
|
||
*
|
||
* @param parent the certificate to check.
|
||
*
|
||
* @return true if this certificate's issuer matches the passed certificate's
|
||
* subject.
|
||
*/
|
||
cert.isIssuer = function(parent) {
|
||
var rval = false;
|
||
|
||
var i = cert.issuer;
|
||
var s = parent.subject;
|
||
|
||
// compare hashes if present
|
||
if(i.hash && s.hash) {
|
||
rval = (i.hash === s.hash);
|
||
} else if(i.attributes.length === s.attributes.length) {
|
||
// all attributes are the same so issuer matches subject
|
||
rval = true;
|
||
var iattr, sattr;
|
||
for(var n = 0; rval && n < i.attributes.length; ++n) {
|
||
iattr = i.attributes[n];
|
||
sattr = s.attributes[n];
|
||
if(iattr.type !== sattr.type || iattr.value !== sattr.value) {
|
||
// attribute mismatch
|
||
rval = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Returns true if this certificate's subject matches the issuer of the
|
||
* given certificate). Note that not signature check is performed.
|
||
*
|
||
* @param child the certificate to check.
|
||
*
|
||
* @return true if this certificate's subject matches the passed
|
||
* certificate's issuer.
|
||
*/
|
||
cert.issued = function(child) {
|
||
return child.isIssuer(cert);
|
||
};
|
||
|
||
/**
|
||
* Generates the subjectKeyIdentifier for this certificate as byte buffer.
|
||
*
|
||
* @return the subjectKeyIdentifier for this certificate as byte buffer.
|
||
*/
|
||
cert.generateSubjectKeyIdentifier = function() {
|
||
/* See: 4.2.1.2 section of the the RFC3280, keyIdentifier is either:
|
||
|
||
(1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
|
||
value of the BIT STRING subjectPublicKey (excluding the tag,
|
||
length, and number of unused bits).
|
||
|
||
(2) The keyIdentifier is composed of a four bit type field with
|
||
the value 0100 followed by the least significant 60 bits of the
|
||
SHA-1 hash of the value of the BIT STRING subjectPublicKey
|
||
(excluding the tag, length, and number of unused bit string bits).
|
||
*/
|
||
|
||
// skipping the tag, length, and number of unused bits is the same
|
||
// as just using the RSAPublicKey (for RSA keys, which are the
|
||
// only ones supported)
|
||
return pki.getPublicKeyFingerprint(cert.publicKey, {type: 'RSAPublicKey'});
|
||
};
|
||
|
||
/**
|
||
* Verifies the subjectKeyIdentifier extension value for this certificate
|
||
* against its public key. If no extension is found, false will be
|
||
* returned.
|
||
*
|
||
* @return true if verified, false if not.
|
||
*/
|
||
cert.verifySubjectKeyIdentifier = function() {
|
||
var oid = oids['subjectKeyIdentifier'];
|
||
for(var i = 0; i < cert.extensions.length; ++i) {
|
||
var ext = cert.extensions[i];
|
||
if(ext.id === oid) {
|
||
var ski = cert.generateSubjectKeyIdentifier().getBytes();
|
||
return (forge.util.hexToBytes(ext.subjectKeyIdentifier) === ski);
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
return cert;
|
||
};
|
||
|
||
/**
|
||
* Converts an X.509v3 RSA certificate from an ASN.1 object.
|
||
*
|
||
* Note: If the certificate is to be verified then compute hash should
|
||
* be set to true. There is currently no implementation for converting
|
||
* a certificate back to ASN.1 so the TBSCertificate part of the ASN.1
|
||
* object needs to be scanned before the cert object is created.
|
||
*
|
||
* @param obj the asn1 representation of an X.509v3 RSA certificate.
|
||
* @param computeHash true to compute the hash for verification.
|
||
*
|
||
* @return the certificate.
|
||
*/
|
||
pki.certificateFromAsn1 = function(obj, computeHash) {
|
||
// validate certificate and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, x509CertificateValidator, capture, errors)) {
|
||
var error = new Error('Cannot read X.509 certificate. ' +
|
||
'ASN.1 object is not an X509v3 Certificate.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// ensure signature is not interpreted as an embedded ASN.1 object
|
||
if(typeof capture.certSignature !== 'string') {
|
||
var certSignature = '\x00';
|
||
for(var i = 0; i < capture.certSignature.length; ++i) {
|
||
certSignature += asn1.toDer(capture.certSignature[i]).getBytes();
|
||
}
|
||
capture.certSignature = certSignature;
|
||
}
|
||
|
||
// get oid
|
||
var oid = asn1.derToOid(capture.publicKeyOid);
|
||
if(oid !== pki.oids['rsaEncryption']) {
|
||
throw new Error('Cannot read public key. OID is not RSA.');
|
||
}
|
||
|
||
// create certificate
|
||
var cert = pki.createCertificate();
|
||
cert.version = capture.certVersion ?
|
||
capture.certVersion.charCodeAt(0) : 0;
|
||
var serial = forge.util.createBuffer(capture.certSerialNumber);
|
||
cert.serialNumber = serial.toHex();
|
||
cert.signatureOid = forge.asn1.derToOid(capture.certSignatureOid);
|
||
cert.signatureParameters = _readSignatureParameters(
|
||
cert.signatureOid, capture.certSignatureParams, true);
|
||
cert.siginfo.algorithmOid = forge.asn1.derToOid(capture.certinfoSignatureOid);
|
||
cert.siginfo.parameters = _readSignatureParameters(cert.siginfo.algorithmOid,
|
||
capture.certinfoSignatureParams, false);
|
||
// skip "unused bits" in signature value BITSTRING
|
||
var signature = forge.util.createBuffer(capture.certSignature);
|
||
++signature.read;
|
||
cert.signature = signature.getBytes();
|
||
|
||
var validity = [];
|
||
if(capture.certValidity1UTCTime !== undefined) {
|
||
validity.push(asn1.utcTimeToDate(capture.certValidity1UTCTime));
|
||
}
|
||
if(capture.certValidity2GeneralizedTime !== undefined) {
|
||
validity.push(asn1.generalizedTimeToDate(
|
||
capture.certValidity2GeneralizedTime));
|
||
}
|
||
if(capture.certValidity3UTCTime !== undefined) {
|
||
validity.push(asn1.utcTimeToDate(capture.certValidity3UTCTime));
|
||
}
|
||
if(capture.certValidity4GeneralizedTime !== undefined) {
|
||
validity.push(asn1.generalizedTimeToDate(
|
||
capture.certValidity4GeneralizedTime));
|
||
}
|
||
if(validity.length > 2) {
|
||
throw new Error('Cannot read notBefore/notAfter validity times; more ' +
|
||
'than two times were provided in the certificate.');
|
||
}
|
||
if(validity.length < 2) {
|
||
throw new Error('Cannot read notBefore/notAfter validity times; they ' +
|
||
'were not provided as either UTCTime or GeneralizedTime.');
|
||
}
|
||
cert.validity.notBefore = validity[0];
|
||
cert.validity.notAfter = validity[1];
|
||
|
||
// keep TBSCertificate to preserve signature when exporting
|
||
cert.tbsCertificate = capture.tbsCertificate;
|
||
|
||
if(computeHash) {
|
||
// check signature OID for supported signature types
|
||
cert.md = null;
|
||
if(cert.signatureOid in oids) {
|
||
var oid = oids[cert.signatureOid];
|
||
switch(oid) {
|
||
case 'sha1WithRSAEncryption':
|
||
cert.md = forge.md.sha1.create();
|
||
break;
|
||
case 'md5WithRSAEncryption':
|
||
cert.md = forge.md.md5.create();
|
||
break;
|
||
case 'sha256WithRSAEncryption':
|
||
cert.md = forge.md.sha256.create();
|
||
break;
|
||
case 'sha512WithRSAEncryption':
|
||
cert.md = forge.md.sha512.create();
|
||
break;
|
||
case 'RSASSA-PSS':
|
||
cert.md = forge.md.sha256.create();
|
||
break;
|
||
}
|
||
}
|
||
if(cert.md === null) {
|
||
var error = new Error('Could not compute certificate digest. ' +
|
||
'Unknown signature OID.');
|
||
error.signatureOid = cert.signatureOid;
|
||
throw error;
|
||
}
|
||
|
||
// produce DER formatted TBSCertificate and digest it
|
||
var bytes = asn1.toDer(cert.tbsCertificate);
|
||
cert.md.update(bytes.getBytes());
|
||
}
|
||
|
||
// handle issuer, build issuer message digest
|
||
var imd = forge.md.sha1.create();
|
||
cert.issuer.getField = function(sn) {
|
||
return _getAttribute(cert.issuer, sn);
|
||
};
|
||
cert.issuer.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
cert.issuer.attributes.push(attr);
|
||
};
|
||
cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd);
|
||
if(capture.certIssuerUniqueId) {
|
||
cert.issuer.uniqueId = capture.certIssuerUniqueId;
|
||
}
|
||
cert.issuer.hash = imd.digest().toHex();
|
||
|
||
// handle subject, build subject message digest
|
||
var smd = forge.md.sha1.create();
|
||
cert.subject.getField = function(sn) {
|
||
return _getAttribute(cert.subject, sn);
|
||
};
|
||
cert.subject.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
cert.subject.attributes.push(attr);
|
||
};
|
||
cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd);
|
||
if(capture.certSubjectUniqueId) {
|
||
cert.subject.uniqueId = capture.certSubjectUniqueId;
|
||
}
|
||
cert.subject.hash = smd.digest().toHex();
|
||
|
||
// handle extensions
|
||
if(capture.certExtensions) {
|
||
cert.extensions = pki.certificateExtensionsFromAsn1(capture.certExtensions);
|
||
} else {
|
||
cert.extensions = [];
|
||
}
|
||
|
||
// convert RSA public key from ASN.1
|
||
cert.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
|
||
|
||
return cert;
|
||
};
|
||
|
||
/**
|
||
* Converts an ASN.1 extensions object (with extension sequences as its
|
||
* values) into an array of extension objects with types and values.
|
||
*
|
||
* Supported extensions:
|
||
*
|
||
* id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
|
||
* KeyUsage ::= BIT STRING {
|
||
* digitalSignature (0),
|
||
* nonRepudiation (1),
|
||
* keyEncipherment (2),
|
||
* dataEncipherment (3),
|
||
* keyAgreement (4),
|
||
* keyCertSign (5),
|
||
* cRLSign (6),
|
||
* encipherOnly (7),
|
||
* decipherOnly (8)
|
||
* }
|
||
*
|
||
* id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
|
||
* BasicConstraints ::= SEQUENCE {
|
||
* cA BOOLEAN DEFAULT FALSE,
|
||
* pathLenConstraint INTEGER (0..MAX) OPTIONAL
|
||
* }
|
||
*
|
||
* subjectAltName EXTENSION ::= {
|
||
* SYNTAX GeneralNames
|
||
* IDENTIFIED BY id-ce-subjectAltName
|
||
* }
|
||
*
|
||
* GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
||
*
|
||
* GeneralName ::= CHOICE {
|
||
* otherName [0] INSTANCE OF OTHER-NAME,
|
||
* rfc822Name [1] IA5String,
|
||
* dNSName [2] IA5String,
|
||
* x400Address [3] ORAddress,
|
||
* directoryName [4] Name,
|
||
* ediPartyName [5] EDIPartyName,
|
||
* uniformResourceIdentifier [6] IA5String,
|
||
* IPAddress [7] OCTET STRING,
|
||
* registeredID [8] OBJECT IDENTIFIER
|
||
* }
|
||
*
|
||
* OTHER-NAME ::= TYPE-IDENTIFIER
|
||
*
|
||
* EDIPartyName ::= SEQUENCE {
|
||
* nameAssigner [0] DirectoryString {ub-name} OPTIONAL,
|
||
* partyName [1] DirectoryString {ub-name}
|
||
* }
|
||
*
|
||
* @param exts the extensions ASN.1 with extension sequences to parse.
|
||
*
|
||
* @return the array.
|
||
*/
|
||
pki.certificateExtensionsFromAsn1 = function(exts) {
|
||
var rval = [];
|
||
for(var i = 0; i < exts.value.length; ++i) {
|
||
// get extension sequence
|
||
var extseq = exts.value[i];
|
||
for(var ei = 0; ei < extseq.value.length; ++ei) {
|
||
rval.push(pki.certificateExtensionFromAsn1(extseq.value[ei]));
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Parses a single certificate extension from ASN.1.
|
||
*
|
||
* @param ext the extension in ASN.1 format.
|
||
*
|
||
* @return the parsed extension as an object.
|
||
*/
|
||
pki.certificateExtensionFromAsn1 = function(ext) {
|
||
// an extension has:
|
||
// [0] extnID OBJECT IDENTIFIER
|
||
// [1] critical BOOLEAN DEFAULT FALSE
|
||
// [2] extnValue OCTET STRING
|
||
var e = {};
|
||
e.id = asn1.derToOid(ext.value[0].value);
|
||
e.critical = false;
|
||
if(ext.value[1].type === asn1.Type.BOOLEAN) {
|
||
e.critical = (ext.value[1].value.charCodeAt(0) !== 0x00);
|
||
e.value = ext.value[2].value;
|
||
} else {
|
||
e.value = ext.value[1].value;
|
||
}
|
||
// if the oid is known, get its name
|
||
if(e.id in oids) {
|
||
e.name = oids[e.id];
|
||
|
||
// handle key usage
|
||
if(e.name === 'keyUsage') {
|
||
// get value as BIT STRING
|
||
var ev = asn1.fromDer(e.value);
|
||
var b2 = 0x00;
|
||
var b3 = 0x00;
|
||
if(ev.value.length > 1) {
|
||
// skip first byte, just indicates unused bits which
|
||
// will be padded with 0s anyway
|
||
// get bytes with flag bits
|
||
b2 = ev.value.charCodeAt(1);
|
||
b3 = ev.value.length > 2 ? ev.value.charCodeAt(2) : 0;
|
||
}
|
||
// set flags
|
||
e.digitalSignature = (b2 & 0x80) === 0x80;
|
||
e.nonRepudiation = (b2 & 0x40) === 0x40;
|
||
e.keyEncipherment = (b2 & 0x20) === 0x20;
|
||
e.dataEncipherment = (b2 & 0x10) === 0x10;
|
||
e.keyAgreement = (b2 & 0x08) === 0x08;
|
||
e.keyCertSign = (b2 & 0x04) === 0x04;
|
||
e.cRLSign = (b2 & 0x02) === 0x02;
|
||
e.encipherOnly = (b2 & 0x01) === 0x01;
|
||
e.decipherOnly = (b3 & 0x80) === 0x80;
|
||
} else if(e.name === 'basicConstraints') {
|
||
// handle basic constraints
|
||
// get value as SEQUENCE
|
||
var ev = asn1.fromDer(e.value);
|
||
// get cA BOOLEAN flag (defaults to false)
|
||
if(ev.value.length > 0 && ev.value[0].type === asn1.Type.BOOLEAN) {
|
||
e.cA = (ev.value[0].value.charCodeAt(0) !== 0x00);
|
||
} else {
|
||
e.cA = false;
|
||
}
|
||
// get path length constraint
|
||
var value = null;
|
||
if(ev.value.length > 0 && ev.value[0].type === asn1.Type.INTEGER) {
|
||
value = ev.value[0].value;
|
||
} else if(ev.value.length > 1) {
|
||
value = ev.value[1].value;
|
||
}
|
||
if(value !== null) {
|
||
e.pathLenConstraint = asn1.derToInteger(value);
|
||
}
|
||
} else if(e.name === 'extKeyUsage') {
|
||
// handle extKeyUsage
|
||
// value is a SEQUENCE of OIDs
|
||
var ev = asn1.fromDer(e.value);
|
||
for(var vi = 0; vi < ev.value.length; ++vi) {
|
||
var oid = asn1.derToOid(ev.value[vi].value);
|
||
if(oid in oids) {
|
||
e[oids[oid]] = true;
|
||
} else {
|
||
e[oid] = true;
|
||
}
|
||
}
|
||
} else if(e.name === 'nsCertType') {
|
||
// handle nsCertType
|
||
// get value as BIT STRING
|
||
var ev = asn1.fromDer(e.value);
|
||
var b2 = 0x00;
|
||
if(ev.value.length > 1) {
|
||
// skip first byte, just indicates unused bits which
|
||
// will be padded with 0s anyway
|
||
// get bytes with flag bits
|
||
b2 = ev.value.charCodeAt(1);
|
||
}
|
||
// set flags
|
||
e.client = (b2 & 0x80) === 0x80;
|
||
e.server = (b2 & 0x40) === 0x40;
|
||
e.email = (b2 & 0x20) === 0x20;
|
||
e.objsign = (b2 & 0x10) === 0x10;
|
||
e.reserved = (b2 & 0x08) === 0x08;
|
||
e.sslCA = (b2 & 0x04) === 0x04;
|
||
e.emailCA = (b2 & 0x02) === 0x02;
|
||
e.objCA = (b2 & 0x01) === 0x01;
|
||
} else if(
|
||
e.name === 'subjectAltName' ||
|
||
e.name === 'issuerAltName') {
|
||
// handle subjectAltName/issuerAltName
|
||
e.altNames = [];
|
||
|
||
// ev is a SYNTAX SEQUENCE
|
||
var gn;
|
||
var ev = asn1.fromDer(e.value);
|
||
for(var n = 0; n < ev.value.length; ++n) {
|
||
// get GeneralName
|
||
gn = ev.value[n];
|
||
|
||
var altName = {
|
||
type: gn.type,
|
||
value: gn.value
|
||
};
|
||
e.altNames.push(altName);
|
||
|
||
// Note: Support for types 1,2,6,7,8
|
||
switch(gn.type) {
|
||
// rfc822Name
|
||
case 1:
|
||
// dNSName
|
||
case 2:
|
||
// uniformResourceIdentifier (URI)
|
||
case 6:
|
||
break;
|
||
// IPAddress
|
||
case 7:
|
||
// convert to IPv4/IPv6 string representation
|
||
altName.ip = forge.util.bytesToIP(gn.value);
|
||
break;
|
||
// registeredID
|
||
case 8:
|
||
altName.oid = asn1.derToOid(gn.value);
|
||
break;
|
||
default:
|
||
// unsupported
|
||
}
|
||
}
|
||
} else if(e.name === 'subjectKeyIdentifier') {
|
||
// value is an OCTETSTRING w/the hash of the key-type specific
|
||
// public key structure (eg: RSAPublicKey)
|
||
var ev = asn1.fromDer(e.value);
|
||
e.subjectKeyIdentifier = forge.util.bytesToHex(ev.value);
|
||
}
|
||
}
|
||
return e;
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#10 certification request (CSR) from an ASN.1 object.
|
||
*
|
||
* Note: If the certification request is to be verified then compute hash
|
||
* should be set to true. There is currently no implementation for converting
|
||
* a certificate back to ASN.1 so the CertificationRequestInfo part of the
|
||
* ASN.1 object needs to be scanned before the csr object is created.
|
||
*
|
||
* @param obj the asn1 representation of a PKCS#10 certification request (CSR).
|
||
* @param computeHash true to compute the hash for verification.
|
||
*
|
||
* @return the certification request (CSR).
|
||
*/
|
||
pki.certificationRequestFromAsn1 = function(obj, computeHash) {
|
||
// validate certification request and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, certificationRequestValidator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#10 certificate request. ' +
|
||
'ASN.1 object is not a PKCS#10 CertificationRequest.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
// ensure signature is not interpreted as an embedded ASN.1 object
|
||
if(typeof capture.csrSignature !== 'string') {
|
||
var csrSignature = '\x00';
|
||
for(var i = 0; i < capture.csrSignature.length; ++i) {
|
||
csrSignature += asn1.toDer(capture.csrSignature[i]).getBytes();
|
||
}
|
||
capture.csrSignature = csrSignature;
|
||
}
|
||
|
||
// get oid
|
||
var oid = asn1.derToOid(capture.publicKeyOid);
|
||
if(oid !== pki.oids.rsaEncryption) {
|
||
throw new Error('Cannot read public key. OID is not RSA.');
|
||
}
|
||
|
||
// create certification request
|
||
var csr = pki.createCertificationRequest();
|
||
csr.version = capture.csrVersion ? capture.csrVersion.charCodeAt(0) : 0;
|
||
csr.signatureOid = forge.asn1.derToOid(capture.csrSignatureOid);
|
||
csr.signatureParameters = _readSignatureParameters(
|
||
csr.signatureOid, capture.csrSignatureParams, true);
|
||
csr.siginfo.algorithmOid = forge.asn1.derToOid(capture.csrSignatureOid);
|
||
csr.siginfo.parameters = _readSignatureParameters(
|
||
csr.siginfo.algorithmOid, capture.csrSignatureParams, false);
|
||
// skip "unused bits" in signature value BITSTRING
|
||
var signature = forge.util.createBuffer(capture.csrSignature);
|
||
++signature.read;
|
||
csr.signature = signature.getBytes();
|
||
|
||
// keep CertificationRequestInfo to preserve signature when exporting
|
||
csr.certificationRequestInfo = capture.certificationRequestInfo;
|
||
|
||
if(computeHash) {
|
||
// check signature OID for supported signature types
|
||
csr.md = null;
|
||
if(csr.signatureOid in oids) {
|
||
var oid = oids[csr.signatureOid];
|
||
switch(oid) {
|
||
case 'sha1WithRSAEncryption':
|
||
csr.md = forge.md.sha1.create();
|
||
break;
|
||
case 'md5WithRSAEncryption':
|
||
csr.md = forge.md.md5.create();
|
||
break;
|
||
case 'sha256WithRSAEncryption':
|
||
csr.md = forge.md.sha256.create();
|
||
break;
|
||
case 'sha512WithRSAEncryption':
|
||
csr.md = forge.md.sha512.create();
|
||
break;
|
||
case 'RSASSA-PSS':
|
||
csr.md = forge.md.sha256.create();
|
||
break;
|
||
}
|
||
}
|
||
if(csr.md === null) {
|
||
var error = new Error('Could not compute certification request digest. ' +
|
||
'Unknown signature OID.');
|
||
error.signatureOid = csr.signatureOid;
|
||
throw error;
|
||
}
|
||
|
||
// produce DER formatted CertificationRequestInfo and digest it
|
||
var bytes = asn1.toDer(csr.certificationRequestInfo);
|
||
csr.md.update(bytes.getBytes());
|
||
}
|
||
|
||
// handle subject, build subject message digest
|
||
var smd = forge.md.sha1.create();
|
||
csr.subject.getField = function(sn) {
|
||
return _getAttribute(csr.subject, sn);
|
||
};
|
||
csr.subject.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
csr.subject.attributes.push(attr);
|
||
};
|
||
csr.subject.attributes = pki.RDNAttributesAsArray(
|
||
capture.certificationRequestInfoSubject, smd);
|
||
csr.subject.hash = smd.digest().toHex();
|
||
|
||
// convert RSA public key from ASN.1
|
||
csr.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
|
||
|
||
// convert attributes from ASN.1
|
||
csr.getAttribute = function(sn) {
|
||
return _getAttribute(csr, sn);
|
||
};
|
||
csr.addAttribute = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
csr.attributes.push(attr);
|
||
};
|
||
csr.attributes = pki.CRIAttributesAsArray(
|
||
capture.certificationRequestInfoAttributes || []);
|
||
|
||
return csr;
|
||
};
|
||
|
||
/**
|
||
* Creates an empty certification request (a CSR or certificate signing
|
||
* request). Once created, its public key and attributes can be set and then
|
||
* it can be signed.
|
||
*
|
||
* @return the empty certification request.
|
||
*/
|
||
pki.createCertificationRequest = function() {
|
||
var csr = {};
|
||
csr.version = 0x00;
|
||
csr.signatureOid = null;
|
||
csr.signature = null;
|
||
csr.siginfo = {};
|
||
csr.siginfo.algorithmOid = null;
|
||
|
||
csr.subject = {};
|
||
csr.subject.getField = function(sn) {
|
||
return _getAttribute(csr.subject, sn);
|
||
};
|
||
csr.subject.addField = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
csr.subject.attributes.push(attr);
|
||
};
|
||
csr.subject.attributes = [];
|
||
csr.subject.hash = null;
|
||
|
||
csr.publicKey = null;
|
||
csr.attributes = [];
|
||
csr.getAttribute = function(sn) {
|
||
return _getAttribute(csr, sn);
|
||
};
|
||
csr.addAttribute = function(attr) {
|
||
_fillMissingFields([attr]);
|
||
csr.attributes.push(attr);
|
||
};
|
||
csr.md = null;
|
||
|
||
/**
|
||
* Sets the subject of this certification request.
|
||
*
|
||
* @param attrs the array of subject attributes to use.
|
||
*/
|
||
csr.setSubject = function(attrs) {
|
||
// set new attributes
|
||
_fillMissingFields(attrs);
|
||
csr.subject.attributes = attrs;
|
||
csr.subject.hash = null;
|
||
};
|
||
|
||
/**
|
||
* Sets the attributes of this certification request.
|
||
*
|
||
* @param attrs the array of attributes to use.
|
||
*/
|
||
csr.setAttributes = function(attrs) {
|
||
// set new attributes
|
||
_fillMissingFields(attrs);
|
||
csr.attributes = attrs;
|
||
};
|
||
|
||
/**
|
||
* Signs this certification request using the given private key.
|
||
*
|
||
* @param key the private key to sign with.
|
||
* @param md the message digest object to use (defaults to forge.md.sha1).
|
||
*/
|
||
csr.sign = function(key, md) {
|
||
// TODO: get signature OID from private key
|
||
csr.md = md || forge.md.sha1.create();
|
||
var algorithmOid = oids[csr.md.algorithm + 'WithRSAEncryption'];
|
||
if(!algorithmOid) {
|
||
var error = new Error('Could not compute certification request digest. ' +
|
||
'Unknown message digest algorithm OID.');
|
||
error.algorithm = csr.md.algorithm;
|
||
throw error;
|
||
}
|
||
csr.signatureOid = csr.siginfo.algorithmOid = algorithmOid;
|
||
|
||
// get CertificationRequestInfo, convert to DER
|
||
csr.certificationRequestInfo = pki.getCertificationRequestInfo(csr);
|
||
var bytes = asn1.toDer(csr.certificationRequestInfo);
|
||
|
||
// digest and sign
|
||
csr.md.update(bytes.getBytes());
|
||
csr.signature = key.sign(csr.md);
|
||
};
|
||
|
||
/**
|
||
* Attempts verify the signature on the passed certification request using
|
||
* its public key.
|
||
*
|
||
* A CSR that has been exported to a file in PEM format can be verified using
|
||
* OpenSSL using this command:
|
||
*
|
||
* openssl req -in <the-csr-pem-file> -verify -noout -text
|
||
*
|
||
* @return true if verified, false if not.
|
||
*/
|
||
csr.verify = function() {
|
||
var rval = false;
|
||
|
||
var md = csr.md;
|
||
if(md === null) {
|
||
// check signature OID for supported signature types
|
||
if(csr.signatureOid in oids) {
|
||
// TODO: create DRY `OID to md` function
|
||
var oid = oids[csr.signatureOid];
|
||
switch(oid) {
|
||
case 'sha1WithRSAEncryption':
|
||
md = forge.md.sha1.create();
|
||
break;
|
||
case 'md5WithRSAEncryption':
|
||
md = forge.md.md5.create();
|
||
break;
|
||
case 'sha256WithRSAEncryption':
|
||
md = forge.md.sha256.create();
|
||
break;
|
||
case 'sha512WithRSAEncryption':
|
||
md = forge.md.sha512.create();
|
||
break;
|
||
case 'RSASSA-PSS':
|
||
md = forge.md.sha256.create();
|
||
break;
|
||
}
|
||
}
|
||
if(md === null) {
|
||
var error = new Error('Could not compute certification request digest. ' +
|
||
'Unknown signature OID.');
|
||
error.signatureOid = csr.signatureOid;
|
||
throw error;
|
||
}
|
||
|
||
// produce DER formatted CertificationRequestInfo and digest it
|
||
var cri = csr.certificationRequestInfo ||
|
||
pki.getCertificationRequestInfo(csr);
|
||
var bytes = asn1.toDer(cri);
|
||
md.update(bytes.getBytes());
|
||
}
|
||
|
||
if(md !== null) {
|
||
var scheme;
|
||
|
||
switch(csr.signatureOid) {
|
||
case oids.sha1WithRSAEncryption:
|
||
/* use PKCS#1 v1.5 padding scheme */
|
||
break;
|
||
case oids['RSASSA-PSS']:
|
||
var hash, mgf;
|
||
|
||
/* initialize mgf */
|
||
hash = oids[csr.signatureParameters.mgf.hash.algorithmOid];
|
||
if(hash === undefined || forge.md[hash] === undefined) {
|
||
var error = new Error('Unsupported MGF hash function.');
|
||
error.oid = csr.signatureParameters.mgf.hash.algorithmOid;
|
||
error.name = hash;
|
||
throw error;
|
||
}
|
||
|
||
mgf = oids[csr.signatureParameters.mgf.algorithmOid];
|
||
if(mgf === undefined || forge.mgf[mgf] === undefined) {
|
||
var error = new Error('Unsupported MGF function.');
|
||
error.oid = csr.signatureParameters.mgf.algorithmOid;
|
||
error.name = mgf;
|
||
throw error;
|
||
}
|
||
|
||
mgf = forge.mgf[mgf].create(forge.md[hash].create());
|
||
|
||
/* initialize hash function */
|
||
hash = oids[csr.signatureParameters.hash.algorithmOid];
|
||
if(hash === undefined || forge.md[hash] === undefined) {
|
||
var error = new Error('Unsupported RSASSA-PSS hash function.');
|
||
error.oid = csr.signatureParameters.hash.algorithmOid;
|
||
error.name = hash;
|
||
throw error;
|
||
}
|
||
|
||
scheme = forge.pss.create(forge.md[hash].create(), mgf,
|
||
csr.signatureParameters.saltLength);
|
||
break;
|
||
}
|
||
|
||
// verify signature on csr using its public key
|
||
rval = csr.publicKey.verify(
|
||
md.digest().getBytes(), csr.signature, scheme);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
return csr;
|
||
};
|
||
|
||
/**
|
||
* Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
|
||
*
|
||
* @param obj the subject or issuer (distinguished name).
|
||
*
|
||
* @return the ASN.1 RDNSequence.
|
||
*/
|
||
function _dnToAsn1(obj) {
|
||
// create an empty RDNSequence
|
||
var rval = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
|
||
// iterate over attributes
|
||
var attr, set;
|
||
var attrs = obj.attributes;
|
||
for(var i = 0; i < attrs.length; ++i) {
|
||
attr = attrs[i];
|
||
var value = attr.value;
|
||
|
||
// reuse tag class for attribute value if available
|
||
var valueTagClass = asn1.Type.PRINTABLESTRING;
|
||
if('valueTagClass' in attr) {
|
||
valueTagClass = attr.valueTagClass;
|
||
|
||
if(valueTagClass === asn1.Type.UTF8) {
|
||
value = forge.util.encodeUtf8(value);
|
||
}
|
||
// FIXME: handle more encodings
|
||
}
|
||
|
||
// create a RelativeDistinguishedName set
|
||
// each value in the set is an AttributeTypeAndValue first
|
||
// containing the type (an OID) and second the value
|
||
set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// AttributeType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(attr.type).getBytes()),
|
||
// AttributeValue
|
||
asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
|
||
])
|
||
]);
|
||
rval.value.push(set);
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Gets all printable attributes (typically of an issuer or subject) in a
|
||
* simplified JSON format for display.
|
||
*
|
||
* @param attrs the attributes.
|
||
*
|
||
* @return the JSON for display.
|
||
*/
|
||
function _getAttributesAsJson(attrs) {
|
||
var rval = {};
|
||
for(var i = 0; i < attrs.length; ++i) {
|
||
var attr = attrs[i];
|
||
if(attr.shortName && (
|
||
attr.valueTagClass === asn1.Type.UTF8 ||
|
||
attr.valueTagClass === asn1.Type.PRINTABLESTRING ||
|
||
attr.valueTagClass === asn1.Type.IA5STRING)) {
|
||
var value = attr.value;
|
||
if(attr.valueTagClass === asn1.Type.UTF8) {
|
||
value = forge.util.encodeUtf8(attr.value);
|
||
}
|
||
if(!(attr.shortName in rval)) {
|
||
rval[attr.shortName] = value;
|
||
} else if(forge.util.isArray(rval[attr.shortName])) {
|
||
rval[attr.shortName].push(value);
|
||
} else {
|
||
rval[attr.shortName] = [rval[attr.shortName], value];
|
||
}
|
||
}
|
||
}
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Fills in missing fields in attributes.
|
||
*
|
||
* @param attrs the attributes to fill missing fields in.
|
||
*/
|
||
function _fillMissingFields(attrs) {
|
||
var attr;
|
||
for(var i = 0; i < attrs.length; ++i) {
|
||
attr = attrs[i];
|
||
|
||
// populate missing name
|
||
if(typeof attr.name === 'undefined') {
|
||
if(attr.type && attr.type in pki.oids) {
|
||
attr.name = pki.oids[attr.type];
|
||
} else if(attr.shortName && attr.shortName in _shortNames) {
|
||
attr.name = pki.oids[_shortNames[attr.shortName]];
|
||
}
|
||
}
|
||
|
||
// populate missing type (OID)
|
||
if(typeof attr.type === 'undefined') {
|
||
if(attr.name && attr.name in pki.oids) {
|
||
attr.type = pki.oids[attr.name];
|
||
} else {
|
||
var error = new Error('Attribute type not specified.');
|
||
error.attribute = attr;
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// populate missing shortname
|
||
if(typeof attr.shortName === 'undefined') {
|
||
if(attr.name && attr.name in _shortNames) {
|
||
attr.shortName = _shortNames[attr.name];
|
||
}
|
||
}
|
||
|
||
// convert extensions to value
|
||
if(attr.type === oids.extensionRequest) {
|
||
attr.valueConstructed = true;
|
||
attr.valueTagClass = asn1.Type.SEQUENCE;
|
||
if(!attr.value && attr.extensions) {
|
||
attr.value = [];
|
||
for(var ei = 0; ei < attr.extensions.length; ++ei) {
|
||
attr.value.push(pki.certificateExtensionToAsn1(
|
||
_fillMissingExtensionFields(attr.extensions[ei])));
|
||
}
|
||
}
|
||
}
|
||
|
||
if(typeof attr.value === 'undefined') {
|
||
var error = new Error('Attribute value not specified.');
|
||
error.attribute = attr;
|
||
throw error;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Fills in missing fields in certificate extensions.
|
||
*
|
||
* @param e the extension.
|
||
* @param [options] the options to use.
|
||
* [cert] the certificate the extensions are for.
|
||
*
|
||
* @return the extension.
|
||
*/
|
||
function _fillMissingExtensionFields(e, options) {
|
||
options = options || {};
|
||
|
||
// populate missing name
|
||
if(typeof e.name === 'undefined') {
|
||
if(e.id && e.id in pki.oids) {
|
||
e.name = pki.oids[e.id];
|
||
}
|
||
}
|
||
|
||
// populate missing id
|
||
if(typeof e.id === 'undefined') {
|
||
if(e.name && e.name in pki.oids) {
|
||
e.id = pki.oids[e.name];
|
||
} else {
|
||
var error = new Error('Extension ID not specified.');
|
||
error.extension = e;
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
if(typeof e.value !== 'undefined') {
|
||
return e;
|
||
}
|
||
|
||
// handle missing value:
|
||
|
||
// value is a BIT STRING
|
||
if(e.name === 'keyUsage') {
|
||
// build flags
|
||
var unused = 0;
|
||
var b2 = 0x00;
|
||
var b3 = 0x00;
|
||
if(e.digitalSignature) {
|
||
b2 |= 0x80;
|
||
unused = 7;
|
||
}
|
||
if(e.nonRepudiation) {
|
||
b2 |= 0x40;
|
||
unused = 6;
|
||
}
|
||
if(e.keyEncipherment) {
|
||
b2 |= 0x20;
|
||
unused = 5;
|
||
}
|
||
if(e.dataEncipherment) {
|
||
b2 |= 0x10;
|
||
unused = 4;
|
||
}
|
||
if(e.keyAgreement) {
|
||
b2 |= 0x08;
|
||
unused = 3;
|
||
}
|
||
if(e.keyCertSign) {
|
||
b2 |= 0x04;
|
||
unused = 2;
|
||
}
|
||
if(e.cRLSign) {
|
||
b2 |= 0x02;
|
||
unused = 1;
|
||
}
|
||
if(e.encipherOnly) {
|
||
b2 |= 0x01;
|
||
unused = 0;
|
||
}
|
||
if(e.decipherOnly) {
|
||
b3 |= 0x80;
|
||
unused = 7;
|
||
}
|
||
|
||
// create bit string
|
||
var value = String.fromCharCode(unused);
|
||
if(b3 !== 0) {
|
||
value += String.fromCharCode(b2) + String.fromCharCode(b3);
|
||
} else if(b2 !== 0) {
|
||
value += String.fromCharCode(b2);
|
||
}
|
||
e.value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
|
||
} else if(e.name === 'basicConstraints') {
|
||
// basicConstraints is a SEQUENCE
|
||
e.value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
// cA BOOLEAN flag defaults to false
|
||
if(e.cA) {
|
||
e.value.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
|
||
String.fromCharCode(0xFF)));
|
||
}
|
||
if('pathLenConstraint' in e) {
|
||
e.value.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(e.pathLenConstraint).getBytes()));
|
||
}
|
||
} else if(e.name === 'extKeyUsage') {
|
||
// extKeyUsage is a SEQUENCE of OIDs
|
||
e.value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
var seq = e.value.value;
|
||
for(var key in e) {
|
||
if(e[key] !== true) {
|
||
continue;
|
||
}
|
||
// key is name in OID map
|
||
if(key in oids) {
|
||
seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
|
||
false, asn1.oidToDer(oids[key]).getBytes()));
|
||
} else if(key.indexOf('.') !== -1) {
|
||
// assume key is an OID
|
||
seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
|
||
false, asn1.oidToDer(key).getBytes()));
|
||
}
|
||
}
|
||
} else if(e.name === 'nsCertType') {
|
||
// nsCertType is a BIT STRING
|
||
// build flags
|
||
var unused = 0;
|
||
var b2 = 0x00;
|
||
|
||
if(e.client) {
|
||
b2 |= 0x80;
|
||
unused = 7;
|
||
}
|
||
if(e.server) {
|
||
b2 |= 0x40;
|
||
unused = 6;
|
||
}
|
||
if(e.email) {
|
||
b2 |= 0x20;
|
||
unused = 5;
|
||
}
|
||
if(e.objsign) {
|
||
b2 |= 0x10;
|
||
unused = 4;
|
||
}
|
||
if(e.reserved) {
|
||
b2 |= 0x08;
|
||
unused = 3;
|
||
}
|
||
if(e.sslCA) {
|
||
b2 |= 0x04;
|
||
unused = 2;
|
||
}
|
||
if(e.emailCA) {
|
||
b2 |= 0x02;
|
||
unused = 1;
|
||
}
|
||
if(e.objCA) {
|
||
b2 |= 0x01;
|
||
unused = 0;
|
||
}
|
||
|
||
// create bit string
|
||
var value = String.fromCharCode(unused);
|
||
if(b2 !== 0) {
|
||
value += String.fromCharCode(b2);
|
||
}
|
||
e.value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
|
||
} else if(e.name === 'subjectAltName' || e.name === 'issuerAltName') {
|
||
// SYNTAX SEQUENCE
|
||
e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
|
||
var altName;
|
||
for(var n = 0; n < e.altNames.length; ++n) {
|
||
altName = e.altNames[n];
|
||
var value = altName.value;
|
||
// handle IP
|
||
if(altName.type === 7 && altName.ip) {
|
||
value = forge.util.bytesFromIP(altName.ip);
|
||
if(value === null) {
|
||
var error = new Error(
|
||
'Extension "ip" value is not a valid IPv4 or IPv6 address.');
|
||
error.extension = e;
|
||
throw error;
|
||
}
|
||
} else if(altName.type === 8) {
|
||
// handle OID
|
||
if(altName.oid) {
|
||
value = asn1.oidToDer(asn1.oidToDer(altName.oid));
|
||
} else {
|
||
// deprecated ... convert value to OID
|
||
value = asn1.oidToDer(value);
|
||
}
|
||
}
|
||
e.value.value.push(asn1.create(
|
||
asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
|
||
value));
|
||
}
|
||
} else if(e.name === 'subjectKeyIdentifier' && options.cert) {
|
||
var ski = options.cert.generateSubjectKeyIdentifier();
|
||
e.subjectKeyIdentifier = ski.toHex();
|
||
// OCTETSTRING w/digest
|
||
e.value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ski.getBytes());
|
||
}
|
||
|
||
// ensure value has been defined by now
|
||
if(typeof e.value === 'undefined') {
|
||
var error = new Error('Extension value not specified.');
|
||
error.extension = e;
|
||
throw error;
|
||
}
|
||
|
||
return e;
|
||
}
|
||
|
||
/**
|
||
* Convert signature parameters object to ASN.1
|
||
*
|
||
* @param {String} oid Signature algorithm OID
|
||
* @param params The signature parametrs object
|
||
* @return ASN.1 object representing signature parameters
|
||
*/
|
||
function _signatureParametersToAsn1(oid, params) {
|
||
switch(oid) {
|
||
case oids['RSASSA-PSS']:
|
||
var parts = [];
|
||
|
||
if(params.hash.algorithmOid !== undefined) {
|
||
parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(params.hash.algorithmOid).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
])
|
||
]));
|
||
}
|
||
|
||
if(params.mgf.algorithmOid !== undefined) {
|
||
parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(params.mgf.algorithmOid).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
])
|
||
])
|
||
]));
|
||
}
|
||
|
||
if(params.saltLength !== undefined) {
|
||
parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(params.saltLength).getBytes())
|
||
]));
|
||
}
|
||
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts);
|
||
|
||
default:
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Converts a certification request's attributes to an ASN.1 set of
|
||
* CRIAttributes.
|
||
*
|
||
* @param csr certification request.
|
||
*
|
||
* @return the ASN.1 set of CRIAttributes.
|
||
*/
|
||
function _CRIAttributesToAsn1(csr) {
|
||
// create an empty context-specific container
|
||
var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
|
||
|
||
// no attributes, return empty container
|
||
if(csr.attributes.length === 0) {
|
||
return rval;
|
||
}
|
||
|
||
// each attribute has a sequence with a type and a set of values
|
||
var attrs = csr.attributes;
|
||
for(var i = 0; i < attrs.length; ++i) {
|
||
var attr = attrs[i];
|
||
var value = attr.value;
|
||
|
||
// reuse tag class for attribute value if available
|
||
var valueTagClass = asn1.Type.UTF8;
|
||
if('valueTagClass' in attr) {
|
||
valueTagClass = attr.valueTagClass;
|
||
}
|
||
if(valueTagClass === asn1.Type.UTF8) {
|
||
value = forge.util.encodeUtf8(value);
|
||
}
|
||
var valueConstructed = false;
|
||
if('valueConstructed' in attr) {
|
||
valueConstructed = attr.valueConstructed;
|
||
}
|
||
// FIXME: handle more encodings
|
||
|
||
// create a RelativeDistinguishedName set
|
||
// each value in the set is an AttributeTypeAndValue first
|
||
// containing the type (an OID) and second the value
|
||
var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// AttributeType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(attr.type).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||
// AttributeValue
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, valueTagClass, valueConstructed, value)
|
||
])
|
||
]);
|
||
rval.value.push(seq);
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
|
||
*
|
||
* @param cert the certificate.
|
||
*
|
||
* @return the asn1 TBSCertificate.
|
||
*/
|
||
pki.getTBSCertificate = function(cert) {
|
||
// TBSCertificate
|
||
var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
// integer
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(cert.version).getBytes())
|
||
]),
|
||
// serialNumber
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
forge.util.hexToBytes(cert.serialNumber)),
|
||
// signature
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()),
|
||
// parameters
|
||
_signatureParametersToAsn1(
|
||
cert.siginfo.algorithmOid, cert.siginfo.parameters)
|
||
]),
|
||
// issuer
|
||
_dnToAsn1(cert.issuer),
|
||
// validity
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// notBefore
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
|
||
asn1.dateToUtcTime(cert.validity.notBefore)),
|
||
// notAfter
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
|
||
asn1.dateToUtcTime(cert.validity.notAfter))
|
||
]),
|
||
// subject
|
||
_dnToAsn1(cert.subject),
|
||
// SubjectPublicKeyInfo
|
||
pki.publicKeyToAsn1(cert.publicKey)
|
||
]);
|
||
|
||
if(cert.issuer.uniqueId) {
|
||
// issuerUniqueID (optional)
|
||
tbs.value.push(
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
|
||
String.fromCharCode(0x00) +
|
||
cert.issuer.uniqueId
|
||
)
|
||
])
|
||
);
|
||
}
|
||
if(cert.subject.uniqueId) {
|
||
// subjectUniqueID (optional)
|
||
tbs.value.push(
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
|
||
String.fromCharCode(0x00) +
|
||
cert.subject.uniqueId
|
||
)
|
||
])
|
||
);
|
||
}
|
||
|
||
if(cert.extensions.length > 0) {
|
||
// extensions (optional)
|
||
tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions));
|
||
}
|
||
|
||
return tbs;
|
||
};
|
||
|
||
/**
|
||
* Gets the ASN.1 CertificationRequestInfo part of a
|
||
* PKCS#10 CertificationRequest.
|
||
*
|
||
* @param csr the certification request.
|
||
*
|
||
* @return the asn1 CertificationRequestInfo.
|
||
*/
|
||
pki.getCertificationRequestInfo = function(csr) {
|
||
// CertificationRequestInfo
|
||
var cri = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(csr.version).getBytes()),
|
||
// subject
|
||
_dnToAsn1(csr.subject),
|
||
// SubjectPublicKeyInfo
|
||
pki.publicKeyToAsn1(csr.publicKey),
|
||
// attributes
|
||
_CRIAttributesToAsn1(csr)
|
||
]);
|
||
|
||
return cri;
|
||
};
|
||
|
||
/**
|
||
* Converts a DistinguishedName (subject or issuer) to an ASN.1 object.
|
||
*
|
||
* @param dn the DistinguishedName.
|
||
*
|
||
* @return the asn1 representation of a DistinguishedName.
|
||
*/
|
||
pki.distinguishedNameToAsn1 = function(dn) {
|
||
return _dnToAsn1(dn);
|
||
};
|
||
|
||
/**
|
||
* Converts an X.509v3 RSA certificate to an ASN.1 object.
|
||
*
|
||
* @param cert the certificate.
|
||
*
|
||
* @return the asn1 representation of an X.509v3 RSA certificate.
|
||
*/
|
||
pki.certificateToAsn1 = function(cert) {
|
||
// prefer cached TBSCertificate over generating one
|
||
var tbsCertificate = cert.tbsCertificate || pki.getTBSCertificate(cert);
|
||
|
||
// Certificate
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// TBSCertificate
|
||
tbsCertificate,
|
||
// AlgorithmIdentifier (signature algorithm)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(cert.signatureOid).getBytes()),
|
||
// parameters
|
||
_signatureParametersToAsn1(cert.signatureOid, cert.signatureParameters)
|
||
]),
|
||
// SignatureValue
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
|
||
String.fromCharCode(0x00) + cert.signature)
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Converts X.509v3 certificate extensions to ASN.1.
|
||
*
|
||
* @param exts the extensions to convert.
|
||
*
|
||
* @return the extensions in ASN.1 format.
|
||
*/
|
||
pki.certificateExtensionsToAsn1 = function(exts) {
|
||
// create top-level extension container
|
||
var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, []);
|
||
|
||
// create extension sequence (stores a sequence for each extension)
|
||
var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
rval.value.push(seq);
|
||
|
||
for(var i = 0; i < exts.length; ++i) {
|
||
seq.value.push(pki.certificateExtensionToAsn1(exts[i]));
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Converts a single certificate extension to ASN.1.
|
||
*
|
||
* @param ext the extension to convert.
|
||
*
|
||
* @return the extension in ASN.1 format.
|
||
*/
|
||
pki.certificateExtensionToAsn1 = function(ext) {
|
||
// create a sequence for each extension
|
||
var extseq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
|
||
|
||
// extnID (OID)
|
||
extseq.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(ext.id).getBytes()));
|
||
|
||
// critical defaults to false
|
||
if(ext.critical) {
|
||
// critical BOOLEAN DEFAULT FALSE
|
||
extseq.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
|
||
String.fromCharCode(0xFF)));
|
||
}
|
||
|
||
var value = ext.value;
|
||
if(typeof ext.value !== 'string') {
|
||
// value is asn.1
|
||
value = asn1.toDer(value).getBytes();
|
||
}
|
||
|
||
// extnValue (OCTET STRING)
|
||
extseq.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, value));
|
||
|
||
return extseq;
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#10 certification request to an ASN.1 object.
|
||
*
|
||
* @param csr the certification request.
|
||
*
|
||
* @return the asn1 representation of a certification request.
|
||
*/
|
||
pki.certificationRequestToAsn1 = function(csr) {
|
||
// prefer cached CertificationRequestInfo over generating one
|
||
var cri = csr.certificationRequestInfo ||
|
||
pki.getCertificationRequestInfo(csr);
|
||
|
||
// Certificate
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// CertificationRequestInfo
|
||
cri,
|
||
// AlgorithmIdentifier (signature algorithm)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(csr.signatureOid).getBytes()),
|
||
// parameters
|
||
_signatureParametersToAsn1(csr.signatureOid, csr.signatureParameters)
|
||
]),
|
||
// signature
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
|
||
String.fromCharCode(0x00) + csr.signature)
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Creates a CA store.
|
||
*
|
||
* @param certs an optional array of certificate objects or PEM-formatted
|
||
* certificate strings to add to the CA store.
|
||
*
|
||
* @return the CA store.
|
||
*/
|
||
pki.createCaStore = function(certs) {
|
||
// create CA store
|
||
var caStore = {
|
||
// stored certificates
|
||
certs: {}
|
||
};
|
||
|
||
/**
|
||
* Gets the certificate that issued the passed certificate or its
|
||
* 'parent'.
|
||
*
|
||
* @param cert the certificate to get the parent for.
|
||
*
|
||
* @return the parent certificate or null if none was found.
|
||
*/
|
||
caStore.getIssuer = function(cert) {
|
||
var rval = getBySubject(cert.issuer);
|
||
|
||
// see if there are multiple matches
|
||
/*if(forge.util.isArray(rval)) {
|
||
// TODO: resolve multiple matches by checking
|
||
// authorityKey/subjectKey/issuerUniqueID/other identifiers, etc.
|
||
// FIXME: or alternatively do authority key mapping
|
||
// if possible (X.509v1 certs can't work?)
|
||
throw new Error('Resolving multiple issuer matches not implemented yet.');
|
||
}*/
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Adds a trusted certificate to the store.
|
||
*
|
||
* @param cert the certificate to add as a trusted certificate (either a
|
||
* pki.certificate object or a PEM-formatted certificate).
|
||
*/
|
||
caStore.addCertificate = function(cert) {
|
||
// convert from pem if necessary
|
||
if(typeof cert === 'string') {
|
||
cert = forge.pki.certificateFromPem(cert);
|
||
}
|
||
|
||
// produce subject hash if it doesn't exist
|
||
if(!cert.subject.hash) {
|
||
var md = forge.md.sha1.create();
|
||
cert.subject.attributes = pki.RDNAttributesAsArray(
|
||
_dnToAsn1(cert.subject), md);
|
||
cert.subject.hash = md.digest().toHex();
|
||
}
|
||
|
||
if(cert.subject.hash in caStore.certs) {
|
||
// subject hash already exists, append to array
|
||
var tmp = caStore.certs[cert.subject.hash];
|
||
if(!forge.util.isArray(tmp)) {
|
||
tmp = [tmp];
|
||
}
|
||
tmp.push(cert);
|
||
} else {
|
||
caStore.certs[cert.subject.hash] = cert;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Checks to see if the given certificate is in the store.
|
||
*
|
||
* @param cert the certificate to check.
|
||
*
|
||
* @return true if the certificate is in the store, false if not.
|
||
*/
|
||
caStore.hasCertificate = function(cert) {
|
||
var match = getBySubject(cert.subject);
|
||
if(!match) {
|
||
return false;
|
||
}
|
||
if(!forge.util.isArray(match)) {
|
||
match = [match];
|
||
}
|
||
// compare DER-encoding of certificates
|
||
var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
|
||
for(var i = 0; i < match.length; ++i) {
|
||
var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
|
||
if(der1 === der2) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
function getBySubject(subject) {
|
||
// produce subject hash if it doesn't exist
|
||
if(!subject.hash) {
|
||
var md = forge.md.sha1.create();
|
||
subject.attributes = pki.RDNAttributesAsArray(_dnToAsn1(subject), md);
|
||
subject.hash = md.digest().toHex();
|
||
}
|
||
return caStore.certs[subject.hash] || null;
|
||
}
|
||
|
||
// auto-add passed in certs
|
||
if(certs) {
|
||
// parse PEM-formatted certificates as necessary
|
||
for(var i = 0; i < certs.length; ++i) {
|
||
var cert = certs[i];
|
||
caStore.addCertificate(cert);
|
||
}
|
||
}
|
||
|
||
return caStore;
|
||
};
|
||
|
||
/**
|
||
* Certificate verification errors, based on TLS.
|
||
*/
|
||
pki.certificateError = {
|
||
bad_certificate: 'forge.pki.BadCertificate',
|
||
unsupported_certificate: 'forge.pki.UnsupportedCertificate',
|
||
certificate_revoked: 'forge.pki.CertificateRevoked',
|
||
certificate_expired: 'forge.pki.CertificateExpired',
|
||
certificate_unknown: 'forge.pki.CertificateUnknown',
|
||
unknown_ca: 'forge.pki.UnknownCertificateAuthority'
|
||
};
|
||
|
||
/**
|
||
* Verifies a certificate chain against the given Certificate Authority store
|
||
* with an optional custom verify callback.
|
||
*
|
||
* @param caStore a certificate store to verify against.
|
||
* @param chain the certificate chain to verify, with the root or highest
|
||
* authority at the end (an array of certificates).
|
||
* @param verify called for every certificate in the chain.
|
||
*
|
||
* The verify callback has the following signature:
|
||
*
|
||
* verified - Set to true if certificate was verified, otherwise the
|
||
* pki.certificateError for why the certificate failed.
|
||
* depth - The current index in the chain, where 0 is the end point's cert.
|
||
* certs - The certificate chain, *NOTE* an empty chain indicates an anonymous
|
||
* end point.
|
||
*
|
||
* The function returns true on success and on failure either the appropriate
|
||
* pki.certificateError or an object with 'error' set to the appropriate
|
||
* pki.certificateError and 'message' set to a custom error message.
|
||
*
|
||
* @return true if successful, error thrown if not.
|
||
*/
|
||
pki.verifyCertificateChain = function(caStore, chain, verify) {
|
||
/* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate
|
||
Section 6: Certification Path Validation
|
||
See inline parentheticals related to this particular implementation.
|
||
|
||
The primary goal of path validation is to verify the binding between
|
||
a subject distinguished name or a subject alternative name and subject
|
||
public key, as represented in the end entity certificate, based on the
|
||
public key of the trust anchor. This requires obtaining a sequence of
|
||
certificates that support that binding. That sequence should be provided
|
||
in the passed 'chain'. The trust anchor should be in the given CA
|
||
store. The 'end entity' certificate is the certificate provided by the
|
||
end point (typically a server) and is the first in the chain.
|
||
|
||
To meet this goal, the path validation process verifies, among other
|
||
things, that a prospective certification path (a sequence of n
|
||
certificates or a 'chain') satisfies the following conditions:
|
||
|
||
(a) for all x in {1, ..., n-1}, the subject of certificate x is
|
||
the issuer of certificate x+1;
|
||
|
||
(b) certificate 1 is issued by the trust anchor;
|
||
|
||
(c) certificate n is the certificate to be validated; and
|
||
|
||
(d) for all x in {1, ..., n}, the certificate was valid at the
|
||
time in question.
|
||
|
||
Note that here 'n' is index 0 in the chain and 1 is the last certificate
|
||
in the chain and it must be signed by a certificate in the connection's
|
||
CA store.
|
||
|
||
The path validation process also determines the set of certificate
|
||
policies that are valid for this path, based on the certificate policies
|
||
extension, policy mapping extension, policy constraints extension, and
|
||
inhibit any-policy extension.
|
||
|
||
Note: Policy mapping extension not supported (Not Required).
|
||
|
||
Note: If the certificate has an unsupported critical extension, then it
|
||
must be rejected.
|
||
|
||
Note: A certificate is self-issued if the DNs that appear in the subject
|
||
and issuer fields are identical and are not empty.
|
||
|
||
The path validation algorithm assumes the following seven inputs are
|
||
provided to the path processing logic. What this specific implementation
|
||
will use is provided parenthetically:
|
||
|
||
(a) a prospective certification path of length n (the 'chain')
|
||
(b) the current date/time: ('now').
|
||
(c) user-initial-policy-set: A set of certificate policy identifiers
|
||
naming the policies that are acceptable to the certificate user.
|
||
The user-initial-policy-set contains the special value any-policy
|
||
if the user is not concerned about certificate policy
|
||
(Not implemented. Any policy is accepted).
|
||
(d) trust anchor information, describing a CA that serves as a trust
|
||
anchor for the certification path. The trust anchor information
|
||
includes:
|
||
|
||
(1) the trusted issuer name,
|
||
(2) the trusted public key algorithm,
|
||
(3) the trusted public key, and
|
||
(4) optionally, the trusted public key parameters associated
|
||
with the public key.
|
||
|
||
(Trust anchors are provided via certificates in the CA store).
|
||
|
||
The trust anchor information may be provided to the path processing
|
||
procedure in the form of a self-signed certificate. The trusted anchor
|
||
information is trusted because it was delivered to the path processing
|
||
procedure by some trustworthy out-of-band procedure. If the trusted
|
||
public key algorithm requires parameters, then the parameters are
|
||
provided along with the trusted public key (No parameters used in this
|
||
implementation).
|
||
|
||
(e) initial-policy-mapping-inhibit, which indicates if policy mapping is
|
||
allowed in the certification path.
|
||
(Not implemented, no policy checking)
|
||
|
||
(f) initial-explicit-policy, which indicates if the path must be valid
|
||
for at least one of the certificate policies in the user-initial-
|
||
policy-set.
|
||
(Not implemented, no policy checking)
|
||
|
||
(g) initial-any-policy-inhibit, which indicates whether the
|
||
anyPolicy OID should be processed if it is included in a
|
||
certificate.
|
||
(Not implemented, so any policy is valid provided that it is
|
||
not marked as critical) */
|
||
|
||
/* Basic Path Processing:
|
||
|
||
For each certificate in the 'chain', the following is checked:
|
||
|
||
1. The certificate validity period includes the current time.
|
||
2. The certificate was signed by its parent (where the parent is either
|
||
the next in the chain or from the CA store). Allow processing to
|
||
continue to the next step if no parent is found but the certificate is
|
||
in the CA store.
|
||
3. TODO: The certificate has not been revoked.
|
||
4. The certificate issuer name matches the parent's subject name.
|
||
5. TODO: If the certificate is self-issued and not the final certificate
|
||
in the chain, skip this step, otherwise verify that the subject name
|
||
is within one of the permitted subtrees of X.500 distinguished names
|
||
and that each of the alternative names in the subjectAltName extension
|
||
(critical or non-critical) is within one of the permitted subtrees for
|
||
that name type.
|
||
6. TODO: If the certificate is self-issued and not the final certificate
|
||
in the chain, skip this step, otherwise verify that the subject name
|
||
is not within one of the excluded subtrees for X.500 distinguished
|
||
names and none of the subjectAltName extension names are excluded for
|
||
that name type.
|
||
7. The other steps in the algorithm for basic path processing involve
|
||
handling the policy extension which is not presently supported in this
|
||
implementation. Instead, if a critical policy extension is found, the
|
||
certificate is rejected as not supported.
|
||
8. If the certificate is not the first or if its the only certificate in
|
||
the chain (having no parent from the CA store or is self-signed) and it
|
||
has a critical key usage extension, verify that the keyCertSign bit is
|
||
set. If the key usage extension exists, verify that the basic
|
||
constraints extension exists. If the basic constraints extension exists,
|
||
verify that the cA flag is set. If pathLenConstraint is set, ensure that
|
||
the number of certificates that precede in the chain (come earlier
|
||
in the chain as implemented below), excluding the very first in the
|
||
chain (typically the end-entity one), isn't greater than the
|
||
pathLenConstraint. This constraint limits the number of intermediate
|
||
CAs that may appear below a CA before only end-entity certificates
|
||
may be issued. */
|
||
|
||
// copy cert chain references to another array to protect against changes
|
||
// in verify callback
|
||
chain = chain.slice(0);
|
||
var certs = chain.slice(0);
|
||
|
||
// get current date
|
||
var now = new Date();
|
||
|
||
// verify each cert in the chain using its parent, where the parent
|
||
// is either the next in the chain or from the CA store
|
||
var first = true;
|
||
var error = null;
|
||
var depth = 0;
|
||
do {
|
||
var cert = chain.shift();
|
||
var parent = null;
|
||
var selfSigned = false;
|
||
|
||
// 1. check valid time
|
||
if(now < cert.validity.notBefore || now > cert.validity.notAfter) {
|
||
error = {
|
||
message: 'Certificate is not valid yet or has expired.',
|
||
error: pki.certificateError.certificate_expired,
|
||
notBefore: cert.validity.notBefore,
|
||
notAfter: cert.validity.notAfter,
|
||
now: now
|
||
};
|
||
}
|
||
|
||
// 2. verify with parent from chain or CA store
|
||
if(error === null) {
|
||
parent = chain[0] || caStore.getIssuer(cert);
|
||
if(parent === null) {
|
||
// check for self-signed cert
|
||
if(cert.isIssuer(cert)) {
|
||
selfSigned = true;
|
||
parent = cert;
|
||
}
|
||
}
|
||
|
||
if(parent) {
|
||
// FIXME: current CA store implementation might have multiple
|
||
// certificates where the issuer can't be determined from the
|
||
// certificate (happens rarely with, eg: old certificates) so normalize
|
||
// by always putting parents into an array
|
||
// TODO: there's may be an extreme degenerate case currently uncovered
|
||
// where an old intermediate certificate seems to have a matching parent
|
||
// but none of the parents actually verify ... but the intermediate
|
||
// is in the CA and it should pass this check; needs investigation
|
||
var parents = parent;
|
||
if(!forge.util.isArray(parents)) {
|
||
parents = [parents];
|
||
}
|
||
|
||
// try to verify with each possible parent (typically only one)
|
||
var verified = false;
|
||
while(!verified && parents.length > 0) {
|
||
parent = parents.shift();
|
||
try {
|
||
verified = parent.verify(cert);
|
||
} catch(ex) {
|
||
// failure to verify, don't care why, try next one
|
||
}
|
||
}
|
||
|
||
if(!verified) {
|
||
error = {
|
||
message: 'Certificate signature is invalid.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
}
|
||
|
||
if(error === null && (!parent || selfSigned) &&
|
||
!caStore.hasCertificate(cert)) {
|
||
// no parent issuer and certificate itself is not trusted
|
||
error = {
|
||
message: 'Certificate is not trusted.',
|
||
error: pki.certificateError.unknown_ca
|
||
};
|
||
}
|
||
}
|
||
|
||
// TODO: 3. check revoked
|
||
|
||
// 4. check for matching issuer/subject
|
||
if(error === null && parent && !cert.isIssuer(parent)) {
|
||
// parent is not issuer
|
||
error = {
|
||
message: 'Certificate issuer is invalid.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
|
||
// 5. TODO: check names with permitted names tree
|
||
|
||
// 6. TODO: check names against excluded names tree
|
||
|
||
// 7. check for unsupported critical extensions
|
||
if(error === null) {
|
||
// supported extensions
|
||
var se = {
|
||
keyUsage: true,
|
||
basicConstraints: true
|
||
};
|
||
for(var i = 0; error === null && i < cert.extensions.length; ++i) {
|
||
var ext = cert.extensions[i];
|
||
if(ext.critical && !(ext.name in se)) {
|
||
error = {
|
||
message:
|
||
'Certificate has an unsupported critical extension.',
|
||
error: pki.certificateError.unsupported_certificate
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// 8. check for CA if cert is not first or is the only certificate
|
||
// remaining in chain with no parent or is self-signed
|
||
if(error === null &&
|
||
(!first || (chain.length === 0 && (!parent || selfSigned)))) {
|
||
// first check keyUsage extension and then basic constraints
|
||
var bcExt = cert.getExtension('basicConstraints');
|
||
var keyUsageExt = cert.getExtension('keyUsage');
|
||
if(keyUsageExt !== null) {
|
||
// keyCertSign must be true and there must be a basic
|
||
// constraints extension
|
||
if(!keyUsageExt.keyCertSign || bcExt === null) {
|
||
// bad certificate
|
||
error = {
|
||
message:
|
||
'Certificate keyUsage or basicConstraints conflict ' +
|
||
'or indicate that the certificate is not a CA. ' +
|
||
'If the certificate is the only one in the chain or ' +
|
||
'isn\'t the first then the certificate must be a ' +
|
||
'valid CA.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
}
|
||
// basic constraints cA flag must be set
|
||
if(error === null && bcExt !== null && !bcExt.cA) {
|
||
// bad certificate
|
||
error = {
|
||
message:
|
||
'Certificate basicConstraints indicates the certificate ' +
|
||
'is not a CA.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
// if error is not null and keyUsage is available, then we know it
|
||
// has keyCertSign and there is a basic constraints extension too,
|
||
// which means we can check pathLenConstraint (if it exists)
|
||
if(error === null && keyUsageExt !== null &&
|
||
'pathLenConstraint' in bcExt) {
|
||
// pathLen is the maximum # of intermediate CA certs that can be
|
||
// found between the current certificate and the end-entity (depth 0)
|
||
// certificate; this number does not include the end-entity (depth 0,
|
||
// last in the chain) even if it happens to be a CA certificate itself
|
||
var pathLen = depth - 1;
|
||
if(pathLen > bcExt.pathLenConstraint) {
|
||
// pathLenConstraint violated, bad certificate
|
||
error = {
|
||
message:
|
||
'Certificate basicConstraints pathLenConstraint violated.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// call application callback
|
||
var vfd = (error === null) ? true : error.error;
|
||
var ret = verify ? verify(vfd, depth, certs) : vfd;
|
||
if(ret === true) {
|
||
// clear any set error
|
||
error = null;
|
||
} else {
|
||
// if passed basic tests, set default message and alert
|
||
if(vfd === true) {
|
||
error = {
|
||
message: 'The application rejected the certificate.',
|
||
error: pki.certificateError.bad_certificate
|
||
};
|
||
}
|
||
|
||
// check for custom error info
|
||
if(ret || ret === 0) {
|
||
// set custom message and error
|
||
if(typeof ret === 'object' && !forge.util.isArray(ret)) {
|
||
if(ret.message) {
|
||
error.message = ret.message;
|
||
}
|
||
if(ret.error) {
|
||
error.error = ret.error;
|
||
}
|
||
} else if(typeof ret === 'string') {
|
||
// set custom error
|
||
error.error = ret;
|
||
}
|
||
}
|
||
|
||
// throw error
|
||
throw error;
|
||
}
|
||
|
||
// no longer first cert in chain
|
||
first = false;
|
||
++depth;
|
||
} while(chain.length > 0);
|
||
|
||
return true;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'x509';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge.pki;
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/x509',[
|
||
'require',
|
||
'module',
|
||
'./aes',
|
||
'./asn1',
|
||
'./des',
|
||
'./md',
|
||
'./mgf',
|
||
'./oids',
|
||
'./pem',
|
||
'./pss',
|
||
'./rsa',
|
||
'./util'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of PKCS#12.
|
||
*
|
||
* @author Dave Longley
|
||
* @author Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
*
|
||
* The ASN.1 representation of PKCS#12 is as follows
|
||
* (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details)
|
||
*
|
||
* PFX ::= SEQUENCE {
|
||
* version INTEGER {v3(3)}(v3,...),
|
||
* authSafe ContentInfo,
|
||
* macData MacData OPTIONAL
|
||
* }
|
||
*
|
||
* MacData ::= SEQUENCE {
|
||
* mac DigestInfo,
|
||
* macSalt OCTET STRING,
|
||
* iterations INTEGER DEFAULT 1
|
||
* }
|
||
* Note: The iterations default is for historical reasons and its use is
|
||
* deprecated. A higher value, like 1024, is recommended.
|
||
*
|
||
* DigestInfo is defined in PKCS#7 as follows:
|
||
*
|
||
* DigestInfo ::= SEQUENCE {
|
||
* digestAlgorithm DigestAlgorithmIdentifier,
|
||
* digest Digest
|
||
* }
|
||
*
|
||
* DigestAlgorithmIdentifier ::= AlgorithmIdentifier
|
||
*
|
||
* The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
|
||
* for the algorithm, if any. In the case of SHA1 there is none.
|
||
*
|
||
* AlgorithmIdentifer ::= SEQUENCE {
|
||
* algorithm OBJECT IDENTIFIER,
|
||
* parameters ANY DEFINED BY algorithm OPTIONAL
|
||
* }
|
||
*
|
||
* Digest ::= OCTET STRING
|
||
*
|
||
*
|
||
* ContentInfo ::= SEQUENCE {
|
||
* contentType ContentType,
|
||
* content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
|
||
* }
|
||
*
|
||
* ContentType ::= OBJECT IDENTIFIER
|
||
*
|
||
* AuthenticatedSafe ::= SEQUENCE OF ContentInfo
|
||
* -- Data if unencrypted
|
||
* -- EncryptedData if password-encrypted
|
||
* -- EnvelopedData if public key-encrypted
|
||
*
|
||
*
|
||
* SafeContents ::= SEQUENCE OF SafeBag
|
||
*
|
||
* SafeBag ::= SEQUENCE {
|
||
* bagId BAG-TYPE.&id ({PKCS12BagSet})
|
||
* bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
|
||
* bagAttributes SET OF PKCS12Attribute OPTIONAL
|
||
* }
|
||
*
|
||
* PKCS12Attribute ::= SEQUENCE {
|
||
* attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
|
||
* attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
|
||
* } -- This type is compatible with the X.500 type ’Attribute’
|
||
*
|
||
* PKCS12AttrSet ATTRIBUTE ::= {
|
||
* friendlyName | -- from PKCS #9
|
||
* localKeyId, -- from PKCS #9
|
||
* ... -- Other attributes are allowed
|
||
* }
|
||
*
|
||
* CertBag ::= SEQUENCE {
|
||
* certId BAG-TYPE.&id ({CertTypes}),
|
||
* certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
|
||
* }
|
||
*
|
||
* x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}}
|
||
* -- DER-encoded X.509 certificate stored in OCTET STRING
|
||
*
|
||
* sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}}
|
||
* -- Base64-encoded SDSI certificate stored in IA5String
|
||
*
|
||
* CertTypes BAG-TYPE ::= {
|
||
* x509Certificate |
|
||
* sdsiCertificate,
|
||
* ... -- For future extensions
|
||
* }
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for asn.1 & PKI API
|
||
var asn1 = forge.asn1;
|
||
var pki = forge.pki;
|
||
|
||
// shortcut for PKCS#12 API
|
||
var p12 = forge.pkcs12 = forge.pkcs12 || {};
|
||
|
||
var contentInfoValidator = {
|
||
name: 'ContentInfo',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE, // a ContentInfo
|
||
constructed: true,
|
||
value: [{
|
||
name: 'ContentInfo.contentType',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'contentType'
|
||
}, {
|
||
name: 'ContentInfo.content',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
constructed: true,
|
||
captureAsn1: 'content'
|
||
}]
|
||
};
|
||
|
||
var pfxValidator = {
|
||
name: 'PFX',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PFX.version',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
capture: 'version'
|
||
},
|
||
contentInfoValidator, {
|
||
name: 'PFX.macData',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
optional: true,
|
||
captureAsn1: 'mac',
|
||
value: [{
|
||
name: 'PFX.macData.mac',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE, // DigestInfo
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PFX.macData.mac.digestAlgorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE, // DigestAlgorithmIdentifier
|
||
constructed: true,
|
||
value: [{
|
||
name: 'PFX.macData.mac.digestAlgorithm.algorithm',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'macAlgorithm'
|
||
}, {
|
||
name: 'PFX.macData.mac.digestAlgorithm.parameters',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
captureAsn1: 'macAlgorithmParameters'
|
||
}]
|
||
}, {
|
||
name: 'PFX.macData.mac.digest',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'macDigest'
|
||
}]
|
||
}, {
|
||
name: 'PFX.macData.macSalt',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'macSalt'
|
||
}, {
|
||
name: 'PFX.macData.iterations',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.INTEGER,
|
||
constructed: false,
|
||
optional: true,
|
||
capture: 'macIterations'
|
||
}]
|
||
}]
|
||
};
|
||
|
||
var safeBagValidator = {
|
||
name: 'SafeBag',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'SafeBag.bagId',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'bagId'
|
||
}, {
|
||
name: 'SafeBag.bagValue',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
constructed: true,
|
||
captureAsn1: 'bagValue'
|
||
}, {
|
||
name: 'SafeBag.bagAttributes',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
constructed: true,
|
||
optional: true,
|
||
capture: 'bagAttributes'
|
||
}]
|
||
};
|
||
|
||
var attributeValidator = {
|
||
name: 'Attribute',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'Attribute.attrId',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'oid'
|
||
}, {
|
||
name: 'Attribute.attrValues',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SET,
|
||
constructed: true,
|
||
capture: 'values'
|
||
}]
|
||
};
|
||
|
||
var certBagValidator = {
|
||
name: 'CertBag',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.SEQUENCE,
|
||
constructed: true,
|
||
value: [{
|
||
name: 'CertBag.certId',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Type.OID,
|
||
constructed: false,
|
||
capture: 'certId'
|
||
}, {
|
||
name: 'CertBag.certValue',
|
||
tagClass: asn1.Class.CONTEXT_SPECIFIC,
|
||
constructed: true,
|
||
/* So far we only support X.509 certificates (which are wrapped in
|
||
an OCTET STRING, hence hard code that here). */
|
||
value: [{
|
||
name: 'CertBag.certValue[0]',
|
||
tagClass: asn1.Class.UNIVERSAL,
|
||
type: asn1.Class.OCTETSTRING,
|
||
constructed: false,
|
||
capture: 'cert'
|
||
}]
|
||
}]
|
||
};
|
||
|
||
/**
|
||
* Search SafeContents structure for bags with matching attributes.
|
||
*
|
||
* The search can optionally be narrowed by a certain bag type.
|
||
*
|
||
* @param safeContents the SafeContents structure to search in.
|
||
* @param attrName the name of the attribute to compare against.
|
||
* @param attrValue the attribute value to search for.
|
||
* @param [bagType] bag type to narrow search by.
|
||
*
|
||
* @return an array of matching bags.
|
||
*/
|
||
function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) {
|
||
var result = [];
|
||
|
||
for(var i = 0; i < safeContents.length; i ++) {
|
||
for(var j = 0; j < safeContents[i].safeBags.length; j ++) {
|
||
var bag = safeContents[i].safeBags[j];
|
||
if(bagType !== undefined && bag.type !== bagType) {
|
||
continue;
|
||
}
|
||
// only filter by bag type, no attribute specified
|
||
if(attrName === null) {
|
||
result.push(bag);
|
||
continue;
|
||
}
|
||
if(bag.attributes[attrName] !== undefined &&
|
||
bag.attributes[attrName].indexOf(attrValue) >= 0) {
|
||
result.push(bag);
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Converts a PKCS#12 PFX in ASN.1 notation into a PFX object.
|
||
*
|
||
* @param obj The PKCS#12 PFX in ASN.1 notation.
|
||
* @param strict true to use strict DER decoding, false not to (default: true).
|
||
* @param {String} password Password to decrypt with (optional).
|
||
*
|
||
* @return PKCS#12 PFX object.
|
||
*/
|
||
p12.pkcs12FromAsn1 = function(obj, strict, password) {
|
||
// handle args
|
||
if(typeof strict === 'string') {
|
||
password = strict;
|
||
strict = true;
|
||
} else if(strict === undefined) {
|
||
strict = true;
|
||
}
|
||
|
||
// validate PFX and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, pfxValidator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#12 PFX. ' +
|
||
'ASN.1 object is not an PKCS#12 PFX.');
|
||
error.errors = error;
|
||
throw error;
|
||
}
|
||
|
||
var pfx = {
|
||
version: capture.version.charCodeAt(0),
|
||
safeContents: [],
|
||
|
||
/**
|
||
* Gets bags with matching attributes.
|
||
*
|
||
* @param filter the attributes to filter by:
|
||
* [localKeyId] the localKeyId to search for.
|
||
* [localKeyIdHex] the localKeyId in hex to search for.
|
||
* [friendlyName] the friendly name to search for.
|
||
* [bagType] bag type to narrow each attribute search by.
|
||
*
|
||
* @return a map of attribute type to an array of matching bags or, if no
|
||
* attribute was given but a bag type, the map key will be the
|
||
* bag type.
|
||
*/
|
||
getBags: function(filter) {
|
||
var rval = {};
|
||
|
||
var localKeyId;
|
||
if('localKeyId' in filter) {
|
||
localKeyId = filter.localKeyId;
|
||
} else if('localKeyIdHex' in filter) {
|
||
localKeyId = forge.util.hexToBytes(filter.localKeyIdHex);
|
||
}
|
||
|
||
// filter on bagType only
|
||
if(localKeyId === undefined && !('friendlyName' in filter) &&
|
||
'bagType' in filter) {
|
||
rval[filter.bagType] = _getBagsByAttribute(
|
||
pfx.safeContents, null, null, filter.bagType);
|
||
}
|
||
|
||
if(localKeyId !== undefined) {
|
||
rval.localKeyId = _getBagsByAttribute(
|
||
pfx.safeContents, 'localKeyId',
|
||
localKeyId, filter.bagType);
|
||
}
|
||
if('friendlyName' in filter) {
|
||
rval.friendlyName = _getBagsByAttribute(
|
||
pfx.safeContents, 'friendlyName',
|
||
filter.friendlyName, filter.bagType);
|
||
}
|
||
|
||
return rval;
|
||
},
|
||
|
||
/**
|
||
* DEPRECATED: use getBags() instead.
|
||
*
|
||
* Get bags with matching friendlyName attribute.
|
||
*
|
||
* @param friendlyName the friendly name to search for.
|
||
* @param [bagType] bag type to narrow search by.
|
||
*
|
||
* @return an array of bags with matching friendlyName attribute.
|
||
*/
|
||
getBagsByFriendlyName: function(friendlyName, bagType) {
|
||
return _getBagsByAttribute(
|
||
pfx.safeContents, 'friendlyName', friendlyName, bagType);
|
||
},
|
||
|
||
/**
|
||
* DEPRECATED: use getBags() instead.
|
||
*
|
||
* Get bags with matching localKeyId attribute.
|
||
*
|
||
* @param localKeyId the localKeyId to search for.
|
||
* @param [bagType] bag type to narrow search by.
|
||
*
|
||
* @return an array of bags with matching localKeyId attribute.
|
||
*/
|
||
getBagsByLocalKeyId: function(localKeyId, bagType) {
|
||
return _getBagsByAttribute(
|
||
pfx.safeContents, 'localKeyId', localKeyId, bagType);
|
||
}
|
||
};
|
||
|
||
if(capture.version.charCodeAt(0) !== 3) {
|
||
var error = new Error('PKCS#12 PFX of version other than 3 not supported.');
|
||
error.version = capture.version.charCodeAt(0);
|
||
throw error;
|
||
}
|
||
|
||
if(asn1.derToOid(capture.contentType) !== pki.oids.data) {
|
||
var error = new Error('Only PKCS#12 PFX in password integrity mode supported.');
|
||
error.oid = asn1.derToOid(capture.contentType);
|
||
throw error;
|
||
}
|
||
|
||
var data = capture.content.value[0];
|
||
if(data.tagClass !== asn1.Class.UNIVERSAL ||
|
||
data.type !== asn1.Type.OCTETSTRING) {
|
||
throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.');
|
||
}
|
||
data = _decodePkcs7Data(data);
|
||
|
||
// check for MAC
|
||
if(capture.mac) {
|
||
var md = null;
|
||
var macKeyBytes = 0;
|
||
var macAlgorithm = asn1.derToOid(capture.macAlgorithm);
|
||
switch(macAlgorithm) {
|
||
case pki.oids.sha1:
|
||
md = forge.md.sha1.create();
|
||
macKeyBytes = 20;
|
||
break;
|
||
case pki.oids.sha256:
|
||
md = forge.md.sha256.create();
|
||
macKeyBytes = 32;
|
||
break;
|
||
case pki.oids.sha384:
|
||
md = forge.md.sha384.create();
|
||
macKeyBytes = 48;
|
||
break;
|
||
case pki.oids.sha512:
|
||
md = forge.md.sha512.create();
|
||
macKeyBytes = 64;
|
||
break;
|
||
case pki.oids.md5:
|
||
md = forge.md.md5.create();
|
||
macKeyBytes = 16;
|
||
break;
|
||
}
|
||
if(md === null) {
|
||
throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm);
|
||
}
|
||
|
||
// verify MAC (iterations default to 1)
|
||
var macSalt = new forge.util.ByteBuffer(capture.macSalt);
|
||
var macIterations = (('macIterations' in capture) ?
|
||
parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1);
|
||
var macKey = p12.generateKey(
|
||
password, macSalt, 3, macIterations, macKeyBytes, md);
|
||
var mac = forge.hmac.create();
|
||
mac.start(md, macKey);
|
||
mac.update(data.value);
|
||
var macValue = mac.getMac();
|
||
if(macValue.getBytes() !== capture.macDigest) {
|
||
throw new Error('PKCS#12 MAC could not be verified. Invalid password?');
|
||
}
|
||
}
|
||
|
||
_decodeAuthenticatedSafe(pfx, data.value, strict, password);
|
||
return pfx;
|
||
};
|
||
|
||
/**
|
||
* Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING,
|
||
* but it is sometimes an OCTET STRING that is composed/constructed of chunks,
|
||
* each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This
|
||
* function transforms this corner-case into the usual simple,
|
||
* non-composed/constructed OCTET STRING.
|
||
*
|
||
* This function may be moved to ASN.1 at some point to better deal with
|
||
* more BER-encoding issues, should they arise.
|
||
*
|
||
* @param data the ASN.1 Data object to transform.
|
||
*/
|
||
function _decodePkcs7Data(data) {
|
||
// handle special case of "chunked" data content: an octet string composed
|
||
// of other octet strings
|
||
if(data.composed || data.constructed) {
|
||
var value = forge.util.createBuffer();
|
||
for(var i = 0; i < data.value.length; ++i) {
|
||
value.putBytes(data.value[i].value);
|
||
}
|
||
data.composed = data.constructed = false;
|
||
data.value = value.getBytes();
|
||
}
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object.
|
||
*
|
||
* The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo.
|
||
*
|
||
* @param pfx The PKCS#12 PFX object to fill.
|
||
* @param {String} authSafe BER-encoded AuthenticatedSafe.
|
||
* @param strict true to use strict DER decoding, false not to.
|
||
* @param {String} password Password to decrypt with (optional).
|
||
*/
|
||
function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) {
|
||
authSafe = asn1.fromDer(authSafe, strict); /* actually it's BER encoded */
|
||
|
||
if(authSafe.tagClass !== asn1.Class.UNIVERSAL ||
|
||
authSafe.type !== asn1.Type.SEQUENCE ||
|
||
authSafe.constructed !== true) {
|
||
throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' +
|
||
'SEQUENCE OF ContentInfo');
|
||
}
|
||
|
||
for(var i = 0; i < authSafe.value.length; i ++) {
|
||
var contentInfo = authSafe.value[i];
|
||
|
||
// validate contentInfo and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) {
|
||
var error = new Error('Cannot read ContentInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var obj = {
|
||
encrypted: false
|
||
};
|
||
var safeContents = null;
|
||
var data = capture.content.value[0];
|
||
switch(asn1.derToOid(capture.contentType)) {
|
||
case pki.oids.data:
|
||
if(data.tagClass !== asn1.Class.UNIVERSAL ||
|
||
data.type !== asn1.Type.OCTETSTRING) {
|
||
throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.');
|
||
}
|
||
safeContents = _decodePkcs7Data(data).value;
|
||
break;
|
||
case pki.oids.encryptedData:
|
||
safeContents = _decryptSafeContents(data, password);
|
||
obj.encrypted = true;
|
||
break;
|
||
default:
|
||
var error = new Error('Unsupported PKCS#12 contentType.');
|
||
error.contentType = asn1.derToOid(capture.contentType);
|
||
throw error;
|
||
}
|
||
|
||
obj.safeBags = _decodeSafeContents(safeContents, strict, password);
|
||
pfx.safeContents.push(obj);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Decrypt PKCS#7 EncryptedData structure.
|
||
*
|
||
* @param data ASN.1 encoded EncryptedContentInfo object.
|
||
* @param password The user-provided password.
|
||
*
|
||
* @return The decrypted SafeContents (ASN.1 object).
|
||
*/
|
||
function _decryptSafeContents(data, password) {
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(
|
||
data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) {
|
||
var error = new Error('Cannot read EncryptedContentInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var oid = asn1.derToOid(capture.contentType);
|
||
if(oid !== pki.oids.data) {
|
||
var error = new Error(
|
||
'PKCS#12 EncryptedContentInfo ContentType is not Data.');
|
||
error.oid = oid;
|
||
throw error;
|
||
}
|
||
|
||
// get cipher
|
||
oid = asn1.derToOid(capture.encAlgorithm);
|
||
var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
|
||
|
||
// get encrypted data
|
||
var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
|
||
var encrypted = forge.util.createBuffer(encryptedContentAsn1.value);
|
||
|
||
cipher.update(encrypted);
|
||
if(!cipher.finish()) {
|
||
throw new Error('Failed to decrypt PKCS#12 SafeContents.');
|
||
}
|
||
|
||
return cipher.output.getBytes();
|
||
}
|
||
|
||
/**
|
||
* Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects.
|
||
*
|
||
* The safeContents is a BER-encoded SEQUENCE OF SafeBag.
|
||
*
|
||
* @param {String} safeContents BER-encoded safeContents.
|
||
* @param strict true to use strict DER decoding, false not to.
|
||
* @param {String} password Password to decrypt with (optional).
|
||
*
|
||
* @return {Array} Array of Bag objects.
|
||
*/
|
||
function _decodeSafeContents(safeContents, strict, password) {
|
||
// if strict and no safe contents, return empty safes
|
||
if(!strict && safeContents.length === 0) {
|
||
return [];
|
||
}
|
||
|
||
// actually it's BER-encoded
|
||
safeContents = asn1.fromDer(safeContents, strict);
|
||
|
||
if(safeContents.tagClass !== asn1.Class.UNIVERSAL ||
|
||
safeContents.type !== asn1.Type.SEQUENCE ||
|
||
safeContents.constructed !== true) {
|
||
throw new Error(
|
||
'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.');
|
||
}
|
||
|
||
var res = [];
|
||
for(var i = 0; i < safeContents.value.length; i++) {
|
||
var safeBag = safeContents.value[i];
|
||
|
||
// validate SafeBag and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) {
|
||
var error = new Error('Cannot read SafeBag.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
/* Create bag object and push to result array. */
|
||
var bag = {
|
||
type: asn1.derToOid(capture.bagId),
|
||
attributes: _decodeBagAttributes(capture.bagAttributes)
|
||
};
|
||
res.push(bag);
|
||
|
||
var validator, decoder;
|
||
var bagAsn1 = capture.bagValue.value[0];
|
||
switch(bag.type) {
|
||
case pki.oids.pkcs8ShroudedKeyBag:
|
||
/* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt.
|
||
Afterwards we can handle it like a keyBag,
|
||
which is a PrivateKeyInfo. */
|
||
bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password);
|
||
if(bagAsn1 === null) {
|
||
throw new Error(
|
||
'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?');
|
||
}
|
||
|
||
/* fall through */
|
||
case pki.oids.keyBag:
|
||
/* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our
|
||
PKI module, hence we don't have to do validation/capturing here,
|
||
just pass what we already got. */
|
||
try {
|
||
bag.key = pki.privateKeyFromAsn1(bagAsn1);
|
||
} catch(e) {
|
||
// ignore unknown key type, pass asn1 value
|
||
bag.key = null;
|
||
bag.asn1 = bagAsn1;
|
||
}
|
||
continue; /* Nothing more to do. */
|
||
|
||
case pki.oids.certBag:
|
||
/* A PKCS#12 certBag can wrap both X.509 and sdsi certificates.
|
||
Therefore put the SafeBag content through another validator to
|
||
capture the fields. Afterwards check & store the results. */
|
||
validator = certBagValidator;
|
||
decoder = function() {
|
||
if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) {
|
||
var error = new Error(
|
||
'Unsupported certificate type, only X.509 supported.');
|
||
error.oid = asn1.derToOid(capture.certId);
|
||
throw error;
|
||
}
|
||
|
||
// true=produce cert hash
|
||
var certAsn1 = asn1.fromDer(capture.cert, strict);
|
||
try {
|
||
bag.cert = pki.certificateFromAsn1(certAsn1, true);
|
||
} catch(e) {
|
||
// ignore unknown cert type, pass asn1 value
|
||
bag.cert = null;
|
||
bag.asn1 = certAsn1;
|
||
}
|
||
};
|
||
break;
|
||
|
||
default:
|
||
var error = new Error('Unsupported PKCS#12 SafeBag type.');
|
||
error.oid = bag.type;
|
||
throw error;
|
||
}
|
||
|
||
/* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */
|
||
if(validator !== undefined &&
|
||
!asn1.validate(bagAsn1, validator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#12 ' + validator.name);
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
/* Call decoder function from above to store the results. */
|
||
decoder();
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object.
|
||
*
|
||
* @param attributes SET OF PKCS12Attribute (ASN.1 object).
|
||
*
|
||
* @return the decoded attributes.
|
||
*/
|
||
function _decodeBagAttributes(attributes) {
|
||
var decodedAttrs = {};
|
||
|
||
if(attributes !== undefined) {
|
||
for(var i = 0; i < attributes.length; ++i) {
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#12 BagAttribute.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var oid = asn1.derToOid(capture.oid);
|
||
if(pki.oids[oid] === undefined) {
|
||
// unsupported attribute type, ignore.
|
||
continue;
|
||
}
|
||
|
||
decodedAttrs[pki.oids[oid]] = [];
|
||
for(var j = 0; j < capture.values.length; ++j) {
|
||
decodedAttrs[pki.oids[oid]].push(capture.values[j].value);
|
||
}
|
||
}
|
||
}
|
||
|
||
return decodedAttrs;
|
||
}
|
||
|
||
/**
|
||
* Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a
|
||
* password is provided then the private key will be encrypted.
|
||
*
|
||
* An entire certificate chain may also be included. To do this, pass
|
||
* an array for the "cert" parameter where the first certificate is
|
||
* the one that is paired with the private key and each subsequent one
|
||
* verifies the previous one. The certificates may be in PEM format or
|
||
* have been already parsed by Forge.
|
||
*
|
||
* @todo implement password-based-encryption for the whole package
|
||
*
|
||
* @param key the private key.
|
||
* @param cert the certificate (may be an array of certificates in order
|
||
* to specify a certificate chain).
|
||
* @param password the password to use, null for none.
|
||
* @param options:
|
||
* algorithm the encryption algorithm to use
|
||
* ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
|
||
* count the iteration count to use.
|
||
* saltSize the salt size to use.
|
||
* useMac true to include a MAC, false not to, defaults to true.
|
||
* localKeyId the local key ID to use, in hex.
|
||
* friendlyName the friendly name to use.
|
||
* generateLocalKeyId true to generate a random local key ID,
|
||
* false not to, defaults to true.
|
||
*
|
||
* @return the PKCS#12 PFX ASN.1 object.
|
||
*/
|
||
p12.toPkcs12Asn1 = function(key, cert, password, options) {
|
||
// set default options
|
||
options = options || {};
|
||
options.saltSize = options.saltSize || 8;
|
||
options.count = options.count || 2048;
|
||
options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
|
||
if(!('useMac' in options)) {
|
||
options.useMac = true;
|
||
}
|
||
if(!('localKeyId' in options)) {
|
||
options.localKeyId = null;
|
||
}
|
||
if(!('generateLocalKeyId' in options)) {
|
||
options.generateLocalKeyId = true;
|
||
}
|
||
|
||
var localKeyId = options.localKeyId;
|
||
var bagAttrs;
|
||
if(localKeyId !== null) {
|
||
localKeyId = forge.util.hexToBytes(localKeyId);
|
||
} else if(options.generateLocalKeyId) {
|
||
// use SHA-1 of paired cert, if available
|
||
if(cert) {
|
||
var pairedCert = forge.util.isArray(cert) ? cert[0] : cert;
|
||
if(typeof pairedCert === 'string') {
|
||
pairedCert = pki.certificateFromPem(pairedCert);
|
||
}
|
||
var sha1 = forge.md.sha1.create();
|
||
sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes());
|
||
localKeyId = sha1.digest().getBytes();
|
||
} else {
|
||
// FIXME: consider using SHA-1 of public key (which can be generated
|
||
// from private key components), see: cert.generateSubjectKeyIdentifier
|
||
// generate random bytes
|
||
localKeyId = forge.random.getBytes(20);
|
||
}
|
||
}
|
||
|
||
var attrs = [];
|
||
if(localKeyId !== null) {
|
||
attrs.push(
|
||
// localKeyID
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// attrId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.localKeyId).getBytes()),
|
||
// attrValues
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
localKeyId)
|
||
])
|
||
]));
|
||
}
|
||
if('friendlyName' in options) {
|
||
attrs.push(
|
||
// friendlyName
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// attrId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.friendlyName).getBytes()),
|
||
// attrValues
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false,
|
||
options.friendlyName)
|
||
])
|
||
]));
|
||
}
|
||
|
||
if(attrs.length > 0) {
|
||
bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
|
||
}
|
||
|
||
// collect contents for AuthenticatedSafe
|
||
var contents = [];
|
||
|
||
// create safe bag(s) for certificate chain
|
||
var chain = [];
|
||
if(cert !== null) {
|
||
if(forge.util.isArray(cert)) {
|
||
chain = cert;
|
||
} else {
|
||
chain = [cert];
|
||
}
|
||
}
|
||
|
||
var certSafeBags = [];
|
||
for(var i = 0; i < chain.length; ++i) {
|
||
// convert cert from PEM as necessary
|
||
cert = chain[i];
|
||
if(typeof cert === 'string') {
|
||
cert = pki.certificateFromPem(cert);
|
||
}
|
||
|
||
// SafeBag
|
||
var certBagAttrs = (i === 0) ? bagAttrs : undefined;
|
||
var certAsn1 = pki.certificateToAsn1(cert);
|
||
var certSafeBag =
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// bagId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.certBag).getBytes()),
|
||
// bagValue
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
// CertBag
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// certId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.x509Certificate).getBytes()),
|
||
// certValue (x509Certificate)
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
asn1.toDer(certAsn1).getBytes())
|
||
])])]),
|
||
// bagAttributes (OPTIONAL)
|
||
certBagAttrs
|
||
]);
|
||
certSafeBags.push(certSafeBag);
|
||
}
|
||
|
||
if(certSafeBags.length > 0) {
|
||
// SafeContents
|
||
var certSafeContents = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);
|
||
|
||
// ContentInfo
|
||
var certCI =
|
||
// PKCS#7 ContentInfo
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// contentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
// OID for the content type is 'data'
|
||
asn1.oidToDer(pki.oids.data).getBytes()),
|
||
// content
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
asn1.toDer(certSafeContents).getBytes())
|
||
])
|
||
]);
|
||
contents.push(certCI);
|
||
}
|
||
|
||
// create safe contents for private key
|
||
var keyBag = null;
|
||
if(key !== null) {
|
||
// SafeBag
|
||
var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key));
|
||
if(password === null) {
|
||
// no encryption
|
||
keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// bagId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.keyBag).getBytes()),
|
||
// bagValue
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
// PrivateKeyInfo
|
||
pkAsn1
|
||
]),
|
||
// bagAttributes (OPTIONAL)
|
||
bagAttrs
|
||
]);
|
||
} else {
|
||
// encrypted PrivateKeyInfo
|
||
keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// bagId
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()),
|
||
// bagValue
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
// EncryptedPrivateKeyInfo
|
||
pki.encryptPrivateKeyInfo(pkAsn1, password, options)
|
||
]),
|
||
// bagAttributes (OPTIONAL)
|
||
bagAttrs
|
||
]);
|
||
}
|
||
|
||
// SafeContents
|
||
var keySafeContents =
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
|
||
|
||
// ContentInfo
|
||
var keyCI =
|
||
// PKCS#7 ContentInfo
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// contentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
// OID for the content type is 'data'
|
||
asn1.oidToDer(pki.oids.data).getBytes()),
|
||
// content
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
asn1.toDer(keySafeContents).getBytes())
|
||
])
|
||
]);
|
||
contents.push(keyCI);
|
||
}
|
||
|
||
// create AuthenticatedSafe by stringing together the contents
|
||
var safe = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents);
|
||
|
||
var macData;
|
||
if(options.useMac) {
|
||
// MacData
|
||
var sha1 = forge.md.sha1.create();
|
||
var macSalt = new forge.util.ByteBuffer(
|
||
forge.random.getBytes(options.saltSize));
|
||
var count = options.count;
|
||
// 160-bit key
|
||
var key = p12.generateKey(password, macSalt, 3, count, 20);
|
||
var mac = forge.hmac.create();
|
||
mac.start(sha1, key);
|
||
mac.update(asn1.toDer(safe).getBytes());
|
||
var macValue = mac.getMac();
|
||
macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// mac DigestInfo
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// digestAlgorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm = SHA-1
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(pki.oids.sha1).getBytes()),
|
||
// parameters = Null
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]),
|
||
// digest
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
|
||
false, macValue.getBytes())
|
||
]),
|
||
// macSalt OCTET STRING
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()),
|
||
// iterations INTEGER (XXX: Only support count < 65536)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(count).getBytes()
|
||
)
|
||
]);
|
||
}
|
||
|
||
// PFX
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version (3)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(3).getBytes()),
|
||
// PKCS#7 ContentInfo
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// contentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
// OID for the content type is 'data'
|
||
asn1.oidToDer(pki.oids.data).getBytes()),
|
||
// content
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
asn1.toDer(safe).getBytes())
|
||
])
|
||
]),
|
||
macData
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* Derives a PKCS#12 key.
|
||
*
|
||
* @param password the password to derive the key material from, null or
|
||
* undefined for none.
|
||
* @param salt the salt, as a ByteBuffer, to use.
|
||
* @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
|
||
* @param iter the iteration count.
|
||
* @param n the number of bytes to derive from the password.
|
||
* @param md the message digest to use, defaults to SHA-1.
|
||
*
|
||
* @return a ByteBuffer with the bytes derived from the password.
|
||
*/
|
||
p12.generateKey = forge.pbe.generatePkcs12Key;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pkcs12';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pkcs12',[
|
||
'require',
|
||
'module',
|
||
'./asn1',
|
||
'./hmac',
|
||
'./oids',
|
||
'./pkcs7asn1',
|
||
'./pbe',
|
||
'./random',
|
||
'./rsa',
|
||
'./sha1',
|
||
'./util',
|
||
'./x509'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of a basic Public Key Infrastructure, including
|
||
* support for RSA public and private keys.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for asn.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
/* Public Key Infrastructure (PKI) implementation. */
|
||
var pki = forge.pki = forge.pki || {};
|
||
|
||
/**
|
||
* NOTE: THIS METHOD IS DEPRECATED. Use pem.decode() instead.
|
||
*
|
||
* Converts PEM-formatted data to DER.
|
||
*
|
||
* @param pem the PEM-formatted data.
|
||
*
|
||
* @return the DER-formatted data.
|
||
*/
|
||
pki.pemToDer = function(pem) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert PEM to DER; PEM is encrypted.');
|
||
}
|
||
return forge.util.createBuffer(msg.body);
|
||
};
|
||
|
||
/**
|
||
* Converts an RSA private key from PEM format.
|
||
*
|
||
* @param pem the PEM-formatted private key.
|
||
*
|
||
* @return the private key.
|
||
*/
|
||
pki.privateKeyFromPem = function(pem) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'PRIVATE KEY' && msg.type !== 'RSA PRIVATE KEY') {
|
||
var error = new Error('Could not convert private key from PEM; PEM ' +
|
||
'header type is not "PRIVATE KEY" or "RSA PRIVATE KEY".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert private key from PEM; PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
var obj = asn1.fromDer(msg.body);
|
||
|
||
return pki.privateKeyFromAsn1(obj);
|
||
};
|
||
|
||
/**
|
||
* Converts an RSA private key to PEM format.
|
||
*
|
||
* @param key the private key.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted private key.
|
||
*/
|
||
pki.privateKeyToPem = function(key, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var msg = {
|
||
type: 'RSA PRIVATE KEY',
|
||
body: asn1.toDer(pki.privateKeyToAsn1(key)).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Converts a PrivateKeyInfo to PEM format.
|
||
*
|
||
* @param pki the PrivateKeyInfo.
|
||
* @param maxline the maximum characters per line, defaults to 64.
|
||
*
|
||
* @return the PEM-formatted private key.
|
||
*/
|
||
pki.privateKeyInfoToPem = function(pki, maxline) {
|
||
// convert to DER, then PEM-encode
|
||
var msg = {
|
||
type: 'PRIVATE KEY',
|
||
body: asn1.toDer(pki).getBytes()
|
||
};
|
||
return forge.pem.encode(msg, {maxline: maxline});
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pki';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pki',[
|
||
'require',
|
||
'module',
|
||
'./asn1',
|
||
'./oids',
|
||
'./pbe',
|
||
'./pem',
|
||
'./pbkdf2',
|
||
'./pkcs12',
|
||
'./pss',
|
||
'./rsa',
|
||
'./util',
|
||
'./x509'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* A Javascript implementation of Transport Layer Security (TLS).
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2009-2014 Digital Bazaar, Inc.
|
||
*
|
||
* The TLS Handshake Protocol involves the following steps:
|
||
*
|
||
* - Exchange hello messages to agree on algorithms, exchange random values,
|
||
* and check for session resumption.
|
||
*
|
||
* - Exchange the necessary cryptographic parameters to allow the client and
|
||
* server to agree on a premaster secret.
|
||
*
|
||
* - Exchange certificates and cryptographic information to allow the client
|
||
* and server to authenticate themselves.
|
||
*
|
||
* - Generate a master secret from the premaster secret and exchanged random
|
||
* values.
|
||
*
|
||
* - Provide security parameters to the record layer.
|
||
*
|
||
* - Allow the client and server to verify that their peer has calculated the
|
||
* same security parameters and that the handshake occurred without tampering
|
||
* by an attacker.
|
||
*
|
||
* Up to 4 different messages may be sent during a key exchange. The server
|
||
* certificate, the server key exchange, the client certificate, and the
|
||
* client key exchange.
|
||
*
|
||
* A typical handshake (from the client's perspective).
|
||
*
|
||
* 1. Client sends ClientHello.
|
||
* 2. Client receives ServerHello.
|
||
* 3. Client receives optional Certificate.
|
||
* 4. Client receives optional ServerKeyExchange.
|
||
* 5. Client receives ServerHelloDone.
|
||
* 6. Client sends optional Certificate.
|
||
* 7. Client sends ClientKeyExchange.
|
||
* 8. Client sends optional CertificateVerify.
|
||
* 9. Client sends ChangeCipherSpec.
|
||
* 10. Client sends Finished.
|
||
* 11. Client receives ChangeCipherSpec.
|
||
* 12. Client receives Finished.
|
||
* 13. Client sends/receives application data.
|
||
*
|
||
* To reuse an existing session:
|
||
*
|
||
* 1. Client sends ClientHello with session ID for reuse.
|
||
* 2. Client receives ServerHello with same session ID if reusing.
|
||
* 3. Client receives ChangeCipherSpec message if reusing.
|
||
* 4. Client receives Finished.
|
||
* 5. Client sends ChangeCipherSpec.
|
||
* 6. Client sends Finished.
|
||
*
|
||
* Note: Client ignores HelloRequest if in the middle of a handshake.
|
||
*
|
||
* Record Layer:
|
||
*
|
||
* The record layer fragments information blocks into TLSPlaintext records
|
||
* carrying data in chunks of 2^14 bytes or less. Client message boundaries are
|
||
* not preserved in the record layer (i.e., multiple client messages of the
|
||
* same ContentType MAY be coalesced into a single TLSPlaintext record, or a
|
||
* single message MAY be fragmented across several records).
|
||
*
|
||
* struct {
|
||
* uint8 major;
|
||
* uint8 minor;
|
||
* } ProtocolVersion;
|
||
*
|
||
* struct {
|
||
* ContentType type;
|
||
* ProtocolVersion version;
|
||
* uint16 length;
|
||
* opaque fragment[TLSPlaintext.length];
|
||
* } TLSPlaintext;
|
||
*
|
||
* type:
|
||
* The higher-level protocol used to process the enclosed fragment.
|
||
*
|
||
* version:
|
||
* The version of the protocol being employed. TLS Version 1.2 uses version
|
||
* {3, 3}. TLS Version 1.0 uses version {3, 1}. Note that a client that
|
||
* supports multiple versions of TLS may not know what version will be
|
||
* employed before it receives the ServerHello.
|
||
*
|
||
* length:
|
||
* The length (in bytes) of the following TLSPlaintext.fragment. The length
|
||
* MUST NOT exceed 2^14 = 16384 bytes.
|
||
*
|
||
* fragment:
|
||
* The application data. This data is transparent and treated as an
|
||
* independent block to be dealt with by the higher-level protocol specified
|
||
* by the type field.
|
||
*
|
||
* Implementations MUST NOT send zero-length fragments of Handshake, Alert, or
|
||
* ChangeCipherSpec content types. Zero-length fragments of Application data
|
||
* MAY be sent as they are potentially useful as a traffic analysis
|
||
* countermeasure.
|
||
*
|
||
* Note: Data of different TLS record layer content types MAY be interleaved.
|
||
* Application data is generally of lower precedence for transmission than
|
||
* other content types. However, records MUST be delivered to the network in
|
||
* the same order as they are protected by the record layer. Recipients MUST
|
||
* receive and process interleaved application layer traffic during handshakes
|
||
* subsequent to the first one on a connection.
|
||
*
|
||
* struct {
|
||
* ContentType type; // same as TLSPlaintext.type
|
||
* ProtocolVersion version;// same as TLSPlaintext.version
|
||
* uint16 length;
|
||
* opaque fragment[TLSCompressed.length];
|
||
* } TLSCompressed;
|
||
*
|
||
* length:
|
||
* The length (in bytes) of the following TLSCompressed.fragment.
|
||
* The length MUST NOT exceed 2^14 + 1024.
|
||
*
|
||
* fragment:
|
||
* The compressed form of TLSPlaintext.fragment.
|
||
*
|
||
* Note: A CompressionMethod.null operation is an identity operation; no fields
|
||
* are altered. In this implementation, since no compression is supported,
|
||
* uncompressed records are always the same as compressed records.
|
||
*
|
||
* Encryption Information:
|
||
*
|
||
* The encryption and MAC functions translate a TLSCompressed structure into a
|
||
* TLSCiphertext. The decryption functions reverse the process. The MAC of the
|
||
* record also includes a sequence number so that missing, extra, or repeated
|
||
* messages are detectable.
|
||
*
|
||
* struct {
|
||
* ContentType type;
|
||
* ProtocolVersion version;
|
||
* uint16 length;
|
||
* select (SecurityParameters.cipher_type) {
|
||
* case stream: GenericStreamCipher;
|
||
* case block: GenericBlockCipher;
|
||
* case aead: GenericAEADCipher;
|
||
* } fragment;
|
||
* } TLSCiphertext;
|
||
*
|
||
* type:
|
||
* The type field is identical to TLSCompressed.type.
|
||
*
|
||
* version:
|
||
* The version field is identical to TLSCompressed.version.
|
||
*
|
||
* length:
|
||
* The length (in bytes) of the following TLSCiphertext.fragment.
|
||
* The length MUST NOT exceed 2^14 + 2048.
|
||
*
|
||
* fragment:
|
||
* The encrypted form of TLSCompressed.fragment, with the MAC.
|
||
*
|
||
* Note: Only CBC Block Ciphers are supported by this implementation.
|
||
*
|
||
* The TLSCompressed.fragment structures are converted to/from block
|
||
* TLSCiphertext.fragment structures.
|
||
*
|
||
* struct {
|
||
* opaque IV[SecurityParameters.record_iv_length];
|
||
* block-ciphered struct {
|
||
* opaque content[TLSCompressed.length];
|
||
* opaque MAC[SecurityParameters.mac_length];
|
||
* uint8 padding[GenericBlockCipher.padding_length];
|
||
* uint8 padding_length;
|
||
* };
|
||
* } GenericBlockCipher;
|
||
*
|
||
* The MAC is generated as described in Section 6.2.3.1.
|
||
*
|
||
* IV:
|
||
* The Initialization Vector (IV) SHOULD be chosen at random, and MUST be
|
||
* unpredictable. Note that in versions of TLS prior to 1.1, there was no
|
||
* IV field, and the last ciphertext block of the previous record (the "CBC
|
||
* residue") was used as the IV. This was changed to prevent the attacks
|
||
* described in [CBCATT]. For block ciphers, the IV length is of length
|
||
* SecurityParameters.record_iv_length, which is equal to the
|
||
* SecurityParameters.block_size.
|
||
*
|
||
* padding:
|
||
* Padding that is added to force the length of the plaintext to be an
|
||
* integral multiple of the block cipher's block length. The padding MAY be
|
||
* any length up to 255 bytes, as long as it results in the
|
||
* TLSCiphertext.length being an integral multiple of the block length.
|
||
* Lengths longer than necessary might be desirable to frustrate attacks on
|
||
* a protocol that are based on analysis of the lengths of exchanged
|
||
* messages. Each uint8 in the padding data vector MUST be filled with the
|
||
* padding length value. The receiver MUST check this padding and MUST use
|
||
* the bad_record_mac alert to indicate padding errors.
|
||
*
|
||
* padding_length:
|
||
* The padding length MUST be such that the total size of the
|
||
* GenericBlockCipher structure is a multiple of the cipher's block length.
|
||
* Legal values range from zero to 255, inclusive. This length specifies the
|
||
* length of the padding field exclusive of the padding_length field itself.
|
||
*
|
||
* The encrypted data length (TLSCiphertext.length) is one more than the sum of
|
||
* SecurityParameters.block_length, TLSCompressed.length,
|
||
* SecurityParameters.mac_length, and padding_length.
|
||
*
|
||
* Example: If the block length is 8 bytes, the content length
|
||
* (TLSCompressed.length) is 61 bytes, and the MAC length is 20 bytes, then the
|
||
* length before padding is 82 bytes (this does not include the IV. Thus, the
|
||
* padding length modulo 8 must be equal to 6 in order to make the total length
|
||
* an even multiple of 8 bytes (the block length). The padding length can be
|
||
* 6, 14, 22, and so on, through 254. If the padding length were the minimum
|
||
* necessary, 6, the padding would be 6 bytes, each containing the value 6.
|
||
* Thus, the last 8 octets of the GenericBlockCipher before block encryption
|
||
* would be xx 06 06 06 06 06 06 06, where xx is the last octet of the MAC.
|
||
*
|
||
* Note: With block ciphers in CBC mode (Cipher Block Chaining), it is critical
|
||
* that the entire plaintext of the record be known before any ciphertext is
|
||
* transmitted. Otherwise, it is possible for the attacker to mount the attack
|
||
* described in [CBCATT].
|
||
*
|
||
* Implementation note: Canvel et al. [CBCTIME] have demonstrated a timing
|
||
* attack on CBC padding based on the time required to compute the MAC. In
|
||
* order to defend against this attack, implementations MUST ensure that
|
||
* record processing time is essentially the same whether or not the padding
|
||
* is correct. In general, the best way to do this is to compute the MAC even
|
||
* if the padding is incorrect, and only then reject the packet. For instance,
|
||
* if the pad appears to be incorrect, the implementation might assume a
|
||
* zero-length pad and then compute the MAC. This leaves a small timing
|
||
* channel, since MAC performance depends, to some extent, on the size of the
|
||
* data fragment, but it is not believed to be large enough to be exploitable,
|
||
* due to the large block size of existing MACs and the small size of the
|
||
* timing signal.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/**
|
||
* Generates pseudo random bytes by mixing the result of two hash functions,
|
||
* MD5 and SHA-1.
|
||
*
|
||
* prf_TLS1(secret, label, seed) =
|
||
* P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
|
||
*
|
||
* Each P_hash function functions as follows:
|
||
*
|
||
* P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
|
||
* HMAC_hash(secret, A(2) + seed) +
|
||
* HMAC_hash(secret, A(3) + seed) + ...
|
||
* A() is defined as:
|
||
* A(0) = seed
|
||
* A(i) = HMAC_hash(secret, A(i-1))
|
||
*
|
||
* The '+' operator denotes concatenation.
|
||
*
|
||
* As many iterations A(N) as are needed are performed to generate enough
|
||
* pseudo random byte output. If an iteration creates more data than is
|
||
* necessary, then it is truncated.
|
||
*
|
||
* Therefore:
|
||
* A(1) = HMAC_hash(secret, A(0))
|
||
* = HMAC_hash(secret, seed)
|
||
* A(2) = HMAC_hash(secret, A(1))
|
||
* = HMAC_hash(secret, HMAC_hash(secret, seed))
|
||
*
|
||
* Therefore:
|
||
* P_hash(secret, seed) =
|
||
* HMAC_hash(secret, HMAC_hash(secret, A(0)) + seed) +
|
||
* HMAC_hash(secret, HMAC_hash(secret, A(1)) + seed) +
|
||
* ...
|
||
*
|
||
* Therefore:
|
||
* P_hash(secret, seed) =
|
||
* HMAC_hash(secret, HMAC_hash(secret, seed) + seed) +
|
||
* HMAC_hash(secret, HMAC_hash(secret, HMAC_hash(secret, seed)) + seed) +
|
||
* ...
|
||
*
|
||
* @param secret the secret to use.
|
||
* @param label the label to use.
|
||
* @param seed the seed value to use.
|
||
* @param length the number of bytes to generate.
|
||
*
|
||
* @return the pseudo random bytes in a byte buffer.
|
||
*/
|
||
var prf_TLS1 = function(secret, label, seed, length) {
|
||
var rval = forge.util.createBuffer();
|
||
|
||
/* For TLS 1.0, the secret is split in half, into two secrets of equal
|
||
length. If the secret has an odd length then the last byte of the first
|
||
half will be the same as the first byte of the second. The length of the
|
||
two secrets is half of the secret rounded up. */
|
||
var idx = (secret.length >> 1);
|
||
var slen = idx + (secret.length & 1);
|
||
var s1 = secret.substr(0, slen);
|
||
var s2 = secret.substr(idx, slen);
|
||
var ai = forge.util.createBuffer();
|
||
var hmac = forge.hmac.create();
|
||
seed = label + seed;
|
||
|
||
// determine the number of iterations that must be performed to generate
|
||
// enough output bytes, md5 creates 16 byte hashes, sha1 creates 20
|
||
var md5itr = Math.ceil(length / 16);
|
||
var sha1itr = Math.ceil(length / 20);
|
||
|
||
// do md5 iterations
|
||
hmac.start('MD5', s1);
|
||
var md5bytes = forge.util.createBuffer();
|
||
ai.putBytes(seed);
|
||
for(var i = 0; i < md5itr; ++i) {
|
||
// HMAC_hash(secret, A(i-1))
|
||
hmac.start(null, null);
|
||
hmac.update(ai.getBytes());
|
||
ai.putBuffer(hmac.digest());
|
||
|
||
// HMAC_hash(secret, A(i) + seed)
|
||
hmac.start(null, null);
|
||
hmac.update(ai.bytes() + seed);
|
||
md5bytes.putBuffer(hmac.digest());
|
||
}
|
||
|
||
// do sha1 iterations
|
||
hmac.start('SHA1', s2);
|
||
var sha1bytes = forge.util.createBuffer();
|
||
ai.clear();
|
||
ai.putBytes(seed);
|
||
for(var i = 0; i < sha1itr; ++i) {
|
||
// HMAC_hash(secret, A(i-1))
|
||
hmac.start(null, null);
|
||
hmac.update(ai.getBytes());
|
||
ai.putBuffer(hmac.digest());
|
||
|
||
// HMAC_hash(secret, A(i) + seed)
|
||
hmac.start(null, null);
|
||
hmac.update(ai.bytes() + seed);
|
||
sha1bytes.putBuffer(hmac.digest());
|
||
}
|
||
|
||
// XOR the md5 bytes with the sha1 bytes
|
||
rval.putBytes(forge.util.xorBytes(
|
||
md5bytes.getBytes(), sha1bytes.getBytes(), length));
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Generates pseudo random bytes using a SHA256 algorithm. For TLS 1.2.
|
||
*
|
||
* @param secret the secret to use.
|
||
* @param label the label to use.
|
||
* @param seed the seed value to use.
|
||
* @param length the number of bytes to generate.
|
||
*
|
||
* @return the pseudo random bytes in a byte buffer.
|
||
*/
|
||
var prf_sha256 = function(secret, label, seed, length) {
|
||
// FIXME: implement me for TLS 1.2
|
||
};
|
||
|
||
/**
|
||
* Gets a MAC for a record using the SHA-1 hash algorithm.
|
||
*
|
||
* @param key the mac key.
|
||
* @param state the sequence number (array of two 32-bit integers).
|
||
* @param record the record.
|
||
*
|
||
* @return the sha-1 hash (20 bytes) for the given record.
|
||
*/
|
||
var hmac_sha1 = function(key, seqNum, record) {
|
||
/* MAC is computed like so:
|
||
HMAC_hash(
|
||
key, seqNum +
|
||
TLSCompressed.type +
|
||
TLSCompressed.version +
|
||
TLSCompressed.length +
|
||
TLSCompressed.fragment)
|
||
*/
|
||
var hmac = forge.hmac.create();
|
||
hmac.start('SHA1', key);
|
||
var b = forge.util.createBuffer();
|
||
b.putInt32(seqNum[0]);
|
||
b.putInt32(seqNum[1]);
|
||
b.putByte(record.type);
|
||
b.putByte(record.version.major);
|
||
b.putByte(record.version.minor);
|
||
b.putInt16(record.length);
|
||
b.putBytes(record.fragment.bytes());
|
||
hmac.update(b.getBytes());
|
||
return hmac.digest().getBytes();
|
||
};
|
||
|
||
/**
|
||
* Compresses the TLSPlaintext record into a TLSCompressed record using the
|
||
* deflate algorithm.
|
||
*
|
||
* @param c the TLS connection.
|
||
* @param record the TLSPlaintext record to compress.
|
||
* @param s the ConnectionState to use.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
var deflate = function(c, record, s) {
|
||
var rval = false;
|
||
|
||
try {
|
||
var bytes = c.deflate(record.fragment.getBytes());
|
||
record.fragment = forge.util.createBuffer(bytes);
|
||
record.length = bytes.length;
|
||
rval = true;
|
||
} catch(ex) {
|
||
// deflate error, fail out
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Decompresses the TLSCompressed record into a TLSPlaintext record using the
|
||
* deflate algorithm.
|
||
*
|
||
* @param c the TLS connection.
|
||
* @param record the TLSCompressed record to decompress.
|
||
* @param s the ConnectionState to use.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
var inflate = function(c, record, s) {
|
||
var rval = false;
|
||
|
||
try {
|
||
var bytes = c.inflate(record.fragment.getBytes());
|
||
record.fragment = forge.util.createBuffer(bytes);
|
||
record.length = bytes.length;
|
||
rval = true;
|
||
} catch(ex) {
|
||
// inflate error, fail out
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Reads a TLS variable-length vector from a byte buffer.
|
||
*
|
||
* Variable-length vectors are defined by specifying a subrange of legal
|
||
* lengths, inclusively, using the notation <floor..ceiling>. When these are
|
||
* encoded, the actual length precedes the vector's contents in the byte
|
||
* stream. The length will be in the form of a number consuming as many bytes
|
||
* as required to hold the vector's specified maximum (ceiling) length. A
|
||
* variable-length vector with an actual length field of zero is referred to
|
||
* as an empty vector.
|
||
*
|
||
* @param b the byte buffer.
|
||
* @param lenBytes the number of bytes required to store the length.
|
||
*
|
||
* @return the resulting byte buffer.
|
||
*/
|
||
var readVector = function(b, lenBytes) {
|
||
var len = 0;
|
||
switch(lenBytes) {
|
||
case 1:
|
||
len = b.getByte();
|
||
break;
|
||
case 2:
|
||
len = b.getInt16();
|
||
break;
|
||
case 3:
|
||
len = b.getInt24();
|
||
break;
|
||
case 4:
|
||
len = b.getInt32();
|
||
break;
|
||
}
|
||
|
||
// read vector bytes into a new buffer
|
||
return forge.util.createBuffer(b.getBytes(len));
|
||
};
|
||
|
||
/**
|
||
* Writes a TLS variable-length vector to a byte buffer.
|
||
*
|
||
* @param b the byte buffer.
|
||
* @param lenBytes the number of bytes required to store the length.
|
||
* @param v the byte buffer vector.
|
||
*/
|
||
var writeVector = function(b, lenBytes, v) {
|
||
// encode length at the start of the vector, where the number of bytes for
|
||
// the length is the maximum number of bytes it would take to encode the
|
||
// vector's ceiling
|
||
b.putInt(v.length(), lenBytes << 3);
|
||
b.putBuffer(v);
|
||
};
|
||
|
||
/**
|
||
* The tls implementation.
|
||
*/
|
||
var tls = {};
|
||
|
||
/**
|
||
* Version: TLS 1.2 = 3.3, TLS 1.1 = 3.2, TLS 1.0 = 3.1. Both TLS 1.1 and
|
||
* TLS 1.2 were still too new (ie: openSSL didn't implement them) at the time
|
||
* of this implementation so TLS 1.0 was implemented instead.
|
||
*/
|
||
tls.Versions = {
|
||
TLS_1_0: {major: 3, minor: 1},
|
||
TLS_1_1: {major: 3, minor: 2},
|
||
TLS_1_2: {major: 3, minor: 3}
|
||
};
|
||
tls.SupportedVersions = [
|
||
tls.Versions.TLS_1_1,
|
||
tls.Versions.TLS_1_0
|
||
];
|
||
tls.Version = tls.SupportedVersions[0];
|
||
|
||
/**
|
||
* Maximum fragment size. True maximum is 16384, but we fragment before that
|
||
* to allow for unusual small increases during compression.
|
||
*/
|
||
tls.MaxFragment = 16384 - 1024;
|
||
|
||
/**
|
||
* Whether this entity is considered the "client" or "server".
|
||
* enum { server, client } ConnectionEnd;
|
||
*/
|
||
tls.ConnectionEnd = {
|
||
server: 0,
|
||
client: 1
|
||
};
|
||
|
||
/**
|
||
* Pseudo-random function algorithm used to generate keys from the master
|
||
* secret.
|
||
* enum { tls_prf_sha256 } PRFAlgorithm;
|
||
*/
|
||
tls.PRFAlgorithm = {
|
||
tls_prf_sha256: 0
|
||
};
|
||
|
||
/**
|
||
* Bulk encryption algorithms.
|
||
* enum { null, rc4, des3, aes } BulkCipherAlgorithm;
|
||
*/
|
||
tls.BulkCipherAlgorithm = {
|
||
none: null,
|
||
rc4: 0,
|
||
des3: 1,
|
||
aes: 2
|
||
};
|
||
|
||
/**
|
||
* Cipher types.
|
||
* enum { stream, block, aead } CipherType;
|
||
*/
|
||
tls.CipherType = {
|
||
stream: 0,
|
||
block: 1,
|
||
aead: 2
|
||
};
|
||
|
||
/**
|
||
* MAC (Message Authentication Code) algorithms.
|
||
* enum { null, hmac_md5, hmac_sha1, hmac_sha256,
|
||
* hmac_sha384, hmac_sha512} MACAlgorithm;
|
||
*/
|
||
tls.MACAlgorithm = {
|
||
none: null,
|
||
hmac_md5: 0,
|
||
hmac_sha1: 1,
|
||
hmac_sha256: 2,
|
||
hmac_sha384: 3,
|
||
hmac_sha512: 4
|
||
};
|
||
|
||
/**
|
||
* Compression algorithms.
|
||
* enum { null(0), deflate(1), (255) } CompressionMethod;
|
||
*/
|
||
tls.CompressionMethod = {
|
||
none: 0,
|
||
deflate: 1
|
||
};
|
||
|
||
/**
|
||
* TLS record content types.
|
||
* enum {
|
||
* change_cipher_spec(20), alert(21), handshake(22),
|
||
* application_data(23), (255)
|
||
* } ContentType;
|
||
*/
|
||
tls.ContentType = {
|
||
change_cipher_spec: 20,
|
||
alert: 21,
|
||
handshake: 22,
|
||
application_data: 23,
|
||
heartbeat: 24
|
||
};
|
||
|
||
/**
|
||
* TLS handshake types.
|
||
* enum {
|
||
* hello_request(0), client_hello(1), server_hello(2),
|
||
* certificate(11), server_key_exchange (12),
|
||
* certificate_request(13), server_hello_done(14),
|
||
* certificate_verify(15), client_key_exchange(16),
|
||
* finished(20), (255)
|
||
* } HandshakeType;
|
||
*/
|
||
tls.HandshakeType = {
|
||
hello_request: 0,
|
||
client_hello: 1,
|
||
server_hello: 2,
|
||
certificate: 11,
|
||
server_key_exchange: 12,
|
||
certificate_request: 13,
|
||
server_hello_done: 14,
|
||
certificate_verify: 15,
|
||
client_key_exchange: 16,
|
||
finished: 20
|
||
};
|
||
|
||
/**
|
||
* TLS Alert Protocol.
|
||
*
|
||
* enum { warning(1), fatal(2), (255) } AlertLevel;
|
||
*
|
||
* enum {
|
||
* close_notify(0),
|
||
* unexpected_message(10),
|
||
* bad_record_mac(20),
|
||
* decryption_failed(21),
|
||
* record_overflow(22),
|
||
* decompression_failure(30),
|
||
* handshake_failure(40),
|
||
* bad_certificate(42),
|
||
* unsupported_certificate(43),
|
||
* certificate_revoked(44),
|
||
* certificate_expired(45),
|
||
* certificate_unknown(46),
|
||
* illegal_parameter(47),
|
||
* unknown_ca(48),
|
||
* access_denied(49),
|
||
* decode_error(50),
|
||
* decrypt_error(51),
|
||
* export_restriction(60),
|
||
* protocol_version(70),
|
||
* insufficient_security(71),
|
||
* internal_error(80),
|
||
* user_canceled(90),
|
||
* no_renegotiation(100),
|
||
* (255)
|
||
* } AlertDescription;
|
||
*
|
||
* struct {
|
||
* AlertLevel level;
|
||
* AlertDescription description;
|
||
* } Alert;
|
||
*/
|
||
tls.Alert = {};
|
||
tls.Alert.Level = {
|
||
warning: 1,
|
||
fatal: 2
|
||
};
|
||
tls.Alert.Description = {
|
||
close_notify: 0,
|
||
unexpected_message: 10,
|
||
bad_record_mac: 20,
|
||
decryption_failed: 21,
|
||
record_overflow: 22,
|
||
decompression_failure: 30,
|
||
handshake_failure: 40,
|
||
bad_certificate: 42,
|
||
unsupported_certificate: 43,
|
||
certificate_revoked: 44,
|
||
certificate_expired: 45,
|
||
certificate_unknown: 46,
|
||
illegal_parameter: 47,
|
||
unknown_ca: 48,
|
||
access_denied: 49,
|
||
decode_error: 50,
|
||
decrypt_error: 51,
|
||
export_restriction: 60,
|
||
protocol_version: 70,
|
||
insufficient_security: 71,
|
||
internal_error: 80,
|
||
user_canceled: 90,
|
||
no_renegotiation: 100
|
||
};
|
||
|
||
/**
|
||
* TLS Heartbeat Message types.
|
||
* enum {
|
||
* heartbeat_request(1),
|
||
* heartbeat_response(2),
|
||
* (255)
|
||
* } HeartbeatMessageType;
|
||
*/
|
||
tls.HeartbeatMessageType = {
|
||
heartbeat_request: 1,
|
||
heartbeat_response: 2
|
||
};
|
||
|
||
/**
|
||
* Supported cipher suites.
|
||
*/
|
||
tls.CipherSuites = {};
|
||
|
||
/**
|
||
* Gets a supported cipher suite from its 2 byte ID.
|
||
*
|
||
* @param twoBytes two bytes in a string.
|
||
*
|
||
* @return the matching supported cipher suite or null.
|
||
*/
|
||
tls.getCipherSuite = function(twoBytes) {
|
||
var rval = null;
|
||
for(var key in tls.CipherSuites) {
|
||
var cs = tls.CipherSuites[key];
|
||
if(cs.id[0] === twoBytes.charCodeAt(0) &&
|
||
cs.id[1] === twoBytes.charCodeAt(1)) {
|
||
rval = cs;
|
||
break;
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Called when an unexpected record is encountered.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleUnexpected = function(c, record) {
|
||
// if connection is client and closed, ignore unexpected messages
|
||
var ignore = (!c.open && c.entity === tls.ConnectionEnd.client);
|
||
if(!ignore) {
|
||
c.error(c, {
|
||
message: 'Unexpected message. Received TLS record out of order.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.unexpected_message
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a HelloRequest record.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleHelloRequest = function(c, record, length) {
|
||
// ignore renegotiation requests from the server during a handshake, but
|
||
// if handshaking, send a warning alert that renegotation is denied
|
||
if(!c.handshaking && c.handshakes > 0) {
|
||
// send alert warning
|
||
tls.queue(c, tls.createAlert(c, {
|
||
level: tls.Alert.Level.warning,
|
||
description: tls.Alert.Description.no_renegotiation
|
||
}));
|
||
tls.flush(c);
|
||
}
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Parses a hello message from a ClientHello or ServerHello record.
|
||
*
|
||
* @param record the record to parse.
|
||
*
|
||
* @return the parsed message.
|
||
*/
|
||
tls.parseHelloMessage = function(c, record, length) {
|
||
var msg = null;
|
||
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
|
||
// minimum of 38 bytes in message
|
||
if(length < 38) {
|
||
c.error(c, {
|
||
message: client ?
|
||
'Invalid ServerHello message. Message too short.' :
|
||
'Invalid ClientHello message. Message too short.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
} else {
|
||
// use 'remaining' to calculate # of remaining bytes in the message
|
||
var b = record.fragment;
|
||
var remaining = b.length();
|
||
msg = {
|
||
version: {
|
||
major: b.getByte(),
|
||
minor: b.getByte()
|
||
},
|
||
random: forge.util.createBuffer(b.getBytes(32)),
|
||
session_id: readVector(b, 1),
|
||
extensions: []
|
||
};
|
||
if(client) {
|
||
msg.cipher_suite = b.getBytes(2);
|
||
msg.compression_method = b.getByte();
|
||
} else {
|
||
msg.cipher_suites = readVector(b, 2);
|
||
msg.compression_methods = readVector(b, 1);
|
||
}
|
||
|
||
// read extensions if there are any bytes left in the message
|
||
remaining = length - (remaining - b.length());
|
||
if(remaining > 0) {
|
||
// parse extensions
|
||
var exts = readVector(b, 2);
|
||
while(exts.length() > 0) {
|
||
msg.extensions.push({
|
||
type: [exts.getByte(), exts.getByte()],
|
||
data: readVector(exts, 2)
|
||
});
|
||
}
|
||
|
||
// TODO: make extension support modular
|
||
if(!client) {
|
||
for(var i = 0; i < msg.extensions.length; ++i) {
|
||
var ext = msg.extensions[i];
|
||
|
||
// support SNI extension
|
||
if(ext.type[0] === 0x00 && ext.type[1] === 0x00) {
|
||
// get server name list
|
||
var snl = readVector(ext.data, 2);
|
||
while(snl.length() > 0) {
|
||
// read server name type
|
||
var snType = snl.getByte();
|
||
|
||
// only HostName type (0x00) is known, break out if
|
||
// another type is detected
|
||
if(snType !== 0x00) {
|
||
break;
|
||
}
|
||
|
||
// add host name to server name list
|
||
c.session.extensions.server_name.serverNameList.push(
|
||
readVector(snl, 2).getBytes());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// version already set, do not allow version change
|
||
if(c.session.version) {
|
||
if(msg.version.major !== c.session.version.major ||
|
||
msg.version.minor !== c.session.version.minor) {
|
||
return c.error(c, {
|
||
message: 'TLS version change is disallowed during renegotiation.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.protocol_version
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// get the chosen (ServerHello) cipher suite
|
||
if(client) {
|
||
// FIXME: should be checking configured acceptable cipher suites
|
||
c.session.cipherSuite = tls.getCipherSuite(msg.cipher_suite);
|
||
} else {
|
||
// get a supported preferred (ClientHello) cipher suite
|
||
// choose the first supported cipher suite
|
||
var tmp = forge.util.createBuffer(msg.cipher_suites.bytes());
|
||
while(tmp.length() > 0) {
|
||
// FIXME: should be checking configured acceptable suites
|
||
// cipher suites take up 2 bytes
|
||
c.session.cipherSuite = tls.getCipherSuite(tmp.getBytes(2));
|
||
if(c.session.cipherSuite !== null) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// cipher suite not supported
|
||
if(c.session.cipherSuite === null) {
|
||
return c.error(c, {
|
||
message: 'No cipher suites in common.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.handshake_failure
|
||
},
|
||
cipherSuite: forge.util.bytesToHex(msg.cipher_suite)
|
||
});
|
||
}
|
||
|
||
// TODO: handle compression methods
|
||
if(client) {
|
||
c.session.compressionMethod = msg.compression_method;
|
||
} else {
|
||
// no compression
|
||
c.session.compressionMethod = tls.CompressionMethod.none;
|
||
}
|
||
}
|
||
|
||
return msg;
|
||
};
|
||
|
||
/**
|
||
* Creates security parameters for the given connection based on the given
|
||
* hello message.
|
||
*
|
||
* @param c the TLS connection.
|
||
* @param msg the hello message.
|
||
*/
|
||
tls.createSecurityParameters = function(c, msg) {
|
||
/* Note: security params are from TLS 1.2, some values like prf_algorithm
|
||
are ignored for TLS 1.0/1.1 and the builtin as specified in the spec is
|
||
used. */
|
||
|
||
// TODO: handle other options from server when more supported
|
||
|
||
// get client and server randoms
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
var msgRandom = msg.random.bytes();
|
||
var cRandom = client ? c.session.sp.client_random : msgRandom;
|
||
var sRandom = client ? msgRandom : tls.createRandom().getBytes();
|
||
|
||
// create new security parameters
|
||
c.session.sp = {
|
||
entity: c.entity,
|
||
prf_algorithm: tls.PRFAlgorithm.tls_prf_sha256,
|
||
bulk_cipher_algorithm: null,
|
||
cipher_type: null,
|
||
enc_key_length: null,
|
||
block_length: null,
|
||
fixed_iv_length: null,
|
||
record_iv_length: null,
|
||
mac_algorithm: null,
|
||
mac_length: null,
|
||
mac_key_length: null,
|
||
compression_algorithm: c.session.compressionMethod,
|
||
pre_master_secret: null,
|
||
master_secret: null,
|
||
client_random: cRandom,
|
||
server_random: sRandom
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a ServerHello record.
|
||
*
|
||
* When a ServerHello message will be sent:
|
||
* The server will send this message in response to a client hello message
|
||
* when it was able to find an acceptable set of algorithms. If it cannot
|
||
* find such a match, it will respond with a handshake failure alert.
|
||
*
|
||
* uint24 length;
|
||
* struct {
|
||
* ProtocolVersion server_version;
|
||
* Random random;
|
||
* SessionID session_id;
|
||
* CipherSuite cipher_suite;
|
||
* CompressionMethod compression_method;
|
||
* select(extensions_present) {
|
||
* case false:
|
||
* struct {};
|
||
* case true:
|
||
* Extension extensions<0..2^16-1>;
|
||
* };
|
||
* } ServerHello;
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleServerHello = function(c, record, length) {
|
||
var msg = tls.parseHelloMessage(c, record, length);
|
||
if(c.fail) {
|
||
return;
|
||
}
|
||
|
||
// ensure server version is compatible
|
||
if(msg.version.minor <= c.version.minor) {
|
||
c.version.minor = msg.version.minor;
|
||
} else {
|
||
return c.error(c, {
|
||
message: 'Incompatible TLS version.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.protocol_version
|
||
}
|
||
});
|
||
}
|
||
|
||
// indicate session version has been set
|
||
c.session.version = c.version;
|
||
|
||
// get the session ID from the message
|
||
var sessionId = msg.session_id.bytes();
|
||
|
||
// if the session ID is not blank and matches the cached one, resume
|
||
// the session
|
||
if(sessionId.length > 0 && sessionId === c.session.id) {
|
||
// resuming session, expect a ChangeCipherSpec next
|
||
c.expect = SCC;
|
||
c.session.resuming = true;
|
||
|
||
// get new server random
|
||
c.session.sp.server_random = msg.random.bytes();
|
||
} else {
|
||
// not resuming, expect a server Certificate message next
|
||
c.expect = SCE;
|
||
c.session.resuming = false;
|
||
|
||
// create new security parameters
|
||
tls.createSecurityParameters(c, msg);
|
||
}
|
||
|
||
// set new session ID
|
||
c.session.id = sessionId;
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a server receives a ClientHello record.
|
||
*
|
||
* When a ClientHello message will be sent:
|
||
* When a client first connects to a server it is required to send the
|
||
* client hello as its first message. The client can also send a client
|
||
* hello in response to a hello request or on its own initiative in order
|
||
* to renegotiate the security parameters in an existing connection.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleClientHello = function(c, record, length) {
|
||
var msg = tls.parseHelloMessage(c, record, length);
|
||
if(c.fail) {
|
||
return;
|
||
}
|
||
|
||
// get the session ID from the message
|
||
var sessionId = msg.session_id.bytes();
|
||
|
||
// see if the given session ID is in the cache
|
||
var session = null;
|
||
if(c.sessionCache) {
|
||
session = c.sessionCache.getSession(sessionId);
|
||
if(session === null) {
|
||
// session ID not found
|
||
sessionId = '';
|
||
} else if(session.version.major !== msg.version.major ||
|
||
session.version.minor > msg.version.minor) {
|
||
// if session version is incompatible with client version, do not resume
|
||
session = null;
|
||
sessionId = '';
|
||
}
|
||
}
|
||
|
||
// no session found to resume, generate a new session ID
|
||
if(sessionId.length === 0) {
|
||
sessionId = forge.random.getBytes(32);
|
||
}
|
||
|
||
// update session
|
||
c.session.id = sessionId;
|
||
c.session.clientHelloVersion = msg.version;
|
||
c.session.sp = {};
|
||
if(session) {
|
||
// use version and security parameters from resumed session
|
||
c.version = c.session.version = session.version;
|
||
c.session.sp = session.sp;
|
||
} else {
|
||
// use highest compatible minor version
|
||
var version;
|
||
for(var i = 1; i < tls.SupportedVersions.length; ++i) {
|
||
version = tls.SupportedVersions[i];
|
||
if(version.minor <= msg.version.minor) {
|
||
break;
|
||
}
|
||
}
|
||
c.version = {major: version.major, minor: version.minor};
|
||
c.session.version = c.version;
|
||
}
|
||
|
||
// if a session is set, resume it
|
||
if(session !== null) {
|
||
// resuming session, expect a ChangeCipherSpec next
|
||
c.expect = CCC;
|
||
c.session.resuming = true;
|
||
|
||
// get new client random
|
||
c.session.sp.client_random = msg.random.bytes();
|
||
} else {
|
||
// not resuming, expect a Certificate or ClientKeyExchange
|
||
c.expect = (c.verifyClient !== false) ? CCE : CKE;
|
||
c.session.resuming = false;
|
||
|
||
// create new security parameters
|
||
tls.createSecurityParameters(c, msg);
|
||
}
|
||
|
||
// connection now open
|
||
c.open = true;
|
||
|
||
// queue server hello
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createServerHello(c)
|
||
}));
|
||
|
||
if(c.session.resuming) {
|
||
// queue change cipher spec message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.change_cipher_spec,
|
||
data: tls.createChangeCipherSpec()
|
||
}));
|
||
|
||
// create pending state
|
||
c.state.pending = tls.createConnectionState(c);
|
||
|
||
// change current write state to pending write state
|
||
c.state.current.write = c.state.pending.write;
|
||
|
||
// queue finished
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createFinished(c)
|
||
}));
|
||
} else {
|
||
// queue server certificate
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createCertificate(c)
|
||
}));
|
||
|
||
if(!c.fail) {
|
||
// queue server key exchange
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createServerKeyExchange(c)
|
||
}));
|
||
|
||
// request client certificate if set
|
||
if(c.verifyClient !== false) {
|
||
// queue certificate request
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createCertificateRequest(c)
|
||
}));
|
||
}
|
||
|
||
// queue server hello done
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createServerHelloDone(c)
|
||
}));
|
||
}
|
||
}
|
||
|
||
// send records
|
||
tls.flush(c);
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a Certificate record.
|
||
*
|
||
* When this message will be sent:
|
||
* The server must send a certificate whenever the agreed-upon key exchange
|
||
* method is not an anonymous one. This message will always immediately
|
||
* follow the server hello message.
|
||
*
|
||
* Meaning of this message:
|
||
* The certificate type must be appropriate for the selected cipher suite's
|
||
* key exchange algorithm, and is generally an X.509v3 certificate. It must
|
||
* contain a key which matches the key exchange method, as follows. Unless
|
||
* otherwise specified, the signing algorithm for the certificate must be
|
||
* the same as the algorithm for the certificate key. Unless otherwise
|
||
* specified, the public key may be of any length.
|
||
*
|
||
* opaque ASN.1Cert<1..2^24-1>;
|
||
* struct {
|
||
* ASN.1Cert certificate_list<1..2^24-1>;
|
||
* } Certificate;
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleCertificate = function(c, record, length) {
|
||
// minimum of 3 bytes in message
|
||
if(length < 3) {
|
||
return c.error(c, {
|
||
message: 'Invalid Certificate message. Message too short.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
}
|
||
|
||
var b = record.fragment;
|
||
var msg = {
|
||
certificate_list: readVector(b, 3)
|
||
};
|
||
|
||
/* The sender's certificate will be first in the list (chain), each
|
||
subsequent one that follows will certify the previous one, but root
|
||
certificates (self-signed) that specify the certificate authority may
|
||
be omitted under the assumption that clients must already possess it. */
|
||
var cert, asn1;
|
||
var certs = [];
|
||
try {
|
||
while(msg.certificate_list.length() > 0) {
|
||
// each entry in msg.certificate_list is a vector with 3 len bytes
|
||
cert = readVector(msg.certificate_list, 3);
|
||
asn1 = forge.asn1.fromDer(cert);
|
||
cert = forge.pki.certificateFromAsn1(asn1, true);
|
||
certs.push(cert);
|
||
}
|
||
} catch(ex) {
|
||
return c.error(c, {
|
||
message: 'Could not parse certificate list.',
|
||
cause: ex,
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.bad_certificate
|
||
}
|
||
});
|
||
}
|
||
|
||
// ensure at least 1 certificate was provided if in client-mode
|
||
// or if verifyClient was set to true to require a certificate
|
||
// (as opposed to 'optional')
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
if((client || c.verifyClient === true) && certs.length === 0) {
|
||
// error, no certificate
|
||
c.error(c, {
|
||
message: client ?
|
||
'No server certificate provided.' :
|
||
'No client certificate provided.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
} else if(certs.length === 0) {
|
||
// no certs to verify
|
||
// expect a ServerKeyExchange or ClientKeyExchange message next
|
||
c.expect = client ? SKE : CKE;
|
||
} else {
|
||
// save certificate in session
|
||
if(client) {
|
||
c.session.serverCertificate = certs[0];
|
||
} else {
|
||
c.session.clientCertificate = certs[0];
|
||
}
|
||
|
||
if(tls.verifyCertificateChain(c, certs)) {
|
||
// expect a ServerKeyExchange or ClientKeyExchange message next
|
||
c.expect = client ? SKE : CKE;
|
||
}
|
||
}
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a ServerKeyExchange record.
|
||
*
|
||
* When this message will be sent:
|
||
* This message will be sent immediately after the server certificate
|
||
* message (or the server hello message, if this is an anonymous
|
||
* negotiation).
|
||
*
|
||
* The server key exchange message is sent by the server only when the
|
||
* server certificate message (if sent) does not contain enough data to
|
||
* allow the client to exchange a premaster secret.
|
||
*
|
||
* Meaning of this message:
|
||
* This message conveys cryptographic information to allow the client to
|
||
* communicate the premaster secret: either an RSA public key to encrypt
|
||
* the premaster secret with, or a Diffie-Hellman public key with which the
|
||
* client can complete a key exchange (with the result being the premaster
|
||
* secret.)
|
||
*
|
||
* enum {
|
||
* dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa
|
||
* } KeyExchangeAlgorithm;
|
||
*
|
||
* struct {
|
||
* opaque dh_p<1..2^16-1>;
|
||
* opaque dh_g<1..2^16-1>;
|
||
* opaque dh_Ys<1..2^16-1>;
|
||
* } ServerDHParams;
|
||
*
|
||
* struct {
|
||
* select(KeyExchangeAlgorithm) {
|
||
* case dh_anon:
|
||
* ServerDHParams params;
|
||
* case dhe_dss:
|
||
* case dhe_rsa:
|
||
* ServerDHParams params;
|
||
* digitally-signed struct {
|
||
* opaque client_random[32];
|
||
* opaque server_random[32];
|
||
* ServerDHParams params;
|
||
* } signed_params;
|
||
* case rsa:
|
||
* case dh_dss:
|
||
* case dh_rsa:
|
||
* struct {};
|
||
* };
|
||
* } ServerKeyExchange;
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleServerKeyExchange = function(c, record, length) {
|
||
// this implementation only supports RSA, no Diffie-Hellman support
|
||
// so any length > 0 is invalid
|
||
if(length > 0) {
|
||
return c.error(c, {
|
||
message: 'Invalid key parameters. Only RSA is supported.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.unsupported_certificate
|
||
}
|
||
});
|
||
}
|
||
|
||
// expect an optional CertificateRequest message next
|
||
c.expect = SCR;
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a ClientKeyExchange record.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleClientKeyExchange = function(c, record, length) {
|
||
// this implementation only supports RSA, no Diffie-Hellman support
|
||
// so any length < 48 is invalid
|
||
if(length < 48) {
|
||
return c.error(c, {
|
||
message: 'Invalid key parameters. Only RSA is supported.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.unsupported_certificate
|
||
}
|
||
});
|
||
}
|
||
|
||
var b = record.fragment;
|
||
var msg = {
|
||
enc_pre_master_secret: readVector(b, 2).getBytes()
|
||
};
|
||
|
||
// do rsa decryption
|
||
var privateKey = null;
|
||
if(c.getPrivateKey) {
|
||
try {
|
||
privateKey = c.getPrivateKey(c, c.session.serverCertificate);
|
||
privateKey = forge.pki.privateKeyFromPem(privateKey);
|
||
} catch(ex) {
|
||
c.error(c, {
|
||
message: 'Could not get private key.',
|
||
cause: ex,
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
if(privateKey === null) {
|
||
return c.error(c, {
|
||
message: 'No private key set.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
}
|
||
|
||
try {
|
||
// decrypt 48-byte pre-master secret
|
||
var sp = c.session.sp;
|
||
sp.pre_master_secret = privateKey.decrypt(msg.enc_pre_master_secret);
|
||
|
||
// ensure client hello version matches first 2 bytes
|
||
var version = c.session.clientHelloVersion;
|
||
if(version.major !== sp.pre_master_secret.charCodeAt(0) ||
|
||
version.minor !== sp.pre_master_secret.charCodeAt(1)) {
|
||
// error, do not send alert (see BLEI attack below)
|
||
throw new Error('TLS version rollback attack detected.');
|
||
}
|
||
} catch(ex) {
|
||
/* Note: Daniel Bleichenbacher [BLEI] can be used to attack a
|
||
TLS server which is using PKCS#1 encoded RSA, so instead of
|
||
failing here, we generate 48 random bytes and use that as
|
||
the pre-master secret. */
|
||
sp.pre_master_secret = forge.random.getBytes(48);
|
||
}
|
||
|
||
// expect a CertificateVerify message if a Certificate was received that
|
||
// does not have fixed Diffie-Hellman params, otherwise expect
|
||
// ChangeCipherSpec
|
||
c.expect = CCC;
|
||
if(c.session.clientCertificate !== null) {
|
||
// only RSA support, so expect CertificateVerify
|
||
// TODO: support Diffie-Hellman
|
||
c.expect = CCV;
|
||
}
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a CertificateRequest record.
|
||
*
|
||
* When this message will be sent:
|
||
* A non-anonymous server can optionally request a certificate from the
|
||
* client, if appropriate for the selected cipher suite. This message, if
|
||
* sent, will immediately follow the Server Key Exchange message (if it is
|
||
* sent; otherwise, the Server Certificate message).
|
||
*
|
||
* enum {
|
||
* rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
|
||
* rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
|
||
* fortezza_dms_RESERVED(20), (255)
|
||
* } ClientCertificateType;
|
||
*
|
||
* opaque DistinguishedName<1..2^16-1>;
|
||
*
|
||
* struct {
|
||
* ClientCertificateType certificate_types<1..2^8-1>;
|
||
* SignatureAndHashAlgorithm supported_signature_algorithms<2^16-1>;
|
||
* DistinguishedName certificate_authorities<0..2^16-1>;
|
||
* } CertificateRequest;
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleCertificateRequest = function(c, record, length) {
|
||
// minimum of 3 bytes in message
|
||
if(length < 3) {
|
||
return c.error(c, {
|
||
message: 'Invalid CertificateRequest. Message too short.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
}
|
||
|
||
// TODO: TLS 1.2+ has different format including
|
||
// SignatureAndHashAlgorithm after cert types
|
||
var b = record.fragment;
|
||
var msg = {
|
||
certificate_types: readVector(b, 1),
|
||
certificate_authorities: readVector(b, 2)
|
||
};
|
||
|
||
// save certificate request in session
|
||
c.session.certificateRequest = msg;
|
||
|
||
// expect a ServerHelloDone message next
|
||
c.expect = SHD;
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a server receives a CertificateVerify record.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleCertificateVerify = function(c, record, length) {
|
||
if(length < 2) {
|
||
return c.error(c, {
|
||
message: 'Invalid CertificateVerify. Message too short.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
}
|
||
|
||
// rewind to get full bytes for message so it can be manually
|
||
// digested below (special case for CertificateVerify messages because
|
||
// they must be digested *after* handling as opposed to all others)
|
||
var b = record.fragment;
|
||
b.read -= 4;
|
||
var msgBytes = b.bytes();
|
||
b.read += 4;
|
||
|
||
var msg = {
|
||
signature: readVector(b, 2).getBytes()
|
||
};
|
||
|
||
// TODO: add support for DSA
|
||
|
||
// generate data to verify
|
||
var verify = forge.util.createBuffer();
|
||
verify.putBuffer(c.session.md5.digest());
|
||
verify.putBuffer(c.session.sha1.digest());
|
||
verify = verify.getBytes();
|
||
|
||
try {
|
||
var cert = c.session.clientCertificate;
|
||
/*b = forge.pki.rsa.decrypt(
|
||
msg.signature, cert.publicKey, true, verify.length);
|
||
if(b !== verify) {*/
|
||
if(!cert.publicKey.verify(verify, msg.signature, 'NONE')) {
|
||
throw new Error('CertificateVerify signature does not match.');
|
||
}
|
||
|
||
// digest message now that it has been handled
|
||
c.session.md5.update(msgBytes);
|
||
c.session.sha1.update(msgBytes);
|
||
} catch(ex) {
|
||
return c.error(c, {
|
||
message: 'Bad signature in CertificateVerify.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.handshake_failure
|
||
}
|
||
});
|
||
}
|
||
|
||
// expect ChangeCipherSpec
|
||
c.expect = CCC;
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a client receives a ServerHelloDone record.
|
||
*
|
||
* When this message will be sent:
|
||
* The server hello done message is sent by the server to indicate the end
|
||
* of the server hello and associated messages. After sending this message
|
||
* the server will wait for a client response.
|
||
*
|
||
* Meaning of this message:
|
||
* This message means that the server is done sending messages to support
|
||
* the key exchange, and the client can proceed with its phase of the key
|
||
* exchange.
|
||
*
|
||
* Upon receipt of the server hello done message the client should verify
|
||
* that the server provided a valid certificate if required and check that
|
||
* the server hello parameters are acceptable.
|
||
*
|
||
* struct {} ServerHelloDone;
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleServerHelloDone = function(c, record, length) {
|
||
// len must be 0 bytes
|
||
if(length > 0) {
|
||
return c.error(c, {
|
||
message: 'Invalid ServerHelloDone message. Invalid length.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.record_overflow
|
||
}
|
||
});
|
||
}
|
||
|
||
if(c.serverCertificate === null) {
|
||
// no server certificate was provided
|
||
var error = {
|
||
message: 'No server certificate provided. Not enough security.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.insufficient_security
|
||
}
|
||
};
|
||
|
||
// call application callback
|
||
var depth = 0;
|
||
var ret = c.verify(c, error.alert.description, depth, []);
|
||
if(ret !== true) {
|
||
// check for custom alert info
|
||
if(ret || ret === 0) {
|
||
// set custom message and alert description
|
||
if(typeof ret === 'object' && !forge.util.isArray(ret)) {
|
||
if(ret.message) {
|
||
error.message = ret.message;
|
||
}
|
||
if(ret.alert) {
|
||
error.alert.description = ret.alert;
|
||
}
|
||
} else if(typeof ret === 'number') {
|
||
// set custom alert description
|
||
error.alert.description = ret;
|
||
}
|
||
}
|
||
|
||
// send error
|
||
return c.error(c, error);
|
||
}
|
||
}
|
||
|
||
// create client certificate message if requested
|
||
if(c.session.certificateRequest !== null) {
|
||
record = tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createCertificate(c)
|
||
});
|
||
tls.queue(c, record);
|
||
}
|
||
|
||
// create client key exchange message
|
||
record = tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createClientKeyExchange(c)
|
||
});
|
||
tls.queue(c, record);
|
||
|
||
// expect no messages until the following callback has been called
|
||
c.expect = SER;
|
||
|
||
// create callback to handle client signature (for client-certs)
|
||
var callback = function(c, signature) {
|
||
if(c.session.certificateRequest !== null &&
|
||
c.session.clientCertificate !== null) {
|
||
// create certificate verify message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createCertificateVerify(c, signature)
|
||
}));
|
||
}
|
||
|
||
// create change cipher spec message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.change_cipher_spec,
|
||
data: tls.createChangeCipherSpec()
|
||
}));
|
||
|
||
// create pending state
|
||
c.state.pending = tls.createConnectionState(c);
|
||
|
||
// change current write state to pending write state
|
||
c.state.current.write = c.state.pending.write;
|
||
|
||
// create finished message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createFinished(c)
|
||
}));
|
||
|
||
// expect a server ChangeCipherSpec message next
|
||
c.expect = SCC;
|
||
|
||
// send records
|
||
tls.flush(c);
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
// if there is no certificate request or no client certificate, do
|
||
// callback immediately
|
||
if(c.session.certificateRequest === null ||
|
||
c.session.clientCertificate === null) {
|
||
return callback(c, null);
|
||
}
|
||
|
||
// otherwise get the client signature
|
||
tls.getClientSignature(c, callback);
|
||
};
|
||
|
||
/**
|
||
* Called when a ChangeCipherSpec record is received.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleChangeCipherSpec = function(c, record) {
|
||
if(record.fragment.getByte() !== 0x01) {
|
||
return c.error(c, {
|
||
message: 'Invalid ChangeCipherSpec message received.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.illegal_parameter
|
||
}
|
||
});
|
||
}
|
||
|
||
// create pending state if:
|
||
// 1. Resuming session in client mode OR
|
||
// 2. NOT resuming session in server mode
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
if((c.session.resuming && client) || (!c.session.resuming && !client)) {
|
||
c.state.pending = tls.createConnectionState(c);
|
||
}
|
||
|
||
// change current read state to pending read state
|
||
c.state.current.read = c.state.pending.read;
|
||
|
||
// clear pending state if:
|
||
// 1. NOT resuming session in client mode OR
|
||
// 2. resuming a session in server mode
|
||
if((!c.session.resuming && client) || (c.session.resuming && !client)) {
|
||
c.state.pending = null;
|
||
}
|
||
|
||
// expect a Finished record next
|
||
c.expect = client ? SFI : CFI;
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a Finished record is received.
|
||
*
|
||
* When this message will be sent:
|
||
* A finished message is always sent immediately after a change
|
||
* cipher spec message to verify that the key exchange and
|
||
* authentication processes were successful. It is essential that a
|
||
* change cipher spec message be received between the other
|
||
* handshake messages and the Finished message.
|
||
*
|
||
* Meaning of this message:
|
||
* The finished message is the first protected with the just-
|
||
* negotiated algorithms, keys, and secrets. Recipients of finished
|
||
* messages must verify that the contents are correct. Once a side
|
||
* has sent its Finished message and received and validated the
|
||
* Finished message from its peer, it may begin to send and receive
|
||
* application data over the connection.
|
||
*
|
||
* struct {
|
||
* opaque verify_data[verify_data_length];
|
||
* } Finished;
|
||
*
|
||
* verify_data
|
||
* PRF(master_secret, finished_label, Hash(handshake_messages))
|
||
* [0..verify_data_length-1];
|
||
*
|
||
* finished_label
|
||
* For Finished messages sent by the client, the string
|
||
* "client finished". For Finished messages sent by the server, the
|
||
* string "server finished".
|
||
*
|
||
* verify_data_length depends on the cipher suite. If it is not specified
|
||
* by the cipher suite, then it is 12. Versions of TLS < 1.2 always used
|
||
* 12 bytes.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
* @param length the length of the handshake message.
|
||
*/
|
||
tls.handleFinished = function(c, record, length) {
|
||
// rewind to get full bytes for message so it can be manually
|
||
// digested below (special case for Finished messages because they
|
||
// must be digested *after* handling as opposed to all others)
|
||
var b = record.fragment;
|
||
b.read -= 4;
|
||
var msgBytes = b.bytes();
|
||
b.read += 4;
|
||
|
||
// message contains only verify_data
|
||
var vd = record.fragment.getBytes();
|
||
|
||
// ensure verify data is correct
|
||
b = forge.util.createBuffer();
|
||
b.putBuffer(c.session.md5.digest());
|
||
b.putBuffer(c.session.sha1.digest());
|
||
|
||
// set label based on entity type
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
var label = client ? 'server finished' : 'client finished';
|
||
|
||
// TODO: determine prf function and verify length for TLS 1.2
|
||
var sp = c.session.sp;
|
||
var vdl = 12;
|
||
var prf = prf_TLS1;
|
||
b = prf(sp.master_secret, label, b.getBytes(), vdl);
|
||
if(b.getBytes() !== vd) {
|
||
return c.error(c, {
|
||
message: 'Invalid verify_data in Finished message.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.decrypt_error
|
||
}
|
||
});
|
||
}
|
||
|
||
// digest finished message now that it has been handled
|
||
c.session.md5.update(msgBytes);
|
||
c.session.sha1.update(msgBytes);
|
||
|
||
// resuming session as client or NOT resuming session as server
|
||
if((c.session.resuming && client) || (!c.session.resuming && !client)) {
|
||
// create change cipher spec message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.change_cipher_spec,
|
||
data: tls.createChangeCipherSpec()
|
||
}));
|
||
|
||
// change current write state to pending write state, clear pending
|
||
c.state.current.write = c.state.pending.write;
|
||
c.state.pending = null;
|
||
|
||
// create finished message
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createFinished(c)
|
||
}));
|
||
}
|
||
|
||
// expect application data next
|
||
c.expect = client ? SAD : CAD;
|
||
|
||
// handshake complete
|
||
c.handshaking = false;
|
||
++c.handshakes;
|
||
|
||
// save access to peer certificate
|
||
c.peerCertificate = client ?
|
||
c.session.serverCertificate : c.session.clientCertificate;
|
||
|
||
// send records
|
||
tls.flush(c);
|
||
|
||
// now connected
|
||
c.isConnected = true;
|
||
c.connected(c);
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when an Alert record is received.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleAlert = function(c, record) {
|
||
// read alert
|
||
var b = record.fragment;
|
||
var alert = {
|
||
level: b.getByte(),
|
||
description: b.getByte()
|
||
};
|
||
|
||
// TODO: consider using a table?
|
||
// get appropriate message
|
||
var msg;
|
||
switch(alert.description) {
|
||
case tls.Alert.Description.close_notify:
|
||
msg = 'Connection closed.';
|
||
break;
|
||
case tls.Alert.Description.unexpected_message:
|
||
msg = 'Unexpected message.';
|
||
break;
|
||
case tls.Alert.Description.bad_record_mac:
|
||
msg = 'Bad record MAC.';
|
||
break;
|
||
case tls.Alert.Description.decryption_failed:
|
||
msg = 'Decryption failed.';
|
||
break;
|
||
case tls.Alert.Description.record_overflow:
|
||
msg = 'Record overflow.';
|
||
break;
|
||
case tls.Alert.Description.decompression_failure:
|
||
msg = 'Decompression failed.';
|
||
break;
|
||
case tls.Alert.Description.handshake_failure:
|
||
msg = 'Handshake failure.';
|
||
break;
|
||
case tls.Alert.Description.bad_certificate:
|
||
msg = 'Bad certificate.';
|
||
break;
|
||
case tls.Alert.Description.unsupported_certificate:
|
||
msg = 'Unsupported certificate.';
|
||
break;
|
||
case tls.Alert.Description.certificate_revoked:
|
||
msg = 'Certificate revoked.';
|
||
break;
|
||
case tls.Alert.Description.certificate_expired:
|
||
msg = 'Certificate expired.';
|
||
break;
|
||
case tls.Alert.Description.certificate_unknown:
|
||
msg = 'Certificate unknown.';
|
||
break;
|
||
case tls.Alert.Description.illegal_parameter:
|
||
msg = 'Illegal parameter.';
|
||
break;
|
||
case tls.Alert.Description.unknown_ca:
|
||
msg = 'Unknown certificate authority.';
|
||
break;
|
||
case tls.Alert.Description.access_denied:
|
||
msg = 'Access denied.';
|
||
break;
|
||
case tls.Alert.Description.decode_error:
|
||
msg = 'Decode error.';
|
||
break;
|
||
case tls.Alert.Description.decrypt_error:
|
||
msg = 'Decrypt error.';
|
||
break;
|
||
case tls.Alert.Description.export_restriction:
|
||
msg = 'Export restriction.';
|
||
break;
|
||
case tls.Alert.Description.protocol_version:
|
||
msg = 'Unsupported protocol version.';
|
||
break;
|
||
case tls.Alert.Description.insufficient_security:
|
||
msg = 'Insufficient security.';
|
||
break;
|
||
case tls.Alert.Description.internal_error:
|
||
msg = 'Internal error.';
|
||
break;
|
||
case tls.Alert.Description.user_canceled:
|
||
msg = 'User canceled.';
|
||
break;
|
||
case tls.Alert.Description.no_renegotiation:
|
||
msg = 'Renegotiation not supported.';
|
||
break;
|
||
default:
|
||
msg = 'Unknown error.';
|
||
break;
|
||
}
|
||
|
||
// close connection on close_notify, not an error
|
||
if(alert.description === tls.Alert.Description.close_notify) {
|
||
return c.close();
|
||
}
|
||
|
||
// call error handler
|
||
c.error(c, {
|
||
message: msg,
|
||
send: false,
|
||
// origin is the opposite end
|
||
origin: (c.entity === tls.ConnectionEnd.client) ? 'server' : 'client',
|
||
alert: alert
|
||
});
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a Handshake record is received.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleHandshake = function(c, record) {
|
||
// get the handshake type and message length
|
||
var b = record.fragment;
|
||
var type = b.getByte();
|
||
var length = b.getInt24();
|
||
|
||
// see if the record fragment doesn't yet contain the full message
|
||
if(length > b.length()) {
|
||
// cache the record, clear its fragment, and reset the buffer read
|
||
// pointer before the type and length were read
|
||
c.fragmented = record;
|
||
record.fragment = forge.util.createBuffer();
|
||
b.read -= 4;
|
||
|
||
// continue
|
||
return c.process();
|
||
}
|
||
|
||
// full message now available, clear cache, reset read pointer to
|
||
// before type and length
|
||
c.fragmented = null;
|
||
b.read -= 4;
|
||
|
||
// save the handshake bytes for digestion after handler is found
|
||
// (include type and length of handshake msg)
|
||
var bytes = b.bytes(length + 4);
|
||
|
||
// restore read pointer
|
||
b.read += 4;
|
||
|
||
// handle expected message
|
||
if(type in hsTable[c.entity][c.expect]) {
|
||
// initialize server session
|
||
if(c.entity === tls.ConnectionEnd.server && !c.open && !c.fail) {
|
||
c.handshaking = true;
|
||
c.session = {
|
||
version: null,
|
||
extensions: {
|
||
server_name: {
|
||
serverNameList: []
|
||
}
|
||
},
|
||
cipherSuite: null,
|
||
compressionMethod: null,
|
||
serverCertificate: null,
|
||
clientCertificate: null,
|
||
md5: forge.md.md5.create(),
|
||
sha1: forge.md.sha1.create()
|
||
};
|
||
}
|
||
|
||
/* Update handshake messages digest. Finished and CertificateVerify
|
||
messages are not digested here. They can't be digested as part of
|
||
the verify_data that they contain. These messages are manually
|
||
digested in their handlers. HelloRequest messages are simply never
|
||
included in the handshake message digest according to spec. */
|
||
if(type !== tls.HandshakeType.hello_request &&
|
||
type !== tls.HandshakeType.certificate_verify &&
|
||
type !== tls.HandshakeType.finished) {
|
||
c.session.md5.update(bytes);
|
||
c.session.sha1.update(bytes);
|
||
}
|
||
|
||
// handle specific handshake type record
|
||
hsTable[c.entity][c.expect][type](c, record, length);
|
||
} else {
|
||
// unexpected record
|
||
tls.handleUnexpected(c, record);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called when an ApplicationData record is received.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleApplicationData = function(c, record) {
|
||
// buffer data, notify that its ready
|
||
c.data.putBuffer(record.fragment);
|
||
c.dataReady(c);
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* Called when a Heartbeat record is received.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record.
|
||
*/
|
||
tls.handleHeartbeat = function(c, record) {
|
||
// get the heartbeat type and payload
|
||
var b = record.fragment;
|
||
var type = b.getByte();
|
||
var length = b.getInt16();
|
||
var payload = b.getBytes(length);
|
||
|
||
if(type === tls.HeartbeatMessageType.heartbeat_request) {
|
||
// discard request during handshake or if length is too large
|
||
if(c.handshaking || length > payload.length) {
|
||
// continue
|
||
return c.process();
|
||
}
|
||
// retransmit payload
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.heartbeat,
|
||
data: tls.createHeartbeat(
|
||
tls.HeartbeatMessageType.heartbeat_response, payload)
|
||
}));
|
||
tls.flush(c);
|
||
} else if(type === tls.HeartbeatMessageType.heartbeat_response) {
|
||
// check payload against expected payload, discard heartbeat if no match
|
||
if(payload !== c.expectedHeartbeatPayload) {
|
||
// continue
|
||
return c.process();
|
||
}
|
||
|
||
// notify that a valid heartbeat was received
|
||
if(c.heartbeatReceived) {
|
||
c.heartbeatReceived(c, forge.util.createBuffer(payload));
|
||
}
|
||
}
|
||
|
||
// continue
|
||
c.process();
|
||
};
|
||
|
||
/**
|
||
* The transistional state tables for receiving TLS records. It maps the
|
||
* current TLS engine state and a received record to a function to handle the
|
||
* record and update the state.
|
||
*
|
||
* For instance, if the current state is SHE, then the TLS engine is expecting
|
||
* a ServerHello record. Once a record is received, the handler function is
|
||
* looked up using the state SHE and the record's content type.
|
||
*
|
||
* The resulting function will either be an error handler or a record handler.
|
||
* The function will take whatever action is appropriate and update the state
|
||
* for the next record.
|
||
*
|
||
* The states are all based on possible server record types. Note that the
|
||
* client will never specifically expect to receive a HelloRequest or an alert
|
||
* from the server so there is no state that reflects this. These messages may
|
||
* occur at any time.
|
||
*
|
||
* There are two tables for mapping states because there is a second tier of
|
||
* types for handshake messages. Once a record with a content type of handshake
|
||
* is received, the handshake record handler will look up the handshake type in
|
||
* the secondary map to get its appropriate handler.
|
||
*
|
||
* Valid message orders are as follows:
|
||
*
|
||
* =======================FULL HANDSHAKE======================
|
||
* Client Server
|
||
*
|
||
* ClientHello -------->
|
||
* ServerHello
|
||
* Certificate*
|
||
* ServerKeyExchange*
|
||
* CertificateRequest*
|
||
* <-------- ServerHelloDone
|
||
* Certificate*
|
||
* ClientKeyExchange
|
||
* CertificateVerify*
|
||
* [ChangeCipherSpec]
|
||
* Finished -------->
|
||
* [ChangeCipherSpec]
|
||
* <-------- Finished
|
||
* Application Data <-------> Application Data
|
||
*
|
||
* =====================SESSION RESUMPTION=====================
|
||
* Client Server
|
||
*
|
||
* ClientHello -------->
|
||
* ServerHello
|
||
* [ChangeCipherSpec]
|
||
* <-------- Finished
|
||
* [ChangeCipherSpec]
|
||
* Finished -------->
|
||
* Application Data <-------> Application Data
|
||
*/
|
||
// client expect states (indicate which records are expected to be received)
|
||
var SHE = 0; // rcv server hello
|
||
var SCE = 1; // rcv server certificate
|
||
var SKE = 2; // rcv server key exchange
|
||
var SCR = 3; // rcv certificate request
|
||
var SHD = 4; // rcv server hello done
|
||
var SCC = 5; // rcv change cipher spec
|
||
var SFI = 6; // rcv finished
|
||
var SAD = 7; // rcv application data
|
||
var SER = 8; // not expecting any messages at this point
|
||
|
||
// server expect states
|
||
var CHE = 0; // rcv client hello
|
||
var CCE = 1; // rcv client certificate
|
||
var CKE = 2; // rcv client key exchange
|
||
var CCV = 3; // rcv certificate verify
|
||
var CCC = 4; // rcv change cipher spec
|
||
var CFI = 5; // rcv finished
|
||
var CAD = 6; // rcv application data
|
||
var CER = 7; // not expecting any messages at this point
|
||
|
||
// map client current expect state and content type to function
|
||
var __ = tls.handleUnexpected;
|
||
var R0 = tls.handleChangeCipherSpec;
|
||
var R1 = tls.handleAlert;
|
||
var R2 = tls.handleHandshake;
|
||
var R3 = tls.handleApplicationData;
|
||
var R4 = tls.handleHeartbeat;
|
||
var ctTable = [];
|
||
ctTable[tls.ConnectionEnd.client] = [
|
||
// CC,AL,HS,AD,HB
|
||
/*SHE*/[__,R1,R2,__,R4],
|
||
/*SCE*/[__,R1,R2,__,R4],
|
||
/*SKE*/[__,R1,R2,__,R4],
|
||
/*SCR*/[__,R1,R2,__,R4],
|
||
/*SHD*/[__,R1,R2,__,R4],
|
||
/*SCC*/[R0,R1,__,__,R4],
|
||
/*SFI*/[__,R1,R2,__,R4],
|
||
/*SAD*/[__,R1,R2,R3,R4],
|
||
/*SER*/[__,R1,R2,__,R4]
|
||
];
|
||
|
||
// map server current expect state and content type to function
|
||
ctTable[tls.ConnectionEnd.server] = [
|
||
// CC,AL,HS,AD
|
||
/*CHE*/[__,R1,R2,__,R4],
|
||
/*CCE*/[__,R1,R2,__,R4],
|
||
/*CKE*/[__,R1,R2,__,R4],
|
||
/*CCV*/[__,R1,R2,__,R4],
|
||
/*CCC*/[R0,R1,__,__,R4],
|
||
/*CFI*/[__,R1,R2,__,R4],
|
||
/*CAD*/[__,R1,R2,R3,R4],
|
||
/*CER*/[__,R1,R2,__,R4]
|
||
];
|
||
|
||
// map client current expect state and handshake type to function
|
||
var H0 = tls.handleHelloRequest;
|
||
var H1 = tls.handleServerHello;
|
||
var H2 = tls.handleCertificate;
|
||
var H3 = tls.handleServerKeyExchange;
|
||
var H4 = tls.handleCertificateRequest;
|
||
var H5 = tls.handleServerHelloDone;
|
||
var H6 = tls.handleFinished;
|
||
var hsTable = [];
|
||
hsTable[tls.ConnectionEnd.client] = [
|
||
// HR,01,SH,03,04,05,06,07,08,09,10,SC,SK,CR,HD,15,CK,17,18,19,FI
|
||
/*SHE*/[__,__,H1,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*SCE*/[H0,__,__,__,__,__,__,__,__,__,__,H2,H3,H4,H5,__,__,__,__,__,__],
|
||
/*SKE*/[H0,__,__,__,__,__,__,__,__,__,__,__,H3,H4,H5,__,__,__,__,__,__],
|
||
/*SCR*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,H4,H5,__,__,__,__,__,__],
|
||
/*SHD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,H5,__,__,__,__,__,__],
|
||
/*SCC*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*SFI*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
|
||
/*SAD*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*SER*/[H0,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
|
||
];
|
||
|
||
// map server current expect state and handshake type to function
|
||
// Note: CAD[CH] does not map to FB because renegotation is prohibited
|
||
var H7 = tls.handleClientHello;
|
||
var H8 = tls.handleClientKeyExchange;
|
||
var H9 = tls.handleCertificateVerify;
|
||
hsTable[tls.ConnectionEnd.server] = [
|
||
// 01,CH,02,03,04,05,06,07,08,09,10,CC,12,13,14,CV,CK,17,18,19,FI
|
||
/*CHE*/[__,H7,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*CCE*/[__,__,__,__,__,__,__,__,__,__,__,H2,__,__,__,__,__,__,__,__,__],
|
||
/*CKE*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H8,__,__,__,__],
|
||
/*CCV*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H9,__,__,__,__,__],
|
||
/*CCC*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*CFI*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,H6],
|
||
/*CAD*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__],
|
||
/*CER*/[__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__]
|
||
];
|
||
|
||
/**
|
||
* Generates the master_secret and keys using the given security parameters.
|
||
*
|
||
* The security parameters for a TLS connection state are defined as such:
|
||
*
|
||
* struct {
|
||
* ConnectionEnd entity;
|
||
* PRFAlgorithm prf_algorithm;
|
||
* BulkCipherAlgorithm bulk_cipher_algorithm;
|
||
* CipherType cipher_type;
|
||
* uint8 enc_key_length;
|
||
* uint8 block_length;
|
||
* uint8 fixed_iv_length;
|
||
* uint8 record_iv_length;
|
||
* MACAlgorithm mac_algorithm;
|
||
* uint8 mac_length;
|
||
* uint8 mac_key_length;
|
||
* CompressionMethod compression_algorithm;
|
||
* opaque master_secret[48];
|
||
* opaque client_random[32];
|
||
* opaque server_random[32];
|
||
* } SecurityParameters;
|
||
*
|
||
* Note that this definition is from TLS 1.2. In TLS 1.0 some of these
|
||
* parameters are ignored because, for instance, the PRFAlgorithm is a
|
||
* builtin-fixed algorithm combining iterations of MD5 and SHA-1 in TLS 1.0.
|
||
*
|
||
* The Record Protocol requires an algorithm to generate keys required by the
|
||
* current connection state.
|
||
*
|
||
* The master secret is expanded into a sequence of secure bytes, which is then
|
||
* split to a client write MAC key, a server write MAC key, a client write
|
||
* encryption key, and a server write encryption key. In TLS 1.0 a client write
|
||
* IV and server write IV are also generated. Each of these is generated from
|
||
* the byte sequence in that order. Unused values are empty. In TLS 1.2, some
|
||
* AEAD ciphers may additionally require a client write IV and a server write
|
||
* IV (see Section 6.2.3.3).
|
||
*
|
||
* When keys, MAC keys, and IVs are generated, the master secret is used as an
|
||
* entropy source.
|
||
*
|
||
* To generate the key material, compute:
|
||
*
|
||
* master_secret = PRF(pre_master_secret, "master secret",
|
||
* ClientHello.random + ServerHello.random)
|
||
*
|
||
* key_block = PRF(SecurityParameters.master_secret,
|
||
* "key expansion",
|
||
* SecurityParameters.server_random +
|
||
* SecurityParameters.client_random);
|
||
*
|
||
* until enough output has been generated. Then, the key_block is
|
||
* partitioned as follows:
|
||
*
|
||
* client_write_MAC_key[SecurityParameters.mac_key_length]
|
||
* server_write_MAC_key[SecurityParameters.mac_key_length]
|
||
* client_write_key[SecurityParameters.enc_key_length]
|
||
* server_write_key[SecurityParameters.enc_key_length]
|
||
* client_write_IV[SecurityParameters.fixed_iv_length]
|
||
* server_write_IV[SecurityParameters.fixed_iv_length]
|
||
*
|
||
* In TLS 1.2, the client_write_IV and server_write_IV are only generated for
|
||
* implicit nonce techniques as described in Section 3.2.1 of [AEAD]. This
|
||
* implementation uses TLS 1.0 so IVs are generated.
|
||
*
|
||
* Implementation note: The currently defined cipher suite which requires the
|
||
* most material is AES_256_CBC_SHA256. It requires 2 x 32 byte keys and 2 x 32
|
||
* byte MAC keys, for a total 128 bytes of key material. In TLS 1.0 it also
|
||
* requires 2 x 16 byte IVs, so it actually takes 160 bytes of key material.
|
||
*
|
||
* @param c the connection.
|
||
* @param sp the security parameters to use.
|
||
*
|
||
* @return the security keys.
|
||
*/
|
||
tls.generateKeys = function(c, sp) {
|
||
// TLS_RSA_WITH_AES_128_CBC_SHA (required to be compliant with TLS 1.2) &
|
||
// TLS_RSA_WITH_AES_256_CBC_SHA are the only cipher suites implemented
|
||
// at present
|
||
|
||
// TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA is required to be compliant with
|
||
// TLS 1.0 but we don't care right now because AES is better and we have
|
||
// an implementation for it
|
||
|
||
// TODO: TLS 1.2 implementation
|
||
/*
|
||
// determine the PRF
|
||
var prf;
|
||
switch(sp.prf_algorithm) {
|
||
case tls.PRFAlgorithm.tls_prf_sha256:
|
||
prf = prf_sha256;
|
||
break;
|
||
default:
|
||
// should never happen
|
||
throw new Error('Invalid PRF');
|
||
}
|
||
*/
|
||
|
||
// TLS 1.0/1.1 implementation
|
||
var prf = prf_TLS1;
|
||
|
||
// concatenate server and client random
|
||
var random = sp.client_random + sp.server_random;
|
||
|
||
// only create master secret if session is new
|
||
if(!c.session.resuming) {
|
||
// create master secret, clean up pre-master secret
|
||
sp.master_secret = prf(
|
||
sp.pre_master_secret, 'master secret', random, 48).bytes();
|
||
sp.pre_master_secret = null;
|
||
}
|
||
|
||
// generate the amount of key material needed
|
||
random = sp.server_random + sp.client_random;
|
||
var length = 2 * sp.mac_key_length + 2 * sp.enc_key_length;
|
||
|
||
// include IV for TLS/1.0
|
||
var tls10 = (c.version.major === tls.Versions.TLS_1_0.major &&
|
||
c.version.minor === tls.Versions.TLS_1_0.minor);
|
||
if(tls10) {
|
||
length += 2 * sp.fixed_iv_length;
|
||
}
|
||
var km = prf(sp.master_secret, 'key expansion', random, length);
|
||
|
||
// split the key material into the MAC and encryption keys
|
||
var rval = {
|
||
client_write_MAC_key: km.getBytes(sp.mac_key_length),
|
||
server_write_MAC_key: km.getBytes(sp.mac_key_length),
|
||
client_write_key: km.getBytes(sp.enc_key_length),
|
||
server_write_key: km.getBytes(sp.enc_key_length)
|
||
};
|
||
|
||
// include TLS 1.0 IVs
|
||
if(tls10) {
|
||
rval.client_write_IV = km.getBytes(sp.fixed_iv_length);
|
||
rval.server_write_IV = km.getBytes(sp.fixed_iv_length);
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a new initialized TLS connection state. A connection state has
|
||
* a read mode and a write mode.
|
||
*
|
||
* compression state:
|
||
* The current state of the compression algorithm.
|
||
*
|
||
* cipher state:
|
||
* The current state of the encryption algorithm. This will consist of the
|
||
* scheduled key for that connection. For stream ciphers, this will also
|
||
* contain whatever state information is necessary to allow the stream to
|
||
* continue to encrypt or decrypt data.
|
||
*
|
||
* MAC key:
|
||
* The MAC key for the connection.
|
||
*
|
||
* sequence number:
|
||
* Each connection state contains a sequence number, which is maintained
|
||
* separately for read and write states. The sequence number MUST be set to
|
||
* zero whenever a connection state is made the active state. Sequence
|
||
* numbers are of type uint64 and may not exceed 2^64-1. Sequence numbers do
|
||
* not wrap. If a TLS implementation would need to wrap a sequence number,
|
||
* it must renegotiate instead. A sequence number is incremented after each
|
||
* record: specifically, the first record transmitted under a particular
|
||
* connection state MUST use sequence number 0.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the new initialized TLS connection state.
|
||
*/
|
||
tls.createConnectionState = function(c) {
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
|
||
var createMode = function() {
|
||
var mode = {
|
||
// two 32-bit numbers, first is most significant
|
||
sequenceNumber: [0, 0],
|
||
macKey: null,
|
||
macLength: 0,
|
||
macFunction: null,
|
||
cipherState: null,
|
||
cipherFunction: function(record) {return true;},
|
||
compressionState: null,
|
||
compressFunction: function(record) {return true;},
|
||
updateSequenceNumber: function() {
|
||
if(mode.sequenceNumber[1] === 0xFFFFFFFF) {
|
||
mode.sequenceNumber[1] = 0;
|
||
++mode.sequenceNumber[0];
|
||
} else {
|
||
++mode.sequenceNumber[1];
|
||
}
|
||
}
|
||
};
|
||
return mode;
|
||
};
|
||
var state = {
|
||
read: createMode(),
|
||
write: createMode()
|
||
};
|
||
|
||
// update function in read mode will decrypt then decompress a record
|
||
state.read.update = function(c, record) {
|
||
if(!state.read.cipherFunction(record, state.read)) {
|
||
c.error(c, {
|
||
message: 'Could not decrypt record or bad MAC.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
// doesn't matter if decryption failed or MAC was
|
||
// invalid, return the same error so as not to reveal
|
||
// which one occurred
|
||
description: tls.Alert.Description.bad_record_mac
|
||
}
|
||
});
|
||
} else if(!state.read.compressFunction(c, record, state.read)) {
|
||
c.error(c, {
|
||
message: 'Could not decompress record.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.decompression_failure
|
||
}
|
||
});
|
||
}
|
||
return !c.fail;
|
||
};
|
||
|
||
// update function in write mode will compress then encrypt a record
|
||
state.write.update = function(c, record) {
|
||
if(!state.write.compressFunction(c, record, state.write)) {
|
||
// error, but do not send alert since it would require
|
||
// compression as well
|
||
c.error(c, {
|
||
message: 'Could not compress record.',
|
||
send: false,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
} else if(!state.write.cipherFunction(record, state.write)) {
|
||
// error, but do not send alert since it would require
|
||
// encryption as well
|
||
c.error(c, {
|
||
message: 'Could not encrypt record.',
|
||
send: false,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
}
|
||
return !c.fail;
|
||
};
|
||
|
||
// handle security parameters
|
||
if(c.session) {
|
||
var sp = c.session.sp;
|
||
c.session.cipherSuite.initSecurityParameters(sp);
|
||
|
||
// generate keys
|
||
sp.keys = tls.generateKeys(c, sp);
|
||
state.read.macKey = client ?
|
||
sp.keys.server_write_MAC_key : sp.keys.client_write_MAC_key;
|
||
state.write.macKey = client ?
|
||
sp.keys.client_write_MAC_key : sp.keys.server_write_MAC_key;
|
||
|
||
// cipher suite setup
|
||
c.session.cipherSuite.initConnectionState(state, c, sp);
|
||
|
||
// compression setup
|
||
switch(sp.compression_algorithm) {
|
||
case tls.CompressionMethod.none:
|
||
break;
|
||
case tls.CompressionMethod.deflate:
|
||
state.read.compressFunction = inflate;
|
||
state.write.compressFunction = deflate;
|
||
break;
|
||
default:
|
||
throw new Error('Unsupported compression algorithm.');
|
||
}
|
||
}
|
||
|
||
return state;
|
||
};
|
||
|
||
/**
|
||
* Creates a Random structure.
|
||
*
|
||
* struct {
|
||
* uint32 gmt_unix_time;
|
||
* opaque random_bytes[28];
|
||
* } Random;
|
||
*
|
||
* gmt_unix_time:
|
||
* The current time and date in standard UNIX 32-bit format (seconds since
|
||
* the midnight starting Jan 1, 1970, UTC, ignoring leap seconds) according
|
||
* to the sender's internal clock. Clocks are not required to be set
|
||
* correctly by the basic TLS protocol; higher-level or application
|
||
* protocols may define additional requirements. Note that, for historical
|
||
* reasons, the data element is named using GMT, the predecessor of the
|
||
* current worldwide time base, UTC.
|
||
* random_bytes:
|
||
* 28 bytes generated by a secure random number generator.
|
||
*
|
||
* @return the Random structure as a byte array.
|
||
*/
|
||
tls.createRandom = function() {
|
||
// get UTC milliseconds
|
||
var d = new Date();
|
||
var utc = +d + d.getTimezoneOffset() * 60000;
|
||
var rval = forge.util.createBuffer();
|
||
rval.putInt32(utc);
|
||
rval.putBytes(forge.random.getBytes(28));
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a TLS record with the given type and data.
|
||
*
|
||
* @param c the connection.
|
||
* @param options:
|
||
* type: the record type.
|
||
* data: the plain text data in a byte buffer.
|
||
*
|
||
* @return the created record.
|
||
*/
|
||
tls.createRecord = function(c, options) {
|
||
if(!options.data) {
|
||
return null;
|
||
}
|
||
var record = {
|
||
type: options.type,
|
||
version: {
|
||
major: c.version.major,
|
||
minor: c.version.minor
|
||
},
|
||
length: options.data.length(),
|
||
fragment: options.data
|
||
};
|
||
return record;
|
||
};
|
||
|
||
/**
|
||
* Creates a TLS alert record.
|
||
*
|
||
* @param c the connection.
|
||
* @param alert:
|
||
* level: the TLS alert level.
|
||
* description: the TLS alert description.
|
||
*
|
||
* @return the created alert record.
|
||
*/
|
||
tls.createAlert = function(c, alert) {
|
||
var b = forge.util.createBuffer();
|
||
b.putByte(alert.level);
|
||
b.putByte(alert.description);
|
||
return tls.createRecord(c, {
|
||
type: tls.ContentType.alert,
|
||
data: b
|
||
});
|
||
};
|
||
|
||
/* The structure of a TLS handshake message.
|
||
*
|
||
* struct {
|
||
* HandshakeType msg_type; // handshake type
|
||
* uint24 length; // bytes in message
|
||
* select(HandshakeType) {
|
||
* case hello_request: HelloRequest;
|
||
* case client_hello: ClientHello;
|
||
* case server_hello: ServerHello;
|
||
* case certificate: Certificate;
|
||
* case server_key_exchange: ServerKeyExchange;
|
||
* case certificate_request: CertificateRequest;
|
||
* case server_hello_done: ServerHelloDone;
|
||
* case certificate_verify: CertificateVerify;
|
||
* case client_key_exchange: ClientKeyExchange;
|
||
* case finished: Finished;
|
||
* } body;
|
||
* } Handshake;
|
||
*/
|
||
|
||
/**
|
||
* Creates a ClientHello message.
|
||
*
|
||
* opaque SessionID<0..32>;
|
||
* enum { null(0), deflate(1), (255) } CompressionMethod;
|
||
* uint8 CipherSuite[2];
|
||
*
|
||
* struct {
|
||
* ProtocolVersion client_version;
|
||
* Random random;
|
||
* SessionID session_id;
|
||
* CipherSuite cipher_suites<2..2^16-2>;
|
||
* CompressionMethod compression_methods<1..2^8-1>;
|
||
* select(extensions_present) {
|
||
* case false:
|
||
* struct {};
|
||
* case true:
|
||
* Extension extensions<0..2^16-1>;
|
||
* };
|
||
* } ClientHello;
|
||
*
|
||
* The extension format for extended client hellos and server hellos is:
|
||
*
|
||
* struct {
|
||
* ExtensionType extension_type;
|
||
* opaque extension_data<0..2^16-1>;
|
||
* } Extension;
|
||
*
|
||
* Here:
|
||
*
|
||
* - "extension_type" identifies the particular extension type.
|
||
* - "extension_data" contains information specific to the particular
|
||
* extension type.
|
||
*
|
||
* The extension types defined in this document are:
|
||
*
|
||
* enum {
|
||
* server_name(0), max_fragment_length(1),
|
||
* client_certificate_url(2), trusted_ca_keys(3),
|
||
* truncated_hmac(4), status_request(5), (65535)
|
||
* } ExtensionType;
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the ClientHello byte buffer.
|
||
*/
|
||
tls.createClientHello = function(c) {
|
||
// save hello version
|
||
c.session.clientHelloVersion = {
|
||
major: c.version.major,
|
||
minor: c.version.minor
|
||
};
|
||
|
||
// create supported cipher suites
|
||
var cipherSuites = forge.util.createBuffer();
|
||
for(var i = 0; i < c.cipherSuites.length; ++i) {
|
||
var cs = c.cipherSuites[i];
|
||
cipherSuites.putByte(cs.id[0]);
|
||
cipherSuites.putByte(cs.id[1]);
|
||
}
|
||
var cSuites = cipherSuites.length();
|
||
|
||
// create supported compression methods, null always supported, but
|
||
// also support deflate if connection has inflate and deflate methods
|
||
var compressionMethods = forge.util.createBuffer();
|
||
compressionMethods.putByte(tls.CompressionMethod.none);
|
||
// FIXME: deflate support disabled until issues with raw deflate data
|
||
// without zlib headers are resolved
|
||
/*
|
||
if(c.inflate !== null && c.deflate !== null) {
|
||
compressionMethods.putByte(tls.CompressionMethod.deflate);
|
||
}
|
||
*/
|
||
var cMethods = compressionMethods.length();
|
||
|
||
// create TLS SNI (server name indication) extension if virtual host
|
||
// has been specified, see RFC 3546
|
||
var extensions = forge.util.createBuffer();
|
||
if(c.virtualHost) {
|
||
// create extension struct
|
||
var ext = forge.util.createBuffer();
|
||
ext.putByte(0x00); // type server_name (ExtensionType is 2 bytes)
|
||
ext.putByte(0x00);
|
||
|
||
/* In order to provide the server name, clients MAY include an
|
||
* extension of type "server_name" in the (extended) client hello.
|
||
* The "extension_data" field of this extension SHALL contain
|
||
* "ServerNameList" where:
|
||
*
|
||
* struct {
|
||
* NameType name_type;
|
||
* select(name_type) {
|
||
* case host_name: HostName;
|
||
* } name;
|
||
* } ServerName;
|
||
*
|
||
* enum {
|
||
* host_name(0), (255)
|
||
* } NameType;
|
||
*
|
||
* opaque HostName<1..2^16-1>;
|
||
*
|
||
* struct {
|
||
* ServerName server_name_list<1..2^16-1>
|
||
* } ServerNameList;
|
||
*/
|
||
var serverName = forge.util.createBuffer();
|
||
serverName.putByte(0x00); // type host_name
|
||
writeVector(serverName, 2, forge.util.createBuffer(c.virtualHost));
|
||
|
||
// ServerNameList is in extension_data
|
||
var snList = forge.util.createBuffer();
|
||
writeVector(snList, 2, serverName);
|
||
writeVector(ext, 2, snList);
|
||
extensions.putBuffer(ext);
|
||
}
|
||
var extLength = extensions.length();
|
||
if(extLength > 0) {
|
||
// add extension vector length
|
||
extLength += 2;
|
||
}
|
||
|
||
// determine length of the handshake message
|
||
// cipher suites and compression methods size will need to be
|
||
// updated if more get added to the list
|
||
var sessionId = c.session.id;
|
||
var length =
|
||
sessionId.length + 1 + // session ID vector
|
||
2 + // version (major + minor)
|
||
4 + 28 + // random time and random bytes
|
||
2 + cSuites + // cipher suites vector
|
||
1 + cMethods + // compression methods vector
|
||
extLength; // extensions vector
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.client_hello);
|
||
rval.putInt24(length); // handshake length
|
||
rval.putByte(c.version.major); // major version
|
||
rval.putByte(c.version.minor); // minor version
|
||
rval.putBytes(c.session.sp.client_random); // random time + bytes
|
||
writeVector(rval, 1, forge.util.createBuffer(sessionId));
|
||
writeVector(rval, 2, cipherSuites);
|
||
writeVector(rval, 1, compressionMethods);
|
||
if(extLength > 0) {
|
||
writeVector(rval, 2, extensions);
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a ServerHello message.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the ServerHello byte buffer.
|
||
*/
|
||
tls.createServerHello = function(c) {
|
||
// determine length of the handshake message
|
||
var sessionId = c.session.id;
|
||
var length =
|
||
sessionId.length + 1 + // session ID vector
|
||
2 + // version (major + minor)
|
||
4 + 28 + // random time and random bytes
|
||
2 + // chosen cipher suite
|
||
1; // chosen compression method
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.server_hello);
|
||
rval.putInt24(length); // handshake length
|
||
rval.putByte(c.version.major); // major version
|
||
rval.putByte(c.version.minor); // minor version
|
||
rval.putBytes(c.session.sp.server_random); // random time + bytes
|
||
writeVector(rval, 1, forge.util.createBuffer(sessionId));
|
||
rval.putByte(c.session.cipherSuite.id[0]);
|
||
rval.putByte(c.session.cipherSuite.id[1]);
|
||
rval.putByte(c.session.compressionMethod);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a Certificate message.
|
||
*
|
||
* When this message will be sent:
|
||
* This is the first message the client can send after receiving a server
|
||
* hello done message and the first message the server can send after
|
||
* sending a ServerHello. This client message is only sent if the server
|
||
* requests a certificate. If no suitable certificate is available, the
|
||
* client should send a certificate message containing no certificates. If
|
||
* client authentication is required by the server for the handshake to
|
||
* continue, it may respond with a fatal handshake failure alert.
|
||
*
|
||
* opaque ASN.1Cert<1..2^24-1>;
|
||
*
|
||
* struct {
|
||
* ASN.1Cert certificate_list<0..2^24-1>;
|
||
* } Certificate;
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the Certificate byte buffer.
|
||
*/
|
||
tls.createCertificate = function(c) {
|
||
// TODO: check certificate request to ensure types are supported
|
||
|
||
// get a certificate (a certificate as a PEM string)
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
var cert = null;
|
||
if(c.getCertificate) {
|
||
var hint;
|
||
if(client) {
|
||
hint = c.session.certificateRequest;
|
||
} else {
|
||
hint = c.session.extensions.server_name.serverNameList;
|
||
}
|
||
cert = c.getCertificate(c, hint);
|
||
}
|
||
|
||
// buffer to hold certificate list
|
||
var certList = forge.util.createBuffer();
|
||
if(cert !== null) {
|
||
try {
|
||
// normalize cert to a chain of certificates
|
||
if(!forge.util.isArray(cert)) {
|
||
cert = [cert];
|
||
}
|
||
var asn1 = null;
|
||
for(var i = 0; i < cert.length; ++i) {
|
||
var msg = forge.pem.decode(cert[i])[0];
|
||
if(msg.type !== 'CERTIFICATE' &&
|
||
msg.type !== 'X509 CERTIFICATE' &&
|
||
msg.type !== 'TRUSTED CERTIFICATE') {
|
||
var error = new Error('Could not convert certificate from PEM; PEM ' +
|
||
'header type is not "CERTIFICATE", "X509 CERTIFICATE", or ' +
|
||
'"TRUSTED CERTIFICATE".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert certificate from PEM; PEM is encrypted.');
|
||
}
|
||
|
||
var der = forge.util.createBuffer(msg.body);
|
||
if(asn1 === null) {
|
||
asn1 = forge.asn1.fromDer(der.bytes(), false);
|
||
}
|
||
|
||
// certificate entry is itself a vector with 3 length bytes
|
||
var certBuffer = forge.util.createBuffer();
|
||
writeVector(certBuffer, 3, der);
|
||
|
||
// add cert vector to cert list vector
|
||
certList.putBuffer(certBuffer);
|
||
}
|
||
|
||
// save certificate
|
||
cert = forge.pki.certificateFromAsn1(asn1);
|
||
if(client) {
|
||
c.session.clientCertificate = cert;
|
||
} else {
|
||
c.session.serverCertificate = cert;
|
||
}
|
||
} catch(ex) {
|
||
return c.error(c, {
|
||
message: 'Could not send certificate list.',
|
||
cause: ex,
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.bad_certificate
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// determine length of the handshake message
|
||
var length = 3 + certList.length(); // cert list vector
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.certificate);
|
||
rval.putInt24(length);
|
||
writeVector(rval, 3, certList);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a ClientKeyExchange message.
|
||
*
|
||
* When this message will be sent:
|
||
* This message is always sent by the client. It will immediately follow the
|
||
* client certificate message, if it is sent. Otherwise it will be the first
|
||
* message sent by the client after it receives the server hello done
|
||
* message.
|
||
*
|
||
* Meaning of this message:
|
||
* With this message, the premaster secret is set, either though direct
|
||
* transmission of the RSA-encrypted secret, or by the transmission of
|
||
* Diffie-Hellman parameters which will allow each side to agree upon the
|
||
* same premaster secret. When the key exchange method is DH_RSA or DH_DSS,
|
||
* client certification has been requested, and the client was able to
|
||
* respond with a certificate which contained a Diffie-Hellman public key
|
||
* whose parameters (group and generator) matched those specified by the
|
||
* server in its certificate, this message will not contain any data.
|
||
*
|
||
* Meaning of this message:
|
||
* If RSA is being used for key agreement and authentication, the client
|
||
* generates a 48-byte premaster secret, encrypts it using the public key
|
||
* from the server's certificate or the temporary RSA key provided in a
|
||
* server key exchange message, and sends the result in an encrypted
|
||
* premaster secret message. This structure is a variant of the client
|
||
* key exchange message, not a message in itself.
|
||
*
|
||
* struct {
|
||
* select(KeyExchangeAlgorithm) {
|
||
* case rsa: EncryptedPreMasterSecret;
|
||
* case diffie_hellman: ClientDiffieHellmanPublic;
|
||
* } exchange_keys;
|
||
* } ClientKeyExchange;
|
||
*
|
||
* struct {
|
||
* ProtocolVersion client_version;
|
||
* opaque random[46];
|
||
* } PreMasterSecret;
|
||
*
|
||
* struct {
|
||
* public-key-encrypted PreMasterSecret pre_master_secret;
|
||
* } EncryptedPreMasterSecret;
|
||
*
|
||
* A public-key-encrypted element is encoded as a vector <0..2^16-1>.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the ClientKeyExchange byte buffer.
|
||
*/
|
||
tls.createClientKeyExchange = function(c) {
|
||
// create buffer to encrypt
|
||
var b = forge.util.createBuffer();
|
||
|
||
// add highest client-supported protocol to help server avoid version
|
||
// rollback attacks
|
||
b.putByte(c.session.clientHelloVersion.major);
|
||
b.putByte(c.session.clientHelloVersion.minor);
|
||
|
||
// generate and add 46 random bytes
|
||
b.putBytes(forge.random.getBytes(46));
|
||
|
||
// save pre-master secret
|
||
var sp = c.session.sp;
|
||
sp.pre_master_secret = b.getBytes();
|
||
|
||
// RSA-encrypt the pre-master secret
|
||
var key = c.session.serverCertificate.publicKey;
|
||
b = key.encrypt(sp.pre_master_secret);
|
||
|
||
/* Note: The encrypted pre-master secret will be stored in a
|
||
public-key-encrypted opaque vector that has the length prefixed using
|
||
2 bytes, so include those 2 bytes in the handshake message length. This
|
||
is done as a minor optimization instead of calling writeVector(). */
|
||
|
||
// determine length of the handshake message
|
||
var length = b.length + 2;
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.client_key_exchange);
|
||
rval.putInt24(length);
|
||
// add vector length bytes
|
||
rval.putInt16(b.length);
|
||
rval.putBytes(b);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a ServerKeyExchange message.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the ServerKeyExchange byte buffer.
|
||
*/
|
||
tls.createServerKeyExchange = function(c) {
|
||
// this implementation only supports RSA, no Diffie-Hellman support,
|
||
// so this record is empty
|
||
|
||
// determine length of the handshake message
|
||
var length = 0;
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
if(length > 0) {
|
||
rval.putByte(tls.HandshakeType.server_key_exchange);
|
||
rval.putInt24(length);
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Gets the signed data used to verify a client-side certificate. See
|
||
* tls.createCertificateVerify() for details.
|
||
*
|
||
* @param c the connection.
|
||
* @param callback the callback to call once the signed data is ready.
|
||
*/
|
||
tls.getClientSignature = function(c, callback) {
|
||
// generate data to RSA encrypt
|
||
var b = forge.util.createBuffer();
|
||
b.putBuffer(c.session.md5.digest());
|
||
b.putBuffer(c.session.sha1.digest());
|
||
b = b.getBytes();
|
||
|
||
// create default signing function as necessary
|
||
c.getSignature = c.getSignature || function(c, b, callback) {
|
||
// do rsa encryption, call callback
|
||
var privateKey = null;
|
||
if(c.getPrivateKey) {
|
||
try {
|
||
privateKey = c.getPrivateKey(c, c.session.clientCertificate);
|
||
privateKey = forge.pki.privateKeyFromPem(privateKey);
|
||
} catch(ex) {
|
||
c.error(c, {
|
||
message: 'Could not get private key.',
|
||
cause: ex,
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
}
|
||
}
|
||
if(privateKey === null) {
|
||
c.error(c, {
|
||
message: 'No private key set.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.internal_error
|
||
}
|
||
});
|
||
} else {
|
||
b = privateKey.sign(b, null);
|
||
}
|
||
callback(c, b);
|
||
};
|
||
|
||
// get client signature
|
||
c.getSignature(c, b, callback);
|
||
};
|
||
|
||
/**
|
||
* Creates a CertificateVerify message.
|
||
*
|
||
* Meaning of this message:
|
||
* This structure conveys the client's Diffie-Hellman public value
|
||
* (Yc) if it was not already included in the client's certificate.
|
||
* The encoding used for Yc is determined by the enumerated
|
||
* PublicValueEncoding. This structure is a variant of the client
|
||
* key exchange message, not a message in itself.
|
||
*
|
||
* When this message will be sent:
|
||
* This message is used to provide explicit verification of a client
|
||
* certificate. This message is only sent following a client
|
||
* certificate that has signing capability (i.e. all certificates
|
||
* except those containing fixed Diffie-Hellman parameters). When
|
||
* sent, it will immediately follow the client key exchange message.
|
||
*
|
||
* struct {
|
||
* Signature signature;
|
||
* } CertificateVerify;
|
||
*
|
||
* CertificateVerify.signature.md5_hash
|
||
* MD5(handshake_messages);
|
||
*
|
||
* Certificate.signature.sha_hash
|
||
* SHA(handshake_messages);
|
||
*
|
||
* Here handshake_messages refers to all handshake messages sent or
|
||
* received starting at client hello up to but not including this
|
||
* message, including the type and length fields of the handshake
|
||
* messages.
|
||
*
|
||
* select(SignatureAlgorithm) {
|
||
* case anonymous: struct { };
|
||
* case rsa:
|
||
* digitally-signed struct {
|
||
* opaque md5_hash[16];
|
||
* opaque sha_hash[20];
|
||
* };
|
||
* case dsa:
|
||
* digitally-signed struct {
|
||
* opaque sha_hash[20];
|
||
* };
|
||
* } Signature;
|
||
*
|
||
* In digital signing, one-way hash functions are used as input for a
|
||
* signing algorithm. A digitally-signed element is encoded as an opaque
|
||
* vector <0..2^16-1>, where the length is specified by the signing
|
||
* algorithm and key.
|
||
*
|
||
* In RSA signing, a 36-byte structure of two hashes (one SHA and one
|
||
* MD5) is signed (encrypted with the private key). It is encoded with
|
||
* PKCS #1 block type 0 or type 1 as described in [PKCS1].
|
||
*
|
||
* In DSS, the 20 bytes of the SHA hash are run directly through the
|
||
* Digital Signing Algorithm with no additional hashing.
|
||
*
|
||
* @param c the connection.
|
||
* @param signature the signature to include in the message.
|
||
*
|
||
* @return the CertificateVerify byte buffer.
|
||
*/
|
||
tls.createCertificateVerify = function(c, signature) {
|
||
/* Note: The signature will be stored in a "digitally-signed" opaque
|
||
vector that has the length prefixed using 2 bytes, so include those
|
||
2 bytes in the handshake message length. This is done as a minor
|
||
optimization instead of calling writeVector(). */
|
||
|
||
// determine length of the handshake message
|
||
var length = signature.length + 2;
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.certificate_verify);
|
||
rval.putInt24(length);
|
||
// add vector length bytes
|
||
rval.putInt16(signature.length);
|
||
rval.putBytes(signature);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a CertificateRequest message.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the CertificateRequest byte buffer.
|
||
*/
|
||
tls.createCertificateRequest = function(c) {
|
||
// TODO: support other certificate types
|
||
var certTypes = forge.util.createBuffer();
|
||
|
||
// common RSA certificate type
|
||
certTypes.putByte(0x01);
|
||
|
||
// TODO: verify that this data format is correct
|
||
// add distinguished names from CA store
|
||
var cAs = forge.util.createBuffer();
|
||
for(var key in c.caStore.certs) {
|
||
var cert = c.caStore.certs[key];
|
||
var dn = forge.pki.distinguishedNameToAsn1(cert.subject);
|
||
cAs.putBuffer(forge.asn1.toDer(dn));
|
||
}
|
||
|
||
// TODO: TLS 1.2+ has a different format
|
||
|
||
// determine length of the handshake message
|
||
var length =
|
||
1 + certTypes.length() +
|
||
2 + cAs.length();
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.certificate_request);
|
||
rval.putInt24(length);
|
||
writeVector(rval, 1, certTypes);
|
||
writeVector(rval, 2, cAs);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a ServerHelloDone message.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the ServerHelloDone byte buffer.
|
||
*/
|
||
tls.createServerHelloDone = function(c) {
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.server_hello_done);
|
||
rval.putInt24(0);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a ChangeCipherSpec message.
|
||
*
|
||
* The change cipher spec protocol exists to signal transitions in
|
||
* ciphering strategies. The protocol consists of a single message,
|
||
* which is encrypted and compressed under the current (not the pending)
|
||
* connection state. The message consists of a single byte of value 1.
|
||
*
|
||
* struct {
|
||
* enum { change_cipher_spec(1), (255) } type;
|
||
* } ChangeCipherSpec;
|
||
*
|
||
* @return the ChangeCipherSpec byte buffer.
|
||
*/
|
||
tls.createChangeCipherSpec = function() {
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(0x01);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a Finished message.
|
||
*
|
||
* struct {
|
||
* opaque verify_data[12];
|
||
* } Finished;
|
||
*
|
||
* verify_data
|
||
* PRF(master_secret, finished_label, MD5(handshake_messages) +
|
||
* SHA-1(handshake_messages)) [0..11];
|
||
*
|
||
* finished_label
|
||
* For Finished messages sent by the client, the string "client
|
||
* finished". For Finished messages sent by the server, the
|
||
* string "server finished".
|
||
*
|
||
* handshake_messages
|
||
* All of the data from all handshake messages up to but not
|
||
* including this message. This is only data visible at the
|
||
* handshake layer and does not include record layer headers.
|
||
* This is the concatenation of all the Handshake structures as
|
||
* defined in 7.4 exchanged thus far.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return the Finished byte buffer.
|
||
*/
|
||
tls.createFinished = function(c) {
|
||
// generate verify_data
|
||
var b = forge.util.createBuffer();
|
||
b.putBuffer(c.session.md5.digest());
|
||
b.putBuffer(c.session.sha1.digest());
|
||
|
||
// TODO: determine prf function and verify length for TLS 1.2
|
||
var client = (c.entity === tls.ConnectionEnd.client);
|
||
var sp = c.session.sp;
|
||
var vdl = 12;
|
||
var prf = prf_TLS1;
|
||
var label = client ? 'client finished' : 'server finished';
|
||
b = prf(sp.master_secret, label, b.getBytes(), vdl);
|
||
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(tls.HandshakeType.finished);
|
||
rval.putInt24(b.length());
|
||
rval.putBuffer(b);
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a HeartbeatMessage (See RFC 6520).
|
||
*
|
||
* struct {
|
||
* HeartbeatMessageType type;
|
||
* uint16 payload_length;
|
||
* opaque payload[HeartbeatMessage.payload_length];
|
||
* opaque padding[padding_length];
|
||
* } HeartbeatMessage;
|
||
*
|
||
* The total length of a HeartbeatMessage MUST NOT exceed 2^14 or
|
||
* max_fragment_length when negotiated as defined in [RFC6066].
|
||
*
|
||
* type: The message type, either heartbeat_request or heartbeat_response.
|
||
*
|
||
* payload_length: The length of the payload.
|
||
*
|
||
* payload: The payload consists of arbitrary content.
|
||
*
|
||
* padding: The padding is random content that MUST be ignored by the
|
||
* receiver. The length of a HeartbeatMessage is TLSPlaintext.length
|
||
* for TLS and DTLSPlaintext.length for DTLS. Furthermore, the
|
||
* length of the type field is 1 byte, and the length of the
|
||
* payload_length is 2. Therefore, the padding_length is
|
||
* TLSPlaintext.length - payload_length - 3 for TLS and
|
||
* DTLSPlaintext.length - payload_length - 3 for DTLS. The
|
||
* padding_length MUST be at least 16.
|
||
*
|
||
* The sender of a HeartbeatMessage MUST use a random padding of at
|
||
* least 16 bytes. The padding of a received HeartbeatMessage message
|
||
* MUST be ignored.
|
||
*
|
||
* If the payload_length of a received HeartbeatMessage is too large,
|
||
* the received HeartbeatMessage MUST be discarded silently.
|
||
*
|
||
* @param c the connection.
|
||
* @param type the tls.HeartbeatMessageType.
|
||
* @param payload the heartbeat data to send as the payload.
|
||
* @param [payloadLength] the payload length to use, defaults to the
|
||
* actual payload length.
|
||
*
|
||
* @return the HeartbeatRequest byte buffer.
|
||
*/
|
||
tls.createHeartbeat = function(type, payload, payloadLength) {
|
||
if(typeof payloadLength === 'undefined') {
|
||
payloadLength = payload.length;
|
||
}
|
||
// build record fragment
|
||
var rval = forge.util.createBuffer();
|
||
rval.putByte(type); // heartbeat message type
|
||
rval.putInt16(payloadLength); // payload length
|
||
rval.putBytes(payload); // payload
|
||
// padding
|
||
var plaintextLength = rval.length();
|
||
var paddingLength = Math.max(16, plaintextLength - payloadLength - 3);
|
||
rval.putBytes(forge.random.getBytes(paddingLength));
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Fragments, compresses, encrypts, and queues a record for delivery.
|
||
*
|
||
* @param c the connection.
|
||
* @param record the record to queue.
|
||
*/
|
||
tls.queue = function(c, record) {
|
||
// error during record creation
|
||
if(!record) {
|
||
return;
|
||
}
|
||
|
||
// if the record is a handshake record, update handshake hashes
|
||
if(record.type === tls.ContentType.handshake) {
|
||
var bytes = record.fragment.bytes();
|
||
c.session.md5.update(bytes);
|
||
c.session.sha1.update(bytes);
|
||
bytes = null;
|
||
}
|
||
|
||
// handle record fragmentation
|
||
var records;
|
||
if(record.fragment.length() <= tls.MaxFragment) {
|
||
records = [record];
|
||
} else {
|
||
// fragment data as long as it is too long
|
||
records = [];
|
||
var data = record.fragment.bytes();
|
||
while(data.length > tls.MaxFragment) {
|
||
records.push(tls.createRecord(c, {
|
||
type: record.type,
|
||
data: forge.util.createBuffer(data.slice(0, tls.MaxFragment))
|
||
}));
|
||
data = data.slice(tls.MaxFragment);
|
||
}
|
||
// add last record
|
||
if(data.length > 0) {
|
||
records.push(tls.createRecord(c, {
|
||
type: record.type,
|
||
data: forge.util.createBuffer(data)
|
||
}));
|
||
}
|
||
}
|
||
|
||
// compress and encrypt all fragmented records
|
||
for(var i = 0; i < records.length && !c.fail; ++i) {
|
||
// update the record using current write state
|
||
var rec = records[i];
|
||
var s = c.state.current.write;
|
||
if(s.update(c, rec)) {
|
||
// store record
|
||
c.records.push(rec);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Flushes all queued records to the output buffer and calls the
|
||
* tlsDataReady() handler on the given connection.
|
||
*
|
||
* @param c the connection.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
tls.flush = function(c) {
|
||
for(var i = 0; i < c.records.length; ++i) {
|
||
var record = c.records[i];
|
||
|
||
// add record header and fragment
|
||
c.tlsData.putByte(record.type);
|
||
c.tlsData.putByte(record.version.major);
|
||
c.tlsData.putByte(record.version.minor);
|
||
c.tlsData.putInt16(record.fragment.length());
|
||
c.tlsData.putBuffer(c.records[i].fragment);
|
||
}
|
||
c.records = [];
|
||
return c.tlsDataReady(c);
|
||
};
|
||
|
||
/**
|
||
* Maps a pki.certificateError to a tls.Alert.Description.
|
||
*
|
||
* @param error the error to map.
|
||
*
|
||
* @return the alert description.
|
||
*/
|
||
var _certErrorToAlertDesc = function(error) {
|
||
switch(error) {
|
||
case true:
|
||
return true;
|
||
case forge.pki.certificateError.bad_certificate:
|
||
return tls.Alert.Description.bad_certificate;
|
||
case forge.pki.certificateError.unsupported_certificate:
|
||
return tls.Alert.Description.unsupported_certificate;
|
||
case forge.pki.certificateError.certificate_revoked:
|
||
return tls.Alert.Description.certificate_revoked;
|
||
case forge.pki.certificateError.certificate_expired:
|
||
return tls.Alert.Description.certificate_expired;
|
||
case forge.pki.certificateError.certificate_unknown:
|
||
return tls.Alert.Description.certificate_unknown;
|
||
case forge.pki.certificateError.unknown_ca:
|
||
return tls.Alert.Description.unknown_ca;
|
||
default:
|
||
return tls.Alert.Description.bad_certificate;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Maps a tls.Alert.Description to a pki.certificateError.
|
||
*
|
||
* @param desc the alert description.
|
||
*
|
||
* @return the certificate error.
|
||
*/
|
||
var _alertDescToCertError = function(desc) {
|
||
switch(desc) {
|
||
case true:
|
||
return true;
|
||
case tls.Alert.Description.bad_certificate:
|
||
return forge.pki.certificateError.bad_certificate;
|
||
case tls.Alert.Description.unsupported_certificate:
|
||
return forge.pki.certificateError.unsupported_certificate;
|
||
case tls.Alert.Description.certificate_revoked:
|
||
return forge.pki.certificateError.certificate_revoked;
|
||
case tls.Alert.Description.certificate_expired:
|
||
return forge.pki.certificateError.certificate_expired;
|
||
case tls.Alert.Description.certificate_unknown:
|
||
return forge.pki.certificateError.certificate_unknown;
|
||
case tls.Alert.Description.unknown_ca:
|
||
return forge.pki.certificateError.unknown_ca;
|
||
default:
|
||
return forge.pki.certificateError.bad_certificate;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Verifies a certificate chain against the given connection's
|
||
* Certificate Authority store.
|
||
*
|
||
* @param c the TLS connection.
|
||
* @param chain the certificate chain to verify, with the root or highest
|
||
* authority at the end.
|
||
*
|
||
* @return true if successful, false if not.
|
||
*/
|
||
tls.verifyCertificateChain = function(c, chain) {
|
||
try {
|
||
// verify chain
|
||
forge.pki.verifyCertificateChain(c.caStore, chain,
|
||
function verify(vfd, depth, chain) {
|
||
// convert pki.certificateError to tls alert description
|
||
var desc = _certErrorToAlertDesc(vfd);
|
||
|
||
// call application callback
|
||
var ret = c.verify(c, vfd, depth, chain);
|
||
if(ret !== true) {
|
||
if(typeof ret === 'object' && !forge.util.isArray(ret)) {
|
||
// throw custom error
|
||
var error = new Error('The application rejected the certificate.');
|
||
error.send = true;
|
||
error.alert = {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.bad_certificate
|
||
};
|
||
if(ret.message) {
|
||
error.message = ret.message;
|
||
}
|
||
if(ret.alert) {
|
||
error.alert.description = ret.alert;
|
||
}
|
||
throw error;
|
||
}
|
||
|
||
// convert tls alert description to pki.certificateError
|
||
if(ret !== vfd) {
|
||
ret = _alertDescToCertError(ret);
|
||
}
|
||
}
|
||
|
||
return ret;
|
||
});
|
||
} catch(ex) {
|
||
// build tls error if not already customized
|
||
var err = ex;
|
||
if(typeof err !== 'object' || forge.util.isArray(err)) {
|
||
err = {
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: _certErrorToAlertDesc(ex)
|
||
}
|
||
};
|
||
}
|
||
if(!('send' in err)) {
|
||
err.send = true;
|
||
}
|
||
if(!('alert' in err)) {
|
||
err.alert = {
|
||
level: tls.Alert.Level.fatal,
|
||
description: _certErrorToAlertDesc(err.error)
|
||
};
|
||
}
|
||
|
||
// send error
|
||
c.error(c, err);
|
||
}
|
||
|
||
return !c.fail;
|
||
};
|
||
|
||
/**
|
||
* Creates a new TLS session cache.
|
||
*
|
||
* @param cache optional map of session ID to cached session.
|
||
* @param capacity the maximum size for the cache (default: 100).
|
||
*
|
||
* @return the new TLS session cache.
|
||
*/
|
||
tls.createSessionCache = function(cache, capacity) {
|
||
var rval = null;
|
||
|
||
// assume input is already a session cache object
|
||
if(cache && cache.getSession && cache.setSession && cache.order) {
|
||
rval = cache;
|
||
} else {
|
||
// create cache
|
||
rval = {};
|
||
rval.cache = cache || {};
|
||
rval.capacity = Math.max(capacity || 100, 1);
|
||
rval.order = [];
|
||
|
||
// store order for sessions, delete session overflow
|
||
for(var key in cache) {
|
||
if(rval.order.length <= capacity) {
|
||
rval.order.push(key);
|
||
} else {
|
||
delete cache[key];
|
||
}
|
||
}
|
||
|
||
// get a session from a session ID (or get any session)
|
||
rval.getSession = function(sessionId) {
|
||
var session = null;
|
||
var key = null;
|
||
|
||
// if session ID provided, use it
|
||
if(sessionId) {
|
||
key = forge.util.bytesToHex(sessionId);
|
||
} else if(rval.order.length > 0) {
|
||
// get first session from cache
|
||
key = rval.order[0];
|
||
}
|
||
|
||
if(key !== null && key in rval.cache) {
|
||
// get cached session and remove from cache
|
||
session = rval.cache[key];
|
||
delete rval.cache[key];
|
||
for(var i in rval.order) {
|
||
if(rval.order[i] === key) {
|
||
rval.order.splice(i, 1);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return session;
|
||
};
|
||
|
||
// set a session in the cache
|
||
rval.setSession = function(sessionId, session) {
|
||
// remove session from cache if at capacity
|
||
if(rval.order.length === rval.capacity) {
|
||
var key = rval.order.shift();
|
||
delete rval.cache[key];
|
||
}
|
||
// add session to cache
|
||
var key = forge.util.bytesToHex(sessionId);
|
||
rval.order.push(key);
|
||
rval.cache[key] = session;
|
||
};
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Creates a new TLS connection.
|
||
*
|
||
* See public createConnection() docs for more details.
|
||
*
|
||
* @param options the options for this connection.
|
||
*
|
||
* @return the new TLS connection.
|
||
*/
|
||
tls.createConnection = function(options) {
|
||
var caStore = null;
|
||
if(options.caStore) {
|
||
// if CA store is an array, convert it to a CA store object
|
||
if(forge.util.isArray(options.caStore)) {
|
||
caStore = forge.pki.createCaStore(options.caStore);
|
||
} else {
|
||
caStore = options.caStore;
|
||
}
|
||
} else {
|
||
// create empty CA store
|
||
caStore = forge.pki.createCaStore();
|
||
}
|
||
|
||
// setup default cipher suites
|
||
var cipherSuites = options.cipherSuites || null;
|
||
if(cipherSuites === null) {
|
||
cipherSuites = [];
|
||
for(var key in tls.CipherSuites) {
|
||
cipherSuites.push(tls.CipherSuites[key]);
|
||
}
|
||
}
|
||
|
||
// set default entity
|
||
var entity = (options.server || false) ?
|
||
tls.ConnectionEnd.server : tls.ConnectionEnd.client;
|
||
|
||
// create session cache if requested
|
||
var sessionCache = options.sessionCache ?
|
||
tls.createSessionCache(options.sessionCache) : null;
|
||
|
||
// create TLS connection
|
||
var c = {
|
||
version: {major: tls.Version.major, minor: tls.Version.minor},
|
||
entity: entity,
|
||
sessionId: options.sessionId,
|
||
caStore: caStore,
|
||
sessionCache: sessionCache,
|
||
cipherSuites: cipherSuites,
|
||
connected: options.connected,
|
||
virtualHost: options.virtualHost || null,
|
||
verifyClient: options.verifyClient || false,
|
||
verify: options.verify || function(cn, vfd, dpth, cts) {return vfd;},
|
||
getCertificate: options.getCertificate || null,
|
||
getPrivateKey: options.getPrivateKey || null,
|
||
getSignature: options.getSignature || null,
|
||
input: forge.util.createBuffer(),
|
||
tlsData: forge.util.createBuffer(),
|
||
data: forge.util.createBuffer(),
|
||
tlsDataReady: options.tlsDataReady,
|
||
dataReady: options.dataReady,
|
||
heartbeatReceived: options.heartbeatReceived,
|
||
closed: options.closed,
|
||
error: function(c, ex) {
|
||
// set origin if not set
|
||
ex.origin = ex.origin ||
|
||
((c.entity === tls.ConnectionEnd.client) ? 'client' : 'server');
|
||
|
||
// send TLS alert
|
||
if(ex.send) {
|
||
tls.queue(c, tls.createAlert(c, ex.alert));
|
||
tls.flush(c);
|
||
}
|
||
|
||
// error is fatal by default
|
||
var fatal = (ex.fatal !== false);
|
||
if(fatal) {
|
||
// set fail flag
|
||
c.fail = true;
|
||
}
|
||
|
||
// call error handler first
|
||
options.error(c, ex);
|
||
|
||
if(fatal) {
|
||
// fatal error, close connection, do not clear fail
|
||
c.close(false);
|
||
}
|
||
},
|
||
deflate: options.deflate || null,
|
||
inflate: options.inflate || null
|
||
};
|
||
|
||
/**
|
||
* Resets a closed TLS connection for reuse. Called in c.close().
|
||
*
|
||
* @param clearFail true to clear the fail flag (default: true).
|
||
*/
|
||
c.reset = function(clearFail) {
|
||
c.version = {major: tls.Version.major, minor: tls.Version.minor};
|
||
c.record = null;
|
||
c.session = null;
|
||
c.peerCertificate = null;
|
||
c.state = {
|
||
pending: null,
|
||
current: null
|
||
};
|
||
c.expect = (c.entity === tls.ConnectionEnd.client) ? SHE : CHE;
|
||
c.fragmented = null;
|
||
c.records = [];
|
||
c.open = false;
|
||
c.handshakes = 0;
|
||
c.handshaking = false;
|
||
c.isConnected = false;
|
||
c.fail = !(clearFail || typeof(clearFail) === 'undefined');
|
||
c.input.clear();
|
||
c.tlsData.clear();
|
||
c.data.clear();
|
||
c.state.current = tls.createConnectionState(c);
|
||
};
|
||
|
||
// do initial reset of connection
|
||
c.reset();
|
||
|
||
/**
|
||
* Updates the current TLS engine state based on the given record.
|
||
*
|
||
* @param c the TLS connection.
|
||
* @param record the TLS record to act on.
|
||
*/
|
||
var _update = function(c, record) {
|
||
// get record handler (align type in table by subtracting lowest)
|
||
var aligned = record.type - tls.ContentType.change_cipher_spec;
|
||
var handlers = ctTable[c.entity][c.expect];
|
||
if(aligned in handlers) {
|
||
handlers[aligned](c, record);
|
||
} else {
|
||
// unexpected record
|
||
tls.handleUnexpected(c, record);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Reads the record header and initializes the next record on the given
|
||
* connection.
|
||
*
|
||
* @param c the TLS connection with the next record.
|
||
*
|
||
* @return 0 if the input data could be processed, otherwise the
|
||
* number of bytes required for data to be processed.
|
||
*/
|
||
var _readRecordHeader = function(c) {
|
||
var rval = 0;
|
||
|
||
// get input buffer and its length
|
||
var b = c.input;
|
||
var len = b.length();
|
||
|
||
// need at least 5 bytes to initialize a record
|
||
if(len < 5) {
|
||
rval = 5 - len;
|
||
} else {
|
||
// enough bytes for header
|
||
// initialize record
|
||
c.record = {
|
||
type: b.getByte(),
|
||
version: {
|
||
major: b.getByte(),
|
||
minor: b.getByte()
|
||
},
|
||
length: b.getInt16(),
|
||
fragment: forge.util.createBuffer(),
|
||
ready: false
|
||
};
|
||
|
||
// check record version
|
||
var compatibleVersion = (c.record.version.major === c.version.major);
|
||
if(compatibleVersion && c.session && c.session.version) {
|
||
// session version already set, require same minor version
|
||
compatibleVersion = (c.record.version.minor === c.version.minor);
|
||
}
|
||
if(!compatibleVersion) {
|
||
c.error(c, {
|
||
message: 'Incompatible TLS version.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description: tls.Alert.Description.protocol_version
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Reads the next record's contents and appends its message to any
|
||
* previously fragmented message.
|
||
*
|
||
* @param c the TLS connection with the next record.
|
||
*
|
||
* @return 0 if the input data could be processed, otherwise the
|
||
* number of bytes required for data to be processed.
|
||
*/
|
||
var _readRecord = function(c) {
|
||
var rval = 0;
|
||
|
||
// ensure there is enough input data to get the entire record
|
||
var b = c.input;
|
||
var len = b.length();
|
||
if(len < c.record.length) {
|
||
// not enough data yet, return how much is required
|
||
rval = c.record.length - len;
|
||
} else {
|
||
// there is enough data to parse the pending record
|
||
// fill record fragment and compact input buffer
|
||
c.record.fragment.putBytes(b.getBytes(c.record.length));
|
||
b.compact();
|
||
|
||
// update record using current read state
|
||
var s = c.state.current.read;
|
||
if(s.update(c, c.record)) {
|
||
// see if there is a previously fragmented message that the
|
||
// new record's message fragment should be appended to
|
||
if(c.fragmented !== null) {
|
||
// if the record type matches a previously fragmented
|
||
// record, append the record fragment to it
|
||
if(c.fragmented.type === c.record.type) {
|
||
// concatenate record fragments
|
||
c.fragmented.fragment.putBuffer(c.record.fragment);
|
||
c.record = c.fragmented;
|
||
} else {
|
||
// error, invalid fragmented record
|
||
c.error(c, {
|
||
message: 'Invalid fragmented record.',
|
||
send: true,
|
||
alert: {
|
||
level: tls.Alert.Level.fatal,
|
||
description:
|
||
tls.Alert.Description.unexpected_message
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// record is now ready
|
||
c.record.ready = true;
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Performs a handshake using the TLS Handshake Protocol, as a client.
|
||
*
|
||
* This method should only be called if the connection is in client mode.
|
||
*
|
||
* @param sessionId the session ID to use, null to start a new one.
|
||
*/
|
||
c.handshake = function(sessionId) {
|
||
// error to call this in non-client mode
|
||
if(c.entity !== tls.ConnectionEnd.client) {
|
||
// not fatal error
|
||
c.error(c, {
|
||
message: 'Cannot initiate handshake as a server.',
|
||
fatal: false
|
||
});
|
||
} else if(c.handshaking) {
|
||
// handshake is already in progress, fail but not fatal error
|
||
c.error(c, {
|
||
message: 'Handshake already in progress.',
|
||
fatal: false
|
||
});
|
||
} else {
|
||
// clear fail flag on reuse
|
||
if(c.fail && !c.open && c.handshakes === 0) {
|
||
c.fail = false;
|
||
}
|
||
|
||
// now handshaking
|
||
c.handshaking = true;
|
||
|
||
// default to blank (new session)
|
||
sessionId = sessionId || '';
|
||
|
||
// if a session ID was specified, try to find it in the cache
|
||
var session = null;
|
||
if(sessionId.length > 0) {
|
||
if(c.sessionCache) {
|
||
session = c.sessionCache.getSession(sessionId);
|
||
}
|
||
|
||
// matching session not found in cache, clear session ID
|
||
if(session === null) {
|
||
sessionId = '';
|
||
}
|
||
}
|
||
|
||
// no session given, grab a session from the cache, if available
|
||
if(sessionId.length === 0 && c.sessionCache) {
|
||
session = c.sessionCache.getSession();
|
||
if(session !== null) {
|
||
sessionId = session.id;
|
||
}
|
||
}
|
||
|
||
// set up session
|
||
c.session = {
|
||
id: sessionId,
|
||
version: null,
|
||
cipherSuite: null,
|
||
compressionMethod: null,
|
||
serverCertificate: null,
|
||
certificateRequest: null,
|
||
clientCertificate: null,
|
||
sp: {},
|
||
md5: forge.md.md5.create(),
|
||
sha1: forge.md.sha1.create()
|
||
};
|
||
|
||
// use existing session information
|
||
if(session) {
|
||
// only update version on connection, session version not yet set
|
||
c.version = session.version;
|
||
c.session.sp = session.sp;
|
||
}
|
||
|
||
// generate new client random
|
||
c.session.sp.client_random = tls.createRandom().getBytes();
|
||
|
||
// connection now open
|
||
c.open = true;
|
||
|
||
// send hello
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.handshake,
|
||
data: tls.createClientHello(c)
|
||
}));
|
||
tls.flush(c);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Called when TLS protocol data has been received from somewhere and should
|
||
* be processed by the TLS engine.
|
||
*
|
||
* @param data the TLS protocol data, as a string, to process.
|
||
*
|
||
* @return 0 if the data could be processed, otherwise the number of bytes
|
||
* required for data to be processed.
|
||
*/
|
||
c.process = function(data) {
|
||
var rval = 0;
|
||
|
||
// buffer input data
|
||
if(data) {
|
||
c.input.putBytes(data);
|
||
}
|
||
|
||
// process next record if no failure, process will be called after
|
||
// each record is handled (since handling can be asynchronous)
|
||
if(!c.fail) {
|
||
// reset record if ready and now empty
|
||
if(c.record !== null &&
|
||
c.record.ready && c.record.fragment.isEmpty()) {
|
||
c.record = null;
|
||
}
|
||
|
||
// if there is no pending record, try to read record header
|
||
if(c.record === null) {
|
||
rval = _readRecordHeader(c);
|
||
}
|
||
|
||
// read the next record (if record not yet ready)
|
||
if(!c.fail && c.record !== null && !c.record.ready) {
|
||
rval = _readRecord(c);
|
||
}
|
||
|
||
// record ready to be handled, update engine state
|
||
if(!c.fail && c.record !== null && c.record.ready) {
|
||
_update(c, c.record);
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Requests that application data be packaged into a TLS record. The
|
||
* tlsDataReady handler will be called when the TLS record(s) have been
|
||
* prepared.
|
||
*
|
||
* @param data the application data, as a raw 'binary' encoded string, to
|
||
* be sent; to send utf-16/utf-8 string data, use the return value
|
||
* of util.encodeUtf8(str).
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
c.prepare = function(data) {
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.application_data,
|
||
data: forge.util.createBuffer(data)
|
||
}));
|
||
return tls.flush(c);
|
||
};
|
||
|
||
/**
|
||
* Requests that a heartbeat request be packaged into a TLS record for
|
||
* transmission. The tlsDataReady handler will be called when TLS record(s)
|
||
* have been prepared.
|
||
*
|
||
* When a heartbeat response has been received, the heartbeatReceived
|
||
* handler will be called with the matching payload. This handler can
|
||
* be used to clear a retransmission timer, etc.
|
||
*
|
||
* @param payload the heartbeat data to send as the payload in the message.
|
||
* @param [payloadLength] the payload length to use, defaults to the
|
||
* actual payload length.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
c.prepareHeartbeatRequest = function(payload, payloadLength) {
|
||
if(payload instanceof forge.util.ByteBuffer) {
|
||
payload = payload.bytes();
|
||
}
|
||
if(typeof payloadLength === 'undefined') {
|
||
payloadLength = payload.length;
|
||
}
|
||
c.expectedHeartbeatPayload = payload;
|
||
tls.queue(c, tls.createRecord(c, {
|
||
type: tls.ContentType.heartbeat,
|
||
data: tls.createHeartbeat(
|
||
tls.HeartbeatMessageType.heartbeat_request, payload, payloadLength)
|
||
}));
|
||
return tls.flush(c);
|
||
};
|
||
|
||
/**
|
||
* Closes the connection (sends a close_notify alert).
|
||
*
|
||
* @param clearFail true to clear the fail flag (default: true).
|
||
*/
|
||
c.close = function(clearFail) {
|
||
// save session if connection didn't fail
|
||
if(!c.fail && c.sessionCache && c.session) {
|
||
// only need to preserve session ID, version, and security params
|
||
var session = {
|
||
id: c.session.id,
|
||
version: c.session.version,
|
||
sp: c.session.sp
|
||
};
|
||
session.sp.keys = null;
|
||
c.sessionCache.setSession(session.id, session);
|
||
}
|
||
|
||
if(c.open) {
|
||
// connection no longer open, clear input
|
||
c.open = false;
|
||
c.input.clear();
|
||
|
||
// if connected or handshaking, send an alert
|
||
if(c.isConnected || c.handshaking) {
|
||
c.isConnected = c.handshaking = false;
|
||
|
||
// send close_notify alert
|
||
tls.queue(c, tls.createAlert(c, {
|
||
level: tls.Alert.Level.warning,
|
||
description: tls.Alert.Description.close_notify
|
||
}));
|
||
tls.flush(c);
|
||
}
|
||
|
||
// call handler
|
||
c.closed(c);
|
||
}
|
||
|
||
// reset TLS connection, do not clear fail flag
|
||
c.reset(clearFail);
|
||
};
|
||
|
||
return c;
|
||
};
|
||
|
||
/* TLS API */
|
||
forge.tls = forge.tls || {};
|
||
|
||
// expose non-functions
|
||
for(var key in tls) {
|
||
if(typeof tls[key] !== 'function') {
|
||
forge.tls[key] = tls[key];
|
||
}
|
||
}
|
||
|
||
// expose prf_tls1 for testing
|
||
forge.tls.prf_tls1 = prf_TLS1;
|
||
|
||
// expose sha1 hmac method
|
||
forge.tls.hmac_sha1 = hmac_sha1;
|
||
|
||
// expose session cache creation
|
||
forge.tls.createSessionCache = tls.createSessionCache;
|
||
|
||
/**
|
||
* Creates a new TLS connection. This does not make any assumptions about the
|
||
* transport layer that TLS is working on top of, ie: it does not assume there
|
||
* is a TCP/IP connection or establish one. A TLS connection is totally
|
||
* abstracted away from the layer is runs on top of, it merely establishes a
|
||
* secure channel between a client" and a "server".
|
||
*
|
||
* A TLS connection contains 4 connection states: pending read and write, and
|
||
* current read and write.
|
||
*
|
||
* At initialization, the current read and write states will be null. Only once
|
||
* the security parameters have been set and the keys have been generated can
|
||
* the pending states be converted into current states. Current states will be
|
||
* updated for each record processed.
|
||
*
|
||
* A custom certificate verify callback may be provided to check information
|
||
* like the common name on the server's certificate. It will be called for
|
||
* every certificate in the chain. It has the following signature:
|
||
*
|
||
* variable func(c, certs, index, preVerify)
|
||
* Where:
|
||
* c The TLS connection
|
||
* verified Set to true if certificate was verified, otherwise the alert
|
||
* tls.Alert.Description for why the certificate failed.
|
||
* depth The current index in the chain, where 0 is the server's cert.
|
||
* certs The certificate chain, *NOTE* if the server was anonymous then
|
||
* the chain will be empty.
|
||
*
|
||
* The function returns true on success and on failure either the appropriate
|
||
* tls.Alert.Description or an object with 'alert' set to the appropriate
|
||
* tls.Alert.Description and 'message' set to a custom error message. If true
|
||
* is not returned then the connection will abort using, in order of
|
||
* availability, first the returned alert description, second the preVerify
|
||
* alert description, and lastly the default 'bad_certificate'.
|
||
*
|
||
* There are three callbacks that can be used to make use of client-side
|
||
* certificates where each takes the TLS connection as the first parameter:
|
||
*
|
||
* getCertificate(conn, hint)
|
||
* The second parameter is a hint as to which certificate should be
|
||
* returned. If the connection entity is a client, then the hint will be
|
||
* the CertificateRequest message from the server that is part of the
|
||
* TLS protocol. If the connection entity is a server, then it will be
|
||
* the servername list provided via an SNI extension the ClientHello, if
|
||
* one was provided (empty array if not). The hint can be examined to
|
||
* determine which certificate to use (advanced). Most implementations
|
||
* will just return a certificate. The return value must be a
|
||
* PEM-formatted certificate or an array of PEM-formatted certificates
|
||
* that constitute a certificate chain, with the first in the array/chain
|
||
* being the client's certificate.
|
||
* getPrivateKey(conn, certificate)
|
||
* The second parameter is an forge.pki X.509 certificate object that
|
||
* is associated with the requested private key. The return value must
|
||
* be a PEM-formatted private key.
|
||
* getSignature(conn, bytes, callback)
|
||
* This callback can be used instead of getPrivateKey if the private key
|
||
* is not directly accessible in javascript or should not be. For
|
||
* instance, a secure external web service could provide the signature
|
||
* in exchange for appropriate credentials. The second parameter is a
|
||
* string of bytes to be signed that are part of the TLS protocol. These
|
||
* bytes are used to verify that the private key for the previously
|
||
* provided client-side certificate is accessible to the client. The
|
||
* callback is a function that takes 2 parameters, the TLS connection
|
||
* and the RSA encrypted (signed) bytes as a string. This callback must
|
||
* be called once the signature is ready.
|
||
*
|
||
* @param options the options for this connection:
|
||
* server: true if the connection is server-side, false for client.
|
||
* sessionId: a session ID to reuse, null for a new connection.
|
||
* caStore: an array of certificates to trust.
|
||
* sessionCache: a session cache to use.
|
||
* cipherSuites: an optional array of cipher suites to use,
|
||
* see tls.CipherSuites.
|
||
* connected: function(conn) called when the first handshake completes.
|
||
* virtualHost: the virtual server name to use in a TLS SNI extension.
|
||
* verifyClient: true to require a client certificate in server mode,
|
||
* 'optional' to request one, false not to (default: false).
|
||
* verify: a handler used to custom verify certificates in the chain.
|
||
* getCertificate: an optional callback used to get a certificate or
|
||
* a chain of certificates (as an array).
|
||
* getPrivateKey: an optional callback used to get a private key.
|
||
* getSignature: an optional callback used to get a signature.
|
||
* tlsDataReady: function(conn) called when TLS protocol data has been
|
||
* prepared and is ready to be used (typically sent over a socket
|
||
* connection to its destination), read from conn.tlsData buffer.
|
||
* dataReady: function(conn) called when application data has
|
||
* been parsed from a TLS record and should be consumed by the
|
||
* application, read from conn.data buffer.
|
||
* closed: function(conn) called when the connection has been closed.
|
||
* error: function(conn, error) called when there was an error.
|
||
* deflate: function(inBytes) if provided, will deflate TLS records using
|
||
* the deflate algorithm if the server supports it.
|
||
* inflate: function(inBytes) if provided, will inflate TLS records using
|
||
* the deflate algorithm if the server supports it.
|
||
*
|
||
* @return the new TLS connection.
|
||
*/
|
||
forge.tls.createConnection = tls.createConnection;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'tls';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/tls',[
|
||
'require',
|
||
'module',
|
||
'./asn1',
|
||
'./hmac',
|
||
'./md',
|
||
'./pem',
|
||
'./pki',
|
||
'./random',
|
||
'./util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* A Javascript implementation of AES Cipher Suites for TLS.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2009-2015 Digital Bazaar, Inc.
|
||
*
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var tls = forge.tls;
|
||
|
||
/**
|
||
* Supported cipher suites.
|
||
*/
|
||
tls.CipherSuites['TLS_RSA_WITH_AES_128_CBC_SHA'] = {
|
||
id: [0x00,0x2f],
|
||
name: 'TLS_RSA_WITH_AES_128_CBC_SHA',
|
||
initSecurityParameters: function(sp) {
|
||
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
|
||
sp.cipher_type = tls.CipherType.block;
|
||
sp.enc_key_length = 16;
|
||
sp.block_length = 16;
|
||
sp.fixed_iv_length = 16;
|
||
sp.record_iv_length = 16;
|
||
sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
|
||
sp.mac_length = 20;
|
||
sp.mac_key_length = 20;
|
||
},
|
||
initConnectionState: initConnectionState
|
||
};
|
||
tls.CipherSuites['TLS_RSA_WITH_AES_256_CBC_SHA'] = {
|
||
id: [0x00,0x35],
|
||
name: 'TLS_RSA_WITH_AES_256_CBC_SHA',
|
||
initSecurityParameters: function(sp) {
|
||
sp.bulk_cipher_algorithm = tls.BulkCipherAlgorithm.aes;
|
||
sp.cipher_type = tls.CipherType.block;
|
||
sp.enc_key_length = 32;
|
||
sp.block_length = 16;
|
||
sp.fixed_iv_length = 16;
|
||
sp.record_iv_length = 16;
|
||
sp.mac_algorithm = tls.MACAlgorithm.hmac_sha1;
|
||
sp.mac_length = 20;
|
||
sp.mac_key_length = 20;
|
||
},
|
||
initConnectionState: initConnectionState
|
||
};
|
||
|
||
function initConnectionState(state, c, sp) {
|
||
var client = (c.entity === forge.tls.ConnectionEnd.client);
|
||
|
||
// cipher setup
|
||
state.read.cipherState = {
|
||
init: false,
|
||
cipher: forge.cipher.createDecipher('AES-CBC', client ?
|
||
sp.keys.server_write_key : sp.keys.client_write_key),
|
||
iv: client ? sp.keys.server_write_IV : sp.keys.client_write_IV
|
||
};
|
||
state.write.cipherState = {
|
||
init: false,
|
||
cipher: forge.cipher.createCipher('AES-CBC', client ?
|
||
sp.keys.client_write_key : sp.keys.server_write_key),
|
||
iv: client ? sp.keys.client_write_IV : sp.keys.server_write_IV
|
||
};
|
||
state.read.cipherFunction = decrypt_aes_cbc_sha1;
|
||
state.write.cipherFunction = encrypt_aes_cbc_sha1;
|
||
|
||
// MAC setup
|
||
state.read.macLength = state.write.macLength = sp.mac_length;
|
||
state.read.macFunction = state.write.macFunction = tls.hmac_sha1;
|
||
}
|
||
|
||
/**
|
||
* Encrypts the TLSCompressed record into a TLSCipherText record using AES
|
||
* in CBC mode.
|
||
*
|
||
* @param record the TLSCompressed record to encrypt.
|
||
* @param s the ConnectionState to use.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
function encrypt_aes_cbc_sha1(record, s) {
|
||
var rval = false;
|
||
|
||
// append MAC to fragment, update sequence number
|
||
var mac = s.macFunction(s.macKey, s.sequenceNumber, record);
|
||
record.fragment.putBytes(mac);
|
||
s.updateSequenceNumber();
|
||
|
||
// TLS 1.1+ use an explicit IV every time to protect against CBC attacks
|
||
var iv;
|
||
if(record.version.minor === tls.Versions.TLS_1_0.minor) {
|
||
// use the pre-generated IV when initializing for TLS 1.0, otherwise use
|
||
// the residue from the previous encryption
|
||
iv = s.cipherState.init ? null : s.cipherState.iv;
|
||
} else {
|
||
iv = forge.random.getBytesSync(16);
|
||
}
|
||
|
||
s.cipherState.init = true;
|
||
|
||
// start cipher
|
||
var cipher = s.cipherState.cipher;
|
||
cipher.start({iv: iv});
|
||
|
||
// TLS 1.1+ write IV into output
|
||
if(record.version.minor >= tls.Versions.TLS_1_1.minor) {
|
||
cipher.output.putBytes(iv);
|
||
}
|
||
|
||
// do encryption (default padding is appropriate)
|
||
cipher.update(record.fragment);
|
||
if(cipher.finish(encrypt_aes_cbc_sha1_padding)) {
|
||
// set record fragment to encrypted output
|
||
record.fragment = cipher.output;
|
||
record.length = record.fragment.length();
|
||
rval = true;
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Handles padding for aes_cbc_sha1 in encrypt mode.
|
||
*
|
||
* @param blockSize the block size.
|
||
* @param input the input buffer.
|
||
* @param decrypt true in decrypt mode, false in encrypt mode.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
function encrypt_aes_cbc_sha1_padding(blockSize, input, decrypt) {
|
||
/* The encrypted data length (TLSCiphertext.length) is one more than the sum
|
||
of SecurityParameters.block_length, TLSCompressed.length,
|
||
SecurityParameters.mac_length, and padding_length.
|
||
|
||
The padding may be any length up to 255 bytes long, as long as it results in
|
||
the TLSCiphertext.length being an integral multiple of the block length.
|
||
Lengths longer than necessary might be desirable to frustrate attacks on a
|
||
protocol based on analysis of the lengths of exchanged messages. Each uint8
|
||
in the padding data vector must be filled with the padding length value.
|
||
|
||
The padding length should be such that the total size of the
|
||
GenericBlockCipher structure is a multiple of the cipher's block length.
|
||
Legal values range from zero to 255, inclusive. This length specifies the
|
||
length of the padding field exclusive of the padding_length field itself.
|
||
|
||
This is slightly different from PKCS#7 because the padding value is 1
|
||
less than the actual number of padding bytes if you include the
|
||
padding_length uint8 itself as a padding byte. */
|
||
if(!decrypt) {
|
||
// get the number of padding bytes required to reach the blockSize and
|
||
// subtract 1 for the padding value (to make room for the padding_length
|
||
// uint8)
|
||
var padding = blockSize - (input.length() % blockSize);
|
||
input.fillWithByte(padding - 1, padding);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Handles padding for aes_cbc_sha1 in decrypt mode.
|
||
*
|
||
* @param blockSize the block size.
|
||
* @param output the output buffer.
|
||
* @param decrypt true in decrypt mode, false in encrypt mode.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
function decrypt_aes_cbc_sha1_padding(blockSize, output, decrypt) {
|
||
var rval = true;
|
||
if(decrypt) {
|
||
/* The last byte in the output specifies the number of padding bytes not
|
||
including itself. Each of the padding bytes has the same value as that
|
||
last byte (known as the padding_length). Here we check all padding
|
||
bytes to ensure they have the value of padding_length even if one of
|
||
them is bad in order to ward-off timing attacks. */
|
||
var len = output.length();
|
||
var paddingLength = output.last();
|
||
for(var i = len - 1 - paddingLength; i < len - 1; ++i) {
|
||
rval = rval && (output.at(i) == paddingLength);
|
||
}
|
||
if(rval) {
|
||
// trim off padding bytes and last padding length byte
|
||
output.truncate(paddingLength + 1);
|
||
}
|
||
}
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Decrypts a TLSCipherText record into a TLSCompressed record using
|
||
* AES in CBC mode.
|
||
*
|
||
* @param record the TLSCipherText record to decrypt.
|
||
* @param s the ConnectionState to use.
|
||
*
|
||
* @return true on success, false on failure.
|
||
*/
|
||
var count = 0;
|
||
function decrypt_aes_cbc_sha1(record, s) {
|
||
var rval = false;
|
||
++count;
|
||
|
||
var iv;
|
||
if(record.version.minor === tls.Versions.TLS_1_0.minor) {
|
||
// use pre-generated IV when initializing for TLS 1.0, otherwise use the
|
||
// residue from the previous decryption
|
||
iv = s.cipherState.init ? null : s.cipherState.iv;
|
||
} else {
|
||
// TLS 1.1+ use an explicit IV every time to protect against CBC attacks
|
||
// that is appended to the record fragment
|
||
iv = record.fragment.getBytes(16);
|
||
}
|
||
|
||
s.cipherState.init = true;
|
||
|
||
// start cipher
|
||
var cipher = s.cipherState.cipher;
|
||
cipher.start({iv: iv});
|
||
|
||
// do decryption
|
||
cipher.update(record.fragment);
|
||
rval = cipher.finish(decrypt_aes_cbc_sha1_padding);
|
||
|
||
// even if decryption fails, keep going to minimize timing attacks
|
||
|
||
// decrypted data:
|
||
// first (len - 20) bytes = application data
|
||
// last 20 bytes = MAC
|
||
var macLen = s.macLength;
|
||
|
||
// create a random MAC to check against should the mac length check fail
|
||
// Note: do this regardless of the failure to keep timing consistent
|
||
var mac = forge.random.getBytesSync(macLen);
|
||
|
||
// get fragment and mac
|
||
var len = cipher.output.length();
|
||
if(len >= macLen) {
|
||
record.fragment = cipher.output.getBytes(len - macLen);
|
||
mac = cipher.output.getBytes(macLen);
|
||
} else {
|
||
// bad data, but get bytes anyway to try to keep timing consistent
|
||
record.fragment = cipher.output.getBytes();
|
||
}
|
||
record.fragment = forge.util.createBuffer(record.fragment);
|
||
record.length = record.fragment.length();
|
||
|
||
// see if data integrity checks out, update sequence number
|
||
var mac2 = s.macFunction(s.macKey, s.sequenceNumber, record);
|
||
s.updateSequenceNumber();
|
||
rval = compareMacs(s.macKey, mac, mac2) && rval;
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Safely compare two MACs. This function will compare two MACs in a way
|
||
* that protects against timing attacks.
|
||
*
|
||
* TODO: Expose elsewhere as a utility API.
|
||
*
|
||
* See: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||
*
|
||
* @param key the MAC key to use.
|
||
* @param mac1 as a binary-encoded string of bytes.
|
||
* @param mac2 as a binary-encoded string of bytes.
|
||
*
|
||
* @return true if the MACs are the same, false if not.
|
||
*/
|
||
function compareMacs(key, mac1, mac2) {
|
||
var hmac = forge.hmac.create();
|
||
|
||
hmac.start('SHA1', key);
|
||
hmac.update(mac1);
|
||
mac1 = hmac.digest().getBytes();
|
||
|
||
hmac.start(null, null);
|
||
hmac.update(mac2);
|
||
mac2 = hmac.digest().getBytes();
|
||
|
||
return mac1 === mac2;
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'aesCipherSuites';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/aesCipherSuites',['require', 'module', './aes', './tls'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Debugging support for web applications.
|
||
*
|
||
* @author David I. Lehn <dlehn@digitalbazaar.com>
|
||
*
|
||
* Copyright 2008-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* DEBUG API */
|
||
forge.debug = forge.debug || {};
|
||
|
||
// Private storage for debugging.
|
||
// Useful to expose data that is otherwise unviewable behind closures.
|
||
// NOTE: remember that this can hold references to data and cause leaks!
|
||
// format is "forge._debug.<modulename>.<dataname> = data"
|
||
// Example:
|
||
// (function() {
|
||
// var cat = 'forge.test.Test'; // debugging category
|
||
// var sState = {...}; // local state
|
||
// forge.debug.set(cat, 'sState', sState);
|
||
// })();
|
||
forge.debug.storage = {};
|
||
|
||
/**
|
||
* Gets debug data. Omit name for all cat data Omit name and cat for
|
||
* all data.
|
||
*
|
||
* @param cat name of debugging category.
|
||
* @param name name of data to get (optional).
|
||
* @return object with requested debug data or undefined.
|
||
*/
|
||
forge.debug.get = function(cat, name) {
|
||
var rval;
|
||
if(typeof(cat) === 'undefined') {
|
||
rval = forge.debug.storage;
|
||
} else if(cat in forge.debug.storage) {
|
||
if(typeof(name) === 'undefined') {
|
||
rval = forge.debug.storage[cat];
|
||
} else {
|
||
rval = forge.debug.storage[cat][name];
|
||
}
|
||
}
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Sets debug data.
|
||
*
|
||
* @param cat name of debugging category.
|
||
* @param name name of data to set.
|
||
* @param data data to set.
|
||
*/
|
||
forge.debug.set = function(cat, name, data) {
|
||
if(!(cat in forge.debug.storage)) {
|
||
forge.debug.storage[cat] = {};
|
||
}
|
||
forge.debug.storage[cat][name] = data;
|
||
};
|
||
|
||
/**
|
||
* Clears debug data. Omit name for all cat data. Omit name and cat for
|
||
* all data.
|
||
*
|
||
* @param cat name of debugging category.
|
||
* @param name name of data to clear or omit to clear entire category.
|
||
*/
|
||
forge.debug.clear = function(cat, name) {
|
||
if(typeof(cat) === 'undefined') {
|
||
forge.debug.storage = {};
|
||
} else if(cat in forge.debug.storage) {
|
||
if(typeof(name) === 'undefined') {
|
||
delete forge.debug.storage[cat];
|
||
} else {
|
||
delete forge.debug.storage[cat][name];
|
||
}
|
||
}
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'debug';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/debug',['require', 'module'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of RSA-KEM.
|
||
*
|
||
* @author Lautaro Cozzani Rodriguez
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2014 Lautaro Cozzani <lautaro.cozzani@scytl.com>
|
||
* Copyright (c) 2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
forge.kem = forge.kem || {};
|
||
|
||
var BigInteger = forge.jsbn.BigInteger;
|
||
|
||
/**
|
||
* The API for the RSA Key Encapsulation Mechanism (RSA-KEM) from ISO 18033-2.
|
||
*/
|
||
forge.kem.rsa = {};
|
||
|
||
/**
|
||
* Creates an RSA KEM API object for generating a secret asymmetric key.
|
||
*
|
||
* The symmetric key may be generated via a call to 'encrypt', which will
|
||
* produce a ciphertext to be transmitted to the recipient and a key to be
|
||
* kept secret. The ciphertext is a parameter to be passed to 'decrypt' which
|
||
* will produce the same secret key for the recipient to use to decrypt a
|
||
* message that was encrypted with the secret key.
|
||
*
|
||
* @param kdf the KDF API to use (eg: new forge.kem.kdf1()).
|
||
* @param options the options to use.
|
||
* [prng] a custom crypto-secure pseudo-random number generator to use,
|
||
* that must define "getBytesSync".
|
||
*/
|
||
forge.kem.rsa.create = function(kdf, options) {
|
||
options = options || {};
|
||
var prng = options.prng || forge.random;
|
||
|
||
var kem = {};
|
||
|
||
/**
|
||
* Generates a secret key and its encapsulation.
|
||
*
|
||
* @param publicKey the RSA public key to encrypt with.
|
||
* @param keyLength the length, in bytes, of the secret key to generate.
|
||
*
|
||
* @return an object with:
|
||
* encapsulation: the ciphertext for generating the secret key, as a
|
||
* binary-encoded string of bytes.
|
||
* key: the secret key to use for encrypting a message.
|
||
*/
|
||
kem.encrypt = function(publicKey, keyLength) {
|
||
// generate a random r where 1 > r > n
|
||
var byteLength = Math.ceil(publicKey.n.bitLength() / 8);
|
||
var r;
|
||
do {
|
||
r = new BigInteger(
|
||
forge.util.bytesToHex(prng.getBytesSync(byteLength)),
|
||
16).mod(publicKey.n);
|
||
} while(r.equals(BigInteger.ZERO));
|
||
|
||
// prepend r with zeros
|
||
r = forge.util.hexToBytes(r.toString(16));
|
||
var zeros = byteLength - r.length;
|
||
if(zeros > 0) {
|
||
r = forge.util.fillString(String.fromCharCode(0), zeros) + r;
|
||
}
|
||
|
||
// encrypt the random
|
||
var encapsulation = publicKey.encrypt(r, 'NONE');
|
||
|
||
// generate the secret key
|
||
var key = kdf.generate(r, keyLength);
|
||
|
||
return {encapsulation: encapsulation, key: key};
|
||
};
|
||
|
||
/**
|
||
* Decrypts an encapsulated secret key.
|
||
*
|
||
* @param privateKey the RSA private key to decrypt with.
|
||
* @param encapsulation the ciphertext for generating the secret key, as
|
||
* a binary-encoded string of bytes.
|
||
* @param keyLength the length, in bytes, of the secret key to generate.
|
||
*
|
||
* @return the secret key as a binary-encoded string of bytes.
|
||
*/
|
||
kem.decrypt = function(privateKey, encapsulation, keyLength) {
|
||
// decrypt the encapsulation and generate the secret key
|
||
var r = privateKey.decrypt(encapsulation, 'NONE');
|
||
return kdf.generate(r, keyLength);
|
||
};
|
||
|
||
return kem;
|
||
};
|
||
|
||
// TODO: add forge.kem.kdf.create('KDF1', {md: ..., ...}) API?
|
||
|
||
/**
|
||
* Creates a key derivation API object that implements KDF1 per ISO 18033-2.
|
||
*
|
||
* @param md the hash API to use.
|
||
* @param [digestLength] an optional digest length that must be positive and
|
||
* less than or equal to md.digestLength.
|
||
*
|
||
* @return a KDF1 API object.
|
||
*/
|
||
forge.kem.kdf1 = function(md, digestLength) {
|
||
_createKDF(this, md, 0, digestLength || md.digestLength);
|
||
};
|
||
|
||
/**
|
||
* Creates a key derivation API object that implements KDF2 per ISO 18033-2.
|
||
*
|
||
* @param md the hash API to use.
|
||
* @param [digestLength] an optional digest length that must be positive and
|
||
* less than or equal to md.digestLength.
|
||
*
|
||
* @return a KDF2 API object.
|
||
*/
|
||
forge.kem.kdf2 = function(md, digestLength) {
|
||
_createKDF(this, md, 1, digestLength || md.digestLength);
|
||
};
|
||
|
||
/**
|
||
* Creates a KDF1 or KDF2 API object.
|
||
*
|
||
* @param md the hash API to use.
|
||
* @param counterStart the starting index for the counter.
|
||
* @param digestLength the digest length to use.
|
||
*
|
||
* @return the KDF API object.
|
||
*/
|
||
function _createKDF(kdf, md, counterStart, digestLength) {
|
||
/**
|
||
* Generate a key of the specified length.
|
||
*
|
||
* @param x the binary-encoded byte string to generate a key from.
|
||
* @param length the number of bytes to generate (the size of the key).
|
||
*
|
||
* @return the key as a binary-encoded string.
|
||
*/
|
||
kdf.generate = function(x, length) {
|
||
var key = new forge.util.ByteBuffer();
|
||
|
||
// run counter from counterStart to ceil(length / Hash.len)
|
||
var k = Math.ceil(length / digestLength) + counterStart;
|
||
|
||
var c = new forge.util.ByteBuffer();
|
||
for(var i = counterStart; i < k; ++i) {
|
||
// I2OSP(i, 4): convert counter to an octet string of 4 octets
|
||
c.putInt32(i);
|
||
|
||
// digest 'x' and the counter and add the result to the key
|
||
md.start();
|
||
md.update(x + c.getBytes());
|
||
var hash = md.digest();
|
||
key.putBytes(hash.getBytes(digestLength));
|
||
}
|
||
|
||
// truncate to the correct key length
|
||
key.truncate(key.length() - length);
|
||
return key.getBytes();
|
||
};
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'kem';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/kem',['require', 'module', './util','./random','./jsbn'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Cross-browser support for logging in a web application.
|
||
*
|
||
* @author David I. Lehn <dlehn@digitalbazaar.com>
|
||
*
|
||
* Copyright (c) 2008-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
/* LOG API */
|
||
forge.log = forge.log || {};
|
||
|
||
/**
|
||
* Application logging system.
|
||
*
|
||
* Each logger level available as it's own function of the form:
|
||
* forge.log.level(category, args...)
|
||
* The category is an arbitrary string, and the args are the same as
|
||
* Firebug's console.log API. By default the call will be output as:
|
||
* 'LEVEL [category] <args[0]>, args[1], ...'
|
||
* This enables proper % formatting via the first argument.
|
||
* Each category is enabled by default but can be enabled or disabled with
|
||
* the setCategoryEnabled() function.
|
||
*/
|
||
// list of known levels
|
||
forge.log.levels = [
|
||
'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
|
||
// info on the levels indexed by name:
|
||
// index: level index
|
||
// name: uppercased display name
|
||
var sLevelInfo = {};
|
||
// list of loggers
|
||
var sLoggers = [];
|
||
/**
|
||
* Standard console logger. If no console support is enabled this will
|
||
* remain null. Check before using.
|
||
*/
|
||
var sConsoleLogger = null;
|
||
|
||
// logger flags
|
||
/**
|
||
* Lock the level at the current value. Used in cases where user config may
|
||
* set the level such that only critical messages are seen but more verbose
|
||
* messages are needed for debugging or other purposes.
|
||
*/
|
||
forge.log.LEVEL_LOCKED = (1 << 1);
|
||
/**
|
||
* Always call log function. By default, the logging system will check the
|
||
* message level against logger.level before calling the log function. This
|
||
* flag allows the function to do its own check.
|
||
*/
|
||
forge.log.NO_LEVEL_CHECK = (1 << 2);
|
||
/**
|
||
* Perform message interpolation with the passed arguments. "%" style
|
||
* fields in log messages will be replaced by arguments as needed. Some
|
||
* loggers, such as Firebug, may do this automatically. The original log
|
||
* message will be available as 'message' and the interpolated version will
|
||
* be available as 'fullMessage'.
|
||
*/
|
||
forge.log.INTERPOLATE = (1 << 3);
|
||
|
||
// setup each log level
|
||
for(var i = 0; i < forge.log.levels.length; ++i) {
|
||
var level = forge.log.levels[i];
|
||
sLevelInfo[level] = {
|
||
index: i,
|
||
name: level.toUpperCase()
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Message logger. Will dispatch a message to registered loggers as needed.
|
||
*
|
||
* @param message message object
|
||
*/
|
||
forge.log.logMessage = function(message) {
|
||
var messageLevelIndex = sLevelInfo[message.level].index;
|
||
for(var i = 0; i < sLoggers.length; ++i) {
|
||
var logger = sLoggers[i];
|
||
if(logger.flags & forge.log.NO_LEVEL_CHECK) {
|
||
logger.f(message);
|
||
} else {
|
||
// get logger level
|
||
var loggerLevelIndex = sLevelInfo[logger.level].index;
|
||
// check level
|
||
if(messageLevelIndex <= loggerLevelIndex) {
|
||
// message critical enough, call logger
|
||
logger.f(logger, message);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Sets the 'standard' key on a message object to:
|
||
* "LEVEL [category] " + message
|
||
*
|
||
* @param message a message log object
|
||
*/
|
||
forge.log.prepareStandard = function(message) {
|
||
if(!('standard' in message)) {
|
||
message.standard =
|
||
sLevelInfo[message.level].name +
|
||
//' ' + +message.timestamp +
|
||
' [' + message.category + '] ' +
|
||
message.message;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Sets the 'full' key on a message object to the original message
|
||
* interpolated via % formatting with the message arguments.
|
||
*
|
||
* @param message a message log object.
|
||
*/
|
||
forge.log.prepareFull = function(message) {
|
||
if(!('full' in message)) {
|
||
// copy args and insert message at the front
|
||
var args = [message.message];
|
||
args = args.concat([] || message['arguments']);
|
||
// format the message
|
||
message.full = forge.util.format.apply(this, args);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Applies both preparseStandard() and prepareFull() to a message object and
|
||
* store result in 'standardFull'.
|
||
*
|
||
* @param message a message log object.
|
||
*/
|
||
forge.log.prepareStandardFull = function(message) {
|
||
if(!('standardFull' in message)) {
|
||
// FIXME implement 'standardFull' logging
|
||
forge.log.prepareStandard(message);
|
||
message.standardFull = message.standard;
|
||
}
|
||
};
|
||
|
||
// create log level functions
|
||
if(true) {
|
||
// levels for which we want functions
|
||
var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
|
||
for(var i = 0; i < levels.length; ++i) {
|
||
// wrap in a function to ensure proper level var is passed
|
||
(function(level) {
|
||
// create function for this level
|
||
forge.log[level] = function(category, message/*, args...*/) {
|
||
// convert arguments to real array, remove category and message
|
||
var args = Array.prototype.slice.call(arguments).slice(2);
|
||
// create message object
|
||
// Note: interpolation and standard formatting is done lazily
|
||
var msg = {
|
||
timestamp: new Date(),
|
||
level: level,
|
||
category: category,
|
||
message: message,
|
||
'arguments': args
|
||
/*standard*/
|
||
/*full*/
|
||
/*fullMessage*/
|
||
};
|
||
// process this message
|
||
forge.log.logMessage(msg);
|
||
};
|
||
})(levels[i]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Creates a new logger with specified custom logging function.
|
||
*
|
||
* The logging function has a signature of:
|
||
* function(logger, message)
|
||
* logger: current logger
|
||
* message: object:
|
||
* level: level id
|
||
* category: category
|
||
* message: string message
|
||
* arguments: Array of extra arguments
|
||
* fullMessage: interpolated message and arguments if INTERPOLATE flag set
|
||
*
|
||
* @param logFunction a logging function which takes a log message object
|
||
* as a parameter.
|
||
*
|
||
* @return a logger object.
|
||
*/
|
||
forge.log.makeLogger = function(logFunction) {
|
||
var logger = {
|
||
flags: 0,
|
||
f: logFunction
|
||
};
|
||
forge.log.setLevel(logger, 'none');
|
||
return logger;
|
||
};
|
||
|
||
/**
|
||
* Sets the current log level on a logger.
|
||
*
|
||
* @param logger the target logger.
|
||
* @param level the new maximum log level as a string.
|
||
*
|
||
* @return true if set, false if not.
|
||
*/
|
||
forge.log.setLevel = function(logger, level) {
|
||
var rval = false;
|
||
if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
|
||
for(var i = 0; i < forge.log.levels.length; ++i) {
|
||
var aValidLevel = forge.log.levels[i];
|
||
if(level == aValidLevel) {
|
||
// set level
|
||
logger.level = level;
|
||
rval = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return rval;
|
||
};
|
||
|
||
/**
|
||
* Locks the log level at its current value.
|
||
*
|
||
* @param logger the target logger.
|
||
* @param lock boolean lock value, default to true.
|
||
*/
|
||
forge.log.lock = function(logger, lock) {
|
||
if(typeof lock === 'undefined' || lock) {
|
||
logger.flags |= forge.log.LEVEL_LOCKED;
|
||
} else {
|
||
logger.flags &= ~forge.log.LEVEL_LOCKED;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Adds a logger.
|
||
*
|
||
* @param logger the logger object.
|
||
*/
|
||
forge.log.addLogger = function(logger) {
|
||
sLoggers.push(logger);
|
||
};
|
||
|
||
// setup the console logger if possible, else create fake console.log
|
||
if(typeof(console) !== 'undefined' && 'log' in console) {
|
||
var logger;
|
||
if(console.error && console.warn && console.info && console.debug) {
|
||
// looks like Firebug-style logging is available
|
||
// level handlers map
|
||
var levelHandlers = {
|
||
error: console.error,
|
||
warning: console.warn,
|
||
info: console.info,
|
||
debug: console.debug,
|
||
verbose: console.debug
|
||
};
|
||
var f = function(logger, message) {
|
||
forge.log.prepareStandard(message);
|
||
var handler = levelHandlers[message.level];
|
||
// prepend standard message and concat args
|
||
var args = [message.standard];
|
||
args = args.concat(message['arguments'].slice());
|
||
// apply to low-level console function
|
||
handler.apply(console, args);
|
||
};
|
||
logger = forge.log.makeLogger(f);
|
||
} else {
|
||
// only appear to have basic console.log
|
||
var f = function(logger, message) {
|
||
forge.log.prepareStandardFull(message);
|
||
console.log(message.standardFull);
|
||
};
|
||
logger = forge.log.makeLogger(f);
|
||
}
|
||
forge.log.setLevel(logger, 'debug');
|
||
forge.log.addLogger(logger);
|
||
sConsoleLogger = logger;
|
||
} else {
|
||
// define fake console.log to avoid potential script errors on
|
||
// browsers that do not have console logging
|
||
console = {
|
||
log: function() {}
|
||
};
|
||
}
|
||
|
||
/*
|
||
* Check for logging control query vars.
|
||
*
|
||
* console.level=<level-name>
|
||
* Set's the console log level by name. Useful to override defaults and
|
||
* allow more verbose logging before a user config is loaded.
|
||
*
|
||
* console.lock=<true|false>
|
||
* Lock the console log level at whatever level it is set at. This is run
|
||
* after console.level is processed. Useful to force a level of verbosity
|
||
* that could otherwise be limited by a user config.
|
||
*/
|
||
if(sConsoleLogger !== null) {
|
||
var query = forge.util.getQueryVariables();
|
||
if('console.level' in query) {
|
||
// set with last value
|
||
forge.log.setLevel(
|
||
sConsoleLogger, query['console.level'].slice(-1)[0]);
|
||
}
|
||
if('console.lock' in query) {
|
||
// set with last value
|
||
var lock = query['console.lock'].slice(-1)[0];
|
||
if(lock == 'true') {
|
||
forge.log.lock(sConsoleLogger);
|
||
}
|
||
}
|
||
}
|
||
|
||
// provide public access to console logger
|
||
forge.log.consoleLogger = sConsoleLogger;
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'log';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/log',['require', 'module', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Javascript implementation of PKCS#7 v1.5.
|
||
*
|
||
* @author Stefan Siegl
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
||
* Copyright (c) 2012-2015 Digital Bazaar, Inc.
|
||
*
|
||
* Currently this implementation only supports ContentType of EnvelopedData,
|
||
* EncryptedData, or SignedData at the root level. The top level elements may
|
||
* contain only a ContentInfo of ContentType Data, i.e. plain data. Further
|
||
* nesting is not (yet) supported.
|
||
*
|
||
* The Forge validators for PKCS #7's ASN.1 structures are available from
|
||
* a separate file pkcs7asn1.js, since those are referenced from other
|
||
* PKCS standards like PKCS #12.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// shortcut for ASN.1 API
|
||
var asn1 = forge.asn1;
|
||
|
||
// shortcut for PKCS#7 API
|
||
var p7 = forge.pkcs7 = forge.pkcs7 || {};
|
||
|
||
/**
|
||
* Converts a PKCS#7 message from PEM format.
|
||
*
|
||
* @param pem the PEM-formatted PKCS#7 message.
|
||
*
|
||
* @return the PKCS#7 message.
|
||
*/
|
||
p7.messageFromPem = function(pem) {
|
||
var msg = forge.pem.decode(pem)[0];
|
||
|
||
if(msg.type !== 'PKCS7') {
|
||
var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
|
||
'header type is not "PKCS#7".');
|
||
error.headerType = msg.type;
|
||
throw error;
|
||
}
|
||
if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
||
throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
|
||
}
|
||
|
||
// convert DER to ASN.1 object
|
||
var obj = asn1.fromDer(msg.body);
|
||
|
||
return p7.messageFromAsn1(obj);
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#7 message to PEM format.
|
||
*
|
||
* @param msg The PKCS#7 message object
|
||
* @param maxline The maximum characters per line, defaults to 64.
|
||
*
|
||
* @return The PEM-formatted PKCS#7 message.
|
||
*/
|
||
p7.messageToPem = function(msg, maxline) {
|
||
// convert to ASN.1, then DER, then PEM-encode
|
||
var pemObj = {
|
||
type: 'PKCS7',
|
||
body: asn1.toDer(msg.toAsn1()).getBytes()
|
||
};
|
||
return forge.pem.encode(pemObj, {maxline: maxline});
|
||
};
|
||
|
||
/**
|
||
* Converts a PKCS#7 message from an ASN.1 object.
|
||
*
|
||
* @param obj the ASN.1 representation of a ContentInfo.
|
||
*
|
||
* @return the PKCS#7 message.
|
||
*/
|
||
p7.messageFromAsn1 = function(obj) {
|
||
// validate root level ContentInfo and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors))
|
||
{
|
||
var error = new Error('Cannot read PKCS#7 message. ' +
|
||
'ASN.1 object is not an PKCS#7 ContentInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var contentType = asn1.derToOid(capture.contentType);
|
||
var msg;
|
||
|
||
switch(contentType) {
|
||
case forge.pki.oids.envelopedData:
|
||
msg = p7.createEnvelopedData();
|
||
break;
|
||
|
||
case forge.pki.oids.encryptedData:
|
||
msg = p7.createEncryptedData();
|
||
break;
|
||
|
||
case forge.pki.oids.signedData:
|
||
msg = p7.createSignedData();
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
|
||
contentType + ' is not (yet) supported.');
|
||
}
|
||
|
||
msg.fromAsn1(capture.content.value[0]);
|
||
return msg;
|
||
};
|
||
|
||
p7.createSignedData = function() {
|
||
var msg = null;
|
||
msg = {
|
||
type: forge.pki.oids.signedData,
|
||
version: 1,
|
||
certificates: [],
|
||
crls: [],
|
||
// TODO: add json-formatted signer stuff here?
|
||
signers: [],
|
||
// populated during sign()
|
||
digestAlgorithmIdentifiers: [],
|
||
contentInfo: null,
|
||
signerInfos: [],
|
||
|
||
fromAsn1: function(obj) {
|
||
// validate SignedData content block and capture data.
|
||
_fromAsn1(msg, obj, p7.asn1.signedDataValidator);
|
||
msg.certificates = [];
|
||
msg.crls = [];
|
||
msg.digestAlgorithmIdentifiers = [];
|
||
msg.contentInfo = null;
|
||
msg.signerInfos = [];
|
||
|
||
var certs = msg.rawCapture.certificates.value;
|
||
for(var i = 0; i < certs.length; ++i) {
|
||
msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
|
||
}
|
||
|
||
// TODO: parse crls
|
||
},
|
||
|
||
toAsn1: function() {
|
||
// degenerate case with no content
|
||
if(!msg.contentInfo) {
|
||
msg.sign();
|
||
}
|
||
|
||
var certs = [];
|
||
for(var i = 0; i < msg.certificates.length; ++i) {
|
||
certs.push(forge.pki.certificateToAsn1(msg.certificates[i]));
|
||
}
|
||
|
||
var crls = [];
|
||
// TODO: implement CRLs
|
||
|
||
// [0] SignedData
|
||
var signedData = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Version
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(msg.version).getBytes()),
|
||
// DigestAlgorithmIdentifiers
|
||
asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SET, true,
|
||
msg.digestAlgorithmIdentifiers),
|
||
// ContentInfo
|
||
msg.contentInfo
|
||
])
|
||
]);
|
||
if(certs.length > 0) {
|
||
// [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL
|
||
signedData.value[0].value.push(
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs));
|
||
}
|
||
if(crls.length > 0) {
|
||
// [1] IMPLICIT CertificateRevocationLists OPTIONAL
|
||
signedData.value[0].value.push(
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls));
|
||
}
|
||
// SignerInfos
|
||
signedData.value[0].value.push(
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
|
||
msg.signerInfos));
|
||
|
||
// ContentInfo
|
||
return asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// ContentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(msg.type).getBytes()),
|
||
// [0] SignedData
|
||
signedData
|
||
]);
|
||
},
|
||
|
||
/**
|
||
* Add (another) entity to list of signers.
|
||
*
|
||
* Note: If authenticatedAttributes are provided, then, per RFC 2315,
|
||
* they must include at least two attributes: content type and
|
||
* message digest. The message digest attribute value will be
|
||
* auto-calculated during signing and will be ignored if provided.
|
||
*
|
||
* Here's an example of providing these two attributes:
|
||
*
|
||
* forge.pkcs7.createSignedData();
|
||
* p7.addSigner({
|
||
* issuer: cert.issuer.attributes,
|
||
* serialNumber: cert.serialNumber,
|
||
* key: privateKey,
|
||
* digestAlgorithm: forge.pki.oids.sha1,
|
||
* authenticatedAttributes: [{
|
||
* type: forge.pki.oids.contentType,
|
||
* value: forge.pki.oids.data
|
||
* }, {
|
||
* type: forge.pki.oids.messageDigest
|
||
* }]
|
||
* });
|
||
*
|
||
* TODO: Support [subjectKeyIdentifier] as signer's ID.
|
||
*
|
||
* @param signer the signer information:
|
||
* key the signer's private key.
|
||
* [certificate] a certificate containing the public key
|
||
* associated with the signer's private key; use this option as
|
||
* an alternative to specifying signer.issuer and
|
||
* signer.serialNumber.
|
||
* [issuer] the issuer attributes (eg: cert.issuer.attributes).
|
||
* [serialNumber] the signer's certificate's serial number in
|
||
* hexadecimal (eg: cert.serialNumber).
|
||
* [digestAlgorithm] the message digest OID, as a string, to use
|
||
* (eg: forge.pki.oids.sha1).
|
||
* [authenticatedAttributes] an optional array of attributes
|
||
* to also sign along with the content.
|
||
*/
|
||
addSigner: function(signer) {
|
||
var issuer = signer.issuer;
|
||
var serialNumber = signer.serialNumber;
|
||
if(signer.certificate) {
|
||
var cert = signer.certificate;
|
||
if(typeof cert === 'string') {
|
||
cert = forge.pki.certificateFromPem(cert);
|
||
}
|
||
issuer = cert.issuer.attributes;
|
||
serialNumber = cert.serialNumber;
|
||
}
|
||
var key = signer.key;
|
||
if(!key) {
|
||
throw new Error(
|
||
'Could not add PKCS#7 signer; no private key specified.');
|
||
}
|
||
if(typeof key === 'string') {
|
||
key = forge.pki.privateKeyFromPem(key);
|
||
}
|
||
|
||
// ensure OID known for digest algorithm
|
||
var digestAlgorithm = signer.digestAlgorithm || forge.pki.oids.sha1;
|
||
switch(digestAlgorithm) {
|
||
case forge.pki.oids.sha1:
|
||
case forge.pki.oids.sha256:
|
||
case forge.pki.oids.sha384:
|
||
case forge.pki.oids.sha512:
|
||
case forge.pki.oids.md5:
|
||
break;
|
||
default:
|
||
throw new Error(
|
||
'Could not add PKCS#7 signer; unknown message digest algorithm: ' +
|
||
digestAlgorithm);
|
||
}
|
||
|
||
// if authenticatedAttributes is present, then the attributes
|
||
// must contain at least PKCS #9 content-type and message-digest
|
||
var authenticatedAttributes = signer.authenticatedAttributes || [];
|
||
if(authenticatedAttributes.length > 0) {
|
||
var contentType = false;
|
||
var messageDigest = false;
|
||
for(var i = 0; i < authenticatedAttributes.length; ++i) {
|
||
var attr = authenticatedAttributes[i];
|
||
if(!contentType && attr.type === forge.pki.oids.contentType) {
|
||
contentType = true;
|
||
if(messageDigest) {
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
if(!messageDigest && attr.type === forge.pki.oids.messageDigest) {
|
||
messageDigest = true;
|
||
if(contentType) {
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if(!contentType || !messageDigest) {
|
||
throw new Error('Invalid signer.authenticatedAttributes. If ' +
|
||
'signer.authenticatedAttributes is specified, then it must ' +
|
||
'contain at least two attributes, PKCS #9 content-type and ' +
|
||
'PKCS #9 message-digest.');
|
||
}
|
||
}
|
||
|
||
msg.signers.push({
|
||
key: key,
|
||
version: 1,
|
||
issuer: issuer,
|
||
serialNumber: serialNumber,
|
||
digestAlgorithm: digestAlgorithm,
|
||
signatureAlgorithm: forge.pki.oids.rsaEncryption,
|
||
signature: null,
|
||
authenticatedAttributes: authenticatedAttributes,
|
||
unauthenticatedAttributes: []
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Signs the content.
|
||
*/
|
||
sign: function() {
|
||
// auto-generate content info
|
||
if(typeof msg.content !== 'object' || msg.contentInfo === null) {
|
||
// use Data ContentInfo
|
||
msg.contentInfo = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// ContentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(forge.pki.oids.data).getBytes())
|
||
]);
|
||
|
||
// add actual content, if present
|
||
if('content' in msg) {
|
||
var content;
|
||
if(msg.content instanceof forge.util.ByteBuffer) {
|
||
content = msg.content.bytes();
|
||
} else if(typeof msg.content === 'string') {
|
||
content = forge.util.encodeUtf8(msg.content);
|
||
}
|
||
|
||
msg.contentInfo.value.push(
|
||
// [0] EXPLICIT content
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
content)
|
||
]));
|
||
}
|
||
}
|
||
|
||
// no signers, return early (degenerate case for certificate container)
|
||
if(msg.signers.length === 0) {
|
||
return;
|
||
}
|
||
|
||
// generate digest algorithm identifiers
|
||
var mds = addDigestAlgorithmIds();
|
||
|
||
// generate signerInfos
|
||
addSignerInfos(mds);
|
||
},
|
||
|
||
verify: function() {
|
||
throw new Error('PKCS#7 signature verification not yet implemented.');
|
||
},
|
||
|
||
/**
|
||
* Add a certificate.
|
||
*
|
||
* @param cert the certificate to add.
|
||
*/
|
||
addCertificate: function(cert) {
|
||
// convert from PEM
|
||
if(typeof cert === 'string') {
|
||
cert = forge.pki.certificateFromPem(cert);
|
||
}
|
||
msg.certificates.push(cert);
|
||
},
|
||
|
||
/**
|
||
* Add a certificate revokation list.
|
||
*
|
||
* @param crl the certificate revokation list to add.
|
||
*/
|
||
addCertificateRevokationList: function(crl) {
|
||
throw new Error('PKCS#7 CRL support not yet implemented.');
|
||
}
|
||
};
|
||
return msg;
|
||
|
||
function addDigestAlgorithmIds() {
|
||
var mds = {};
|
||
|
||
for(var i = 0; i < msg.signers.length; ++i) {
|
||
var signer = msg.signers[i];
|
||
var oid = signer.digestAlgorithm;
|
||
if(!(oid in mds)) {
|
||
// content digest
|
||
mds[oid] = forge.md[forge.pki.oids[oid]].create();
|
||
}
|
||
if(signer.authenticatedAttributes.length === 0) {
|
||
// no custom attributes to digest; use content message digest
|
||
signer.md = mds[oid];
|
||
} else {
|
||
// custom attributes to be digested; use own message digest
|
||
// TODO: optimize to just copy message digest state if that
|
||
// feature is ever supported with message digests
|
||
signer.md = forge.md[forge.pki.oids[oid]].create();
|
||
}
|
||
}
|
||
|
||
// add unique digest algorithm identifiers
|
||
msg.digestAlgorithmIdentifiers = [];
|
||
for(var oid in mds) {
|
||
msg.digestAlgorithmIdentifiers.push(
|
||
// AlgorithmIdentifier
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(oid).getBytes()),
|
||
// parameters (null)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]));
|
||
}
|
||
|
||
return mds;
|
||
}
|
||
|
||
function addSignerInfos(mds) {
|
||
// Note: ContentInfo is a SEQUENCE with 2 values, second value is
|
||
// the content field and is optional for a ContentInfo but required here
|
||
// since signers are present
|
||
if(msg.contentInfo.value.length < 2) {
|
||
throw new Error(
|
||
'Could not sign PKCS#7 message; there is no content to sign.');
|
||
}
|
||
|
||
// get ContentInfo content type
|
||
var contentType = asn1.derToOid(msg.contentInfo.value[0].value);
|
||
|
||
// get ContentInfo content
|
||
var content = msg.contentInfo.value[1];
|
||
// skip [0] EXPLICIT content wrapper
|
||
content = content.value[0];
|
||
|
||
// serialize content
|
||
var bytes = asn1.toDer(content);
|
||
|
||
// skip identifier and length per RFC 2315 9.3
|
||
// skip identifier (1 byte)
|
||
bytes.getByte();
|
||
// read and discard length bytes
|
||
asn1.getBerValueLength(bytes);
|
||
bytes = bytes.getBytes();
|
||
|
||
// digest content DER value bytes
|
||
for(var oid in mds) {
|
||
mds[oid].start().update(bytes);
|
||
}
|
||
|
||
// sign content
|
||
var signingTime = new Date();
|
||
for(var i = 0; i < msg.signers.length; ++i) {
|
||
var signer = msg.signers[i];
|
||
|
||
if(signer.authenticatedAttributes.length === 0) {
|
||
// if ContentInfo content type is not "Data", then
|
||
// authenticatedAttributes must be present per RFC 2315
|
||
if(contentType !== forge.pki.oids.data) {
|
||
throw new Error(
|
||
'Invalid signer; authenticatedAttributes must be present ' +
|
||
'when the ContentInfo content type is not PKCS#7 Data.');
|
||
}
|
||
} else {
|
||
// process authenticated attributes
|
||
// [0] IMPLICIT
|
||
signer.authenticatedAttributesAsn1 = asn1.create(
|
||
asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
|
||
|
||
// per RFC 2315, attributes are to be digested using a SET container
|
||
// not the above [0] IMPLICIT container
|
||
var attrsAsn1 = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.SET, true, []);
|
||
|
||
for(var ai = 0; ai < signer.authenticatedAttributes.length; ++ai) {
|
||
var attr = signer.authenticatedAttributes[ai];
|
||
if(attr.type === forge.pki.oids.messageDigest) {
|
||
// use content message digest as value
|
||
attr.value = mds[signer.digestAlgorithm].digest();
|
||
} else if(attr.type === forge.pki.oids.signingTime) {
|
||
// auto-populate signing time if not already set
|
||
if(!attr.value) {
|
||
attr.value = signingTime;
|
||
}
|
||
}
|
||
|
||
// convert to ASN.1 and push onto Attributes SET (for signing) and
|
||
// onto authenticatedAttributesAsn1 to complete SignedData ASN.1
|
||
// TODO: optimize away duplication
|
||
attrsAsn1.value.push(_attributeToAsn1(attr));
|
||
signer.authenticatedAttributesAsn1.value.push(_attributeToAsn1(attr));
|
||
}
|
||
|
||
// DER-serialize and digest SET OF attributes only
|
||
bytes = asn1.toDer(attrsAsn1).getBytes();
|
||
signer.md.start().update(bytes);
|
||
}
|
||
|
||
// sign digest
|
||
signer.signature = signer.key.sign(signer.md, 'RSASSA-PKCS1-V1_5');
|
||
}
|
||
|
||
// add signer info
|
||
msg.signerInfos = _signersToAsn1(msg.signers);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Creates an empty PKCS#7 message of type EncryptedData.
|
||
*
|
||
* @return the message.
|
||
*/
|
||
p7.createEncryptedData = function() {
|
||
var msg = null;
|
||
msg = {
|
||
type: forge.pki.oids.encryptedData,
|
||
version: 0,
|
||
encryptedContent: {
|
||
algorithm: forge.pki.oids['aes256-CBC']
|
||
},
|
||
|
||
/**
|
||
* Reads an EncryptedData content block (in ASN.1 format)
|
||
*
|
||
* @param obj The ASN.1 representation of the EncryptedData content block
|
||
*/
|
||
fromAsn1: function(obj) {
|
||
// Validate EncryptedData content block and capture data.
|
||
_fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
|
||
},
|
||
|
||
/**
|
||
* Decrypt encrypted content
|
||
*
|
||
* @param key The (symmetric) key as a byte buffer
|
||
*/
|
||
decrypt: function(key) {
|
||
if(key !== undefined) {
|
||
msg.encryptedContent.key = key;
|
||
}
|
||
_decryptContent(msg);
|
||
}
|
||
};
|
||
return msg;
|
||
};
|
||
|
||
/**
|
||
* Creates an empty PKCS#7 message of type EnvelopedData.
|
||
*
|
||
* @return the message.
|
||
*/
|
||
p7.createEnvelopedData = function() {
|
||
var msg = null;
|
||
msg = {
|
||
type: forge.pki.oids.envelopedData,
|
||
version: 0,
|
||
recipients: [],
|
||
encryptedContent: {
|
||
algorithm: forge.pki.oids['aes256-CBC']
|
||
},
|
||
|
||
/**
|
||
* Reads an EnvelopedData content block (in ASN.1 format)
|
||
*
|
||
* @param obj the ASN.1 representation of the EnvelopedData content block.
|
||
*/
|
||
fromAsn1: function(obj) {
|
||
// validate EnvelopedData content block and capture data
|
||
var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
|
||
msg.recipients = _recipientsFromAsn1(capture.recipientInfos.value);
|
||
},
|
||
|
||
toAsn1: function() {
|
||
// ContentInfo
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// ContentType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(msg.type).getBytes()),
|
||
// [0] EnvelopedData
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Version
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(msg.version).getBytes()),
|
||
// RecipientInfos
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
|
||
_recipientsToAsn1(msg.recipients)),
|
||
// EncryptedContentInfo
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
|
||
_encryptedContentToAsn1(msg.encryptedContent))
|
||
])
|
||
])
|
||
]);
|
||
},
|
||
|
||
/**
|
||
* Find recipient by X.509 certificate's issuer.
|
||
*
|
||
* @param cert the certificate with the issuer to look for.
|
||
*
|
||
* @return the recipient object.
|
||
*/
|
||
findRecipient: function(cert) {
|
||
var sAttr = cert.issuer.attributes;
|
||
|
||
for(var i = 0; i < msg.recipients.length; ++i) {
|
||
var r = msg.recipients[i];
|
||
var rAttr = r.issuer;
|
||
|
||
if(r.serialNumber !== cert.serialNumber) {
|
||
continue;
|
||
}
|
||
|
||
if(rAttr.length !== sAttr.length) {
|
||
continue;
|
||
}
|
||
|
||
var match = true;
|
||
for(var j = 0; j < sAttr.length; ++j) {
|
||
if(rAttr[j].type !== sAttr[j].type ||
|
||
rAttr[j].value !== sAttr[j].value) {
|
||
match = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(match) {
|
||
return r;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* Decrypt enveloped content
|
||
*
|
||
* @param recipient The recipient object related to the private key
|
||
* @param privKey The (RSA) private key object
|
||
*/
|
||
decrypt: function(recipient, privKey) {
|
||
if(msg.encryptedContent.key === undefined && recipient !== undefined &&
|
||
privKey !== undefined) {
|
||
switch(recipient.encryptedContent.algorithm) {
|
||
case forge.pki.oids.rsaEncryption:
|
||
case forge.pki.oids.desCBC:
|
||
var key = privKey.decrypt(recipient.encryptedContent.content);
|
||
msg.encryptedContent.key = forge.util.createBuffer(key);
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Unsupported asymmetric cipher, ' +
|
||
'OID ' + recipient.encryptedContent.algorithm);
|
||
}
|
||
}
|
||
|
||
_decryptContent(msg);
|
||
},
|
||
|
||
/**
|
||
* Add (another) entity to list of recipients.
|
||
*
|
||
* @param cert The certificate of the entity to add.
|
||
*/
|
||
addRecipient: function(cert) {
|
||
msg.recipients.push({
|
||
version: 0,
|
||
issuer: cert.issuer.attributes,
|
||
serialNumber: cert.serialNumber,
|
||
encryptedContent: {
|
||
// We simply assume rsaEncryption here, since forge.pki only
|
||
// supports RSA so far. If the PKI module supports other
|
||
// ciphers one day, we need to modify this one as well.
|
||
algorithm: forge.pki.oids.rsaEncryption,
|
||
key: cert.publicKey
|
||
}
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Encrypt enveloped content.
|
||
*
|
||
* This function supports two optional arguments, cipher and key, which
|
||
* can be used to influence symmetric encryption. Unless cipher is
|
||
* provided, the cipher specified in encryptedContent.algorithm is used
|
||
* (defaults to AES-256-CBC). If no key is provided, encryptedContent.key
|
||
* is (re-)used. If that one's not set, a random key will be generated
|
||
* automatically.
|
||
*
|
||
* @param [key] The key to be used for symmetric encryption.
|
||
* @param [cipher] The OID of the symmetric cipher to use.
|
||
*/
|
||
encrypt: function(key, cipher) {
|
||
// Part 1: Symmetric encryption
|
||
if(msg.encryptedContent.content === undefined) {
|
||
cipher = cipher || msg.encryptedContent.algorithm;
|
||
key = key || msg.encryptedContent.key;
|
||
|
||
var keyLen, ivLen, ciphFn;
|
||
switch(cipher) {
|
||
case forge.pki.oids['aes128-CBC']:
|
||
keyLen = 16;
|
||
ivLen = 16;
|
||
ciphFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
|
||
case forge.pki.oids['aes192-CBC']:
|
||
keyLen = 24;
|
||
ivLen = 16;
|
||
ciphFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
|
||
case forge.pki.oids['aes256-CBC']:
|
||
keyLen = 32;
|
||
ivLen = 16;
|
||
ciphFn = forge.aes.createEncryptionCipher;
|
||
break;
|
||
|
||
case forge.pki.oids['des-EDE3-CBC']:
|
||
keyLen = 24;
|
||
ivLen = 8;
|
||
ciphFn = forge.des.createEncryptionCipher;
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Unsupported symmetric cipher, OID ' + cipher);
|
||
}
|
||
|
||
if(key === undefined) {
|
||
key = forge.util.createBuffer(forge.random.getBytes(keyLen));
|
||
} else if(key.length() != keyLen) {
|
||
throw new Error('Symmetric key has wrong length; ' +
|
||
'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
|
||
}
|
||
|
||
// Keep a copy of the key & IV in the object, so the caller can
|
||
// use it for whatever reason.
|
||
msg.encryptedContent.algorithm = cipher;
|
||
msg.encryptedContent.key = key;
|
||
msg.encryptedContent.parameter = forge.util.createBuffer(
|
||
forge.random.getBytes(ivLen));
|
||
|
||
var ciph = ciphFn(key);
|
||
ciph.start(msg.encryptedContent.parameter.copy());
|
||
ciph.update(msg.content);
|
||
|
||
// The finish function does PKCS#7 padding by default, therefore
|
||
// no action required by us.
|
||
if(!ciph.finish()) {
|
||
throw new Error('Symmetric encryption failed.');
|
||
}
|
||
|
||
msg.encryptedContent.content = ciph.output;
|
||
}
|
||
|
||
// Part 2: asymmetric encryption for each recipient
|
||
for(var i = 0; i < msg.recipients.length; ++i) {
|
||
var recipient = msg.recipients[i];
|
||
|
||
// Nothing to do, encryption already done.
|
||
if(recipient.encryptedContent.content !== undefined) {
|
||
continue;
|
||
}
|
||
|
||
switch(recipient.encryptedContent.algorithm) {
|
||
case forge.pki.oids.rsaEncryption:
|
||
recipient.encryptedContent.content =
|
||
recipient.encryptedContent.key.encrypt(
|
||
msg.encryptedContent.key.data);
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Unsupported asymmetric cipher, OID ' +
|
||
recipient.encryptedContent.algorithm);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
return msg;
|
||
};
|
||
|
||
/**
|
||
* Converts a single recipient from an ASN.1 object.
|
||
*
|
||
* @param obj the ASN.1 RecipientInfo.
|
||
*
|
||
* @return the recipient object.
|
||
*/
|
||
function _recipientFromAsn1(obj) {
|
||
// validate EnvelopedData content block and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#7 RecipientInfo. ' +
|
||
'ASN.1 object is not an PKCS#7 RecipientInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
return {
|
||
version: capture.version.charCodeAt(0),
|
||
issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
|
||
serialNumber: forge.util.createBuffer(capture.serial).toHex(),
|
||
encryptedContent: {
|
||
algorithm: asn1.derToOid(capture.encAlgorithm),
|
||
parameter: capture.encParameter.value,
|
||
content: capture.encKey
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Converts a single recipient object to an ASN.1 object.
|
||
*
|
||
* @param obj the recipient object.
|
||
*
|
||
* @return the ASN.1 RecipientInfo.
|
||
*/
|
||
function _recipientToAsn1(obj) {
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Version
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(obj.version).getBytes()),
|
||
// IssuerAndSerialNumber
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Name
|
||
forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
|
||
// Serial
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
forge.util.hexToBytes(obj.serialNumber))
|
||
]),
|
||
// KeyEncryptionAlgorithmIdentifier
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
|
||
// Parameter, force NULL, only RSA supported for now.
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]),
|
||
// EncryptedKey
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
obj.encryptedContent.content)
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Map a set of RecipientInfo ASN.1 objects to recipient objects.
|
||
*
|
||
* @param infos an array of ASN.1 representations RecipientInfo (i.e. SET OF).
|
||
*
|
||
* @return an array of recipient objects.
|
||
*/
|
||
function _recipientsFromAsn1(infos) {
|
||
var ret = [];
|
||
for(var i = 0; i < infos.length; ++i) {
|
||
ret.push(_recipientFromAsn1(infos[i]));
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* Map an array of recipient objects to ASN.1 RecipientInfo objects.
|
||
*
|
||
* @param recipients an array of recipientInfo objects.
|
||
*
|
||
* @return an array of ASN.1 RecipientInfos.
|
||
*/
|
||
function _recipientsToAsn1(recipients) {
|
||
var ret = [];
|
||
for(var i = 0; i < recipients.length; ++i) {
|
||
ret.push(_recipientToAsn1(recipients[i]));
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* Converts a single signer from an ASN.1 object.
|
||
*
|
||
* @param obj the ASN.1 representation of a SignerInfo.
|
||
*
|
||
* @return the signer object.
|
||
*/
|
||
function _signerFromAsn1(obj) {
|
||
// validate EnvelopedData content block and capture data
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, p7.asn1.signerInfoValidator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#7 SignerInfo. ' +
|
||
'ASN.1 object is not an PKCS#7 SignerInfo.');
|
||
error.errors = errors;
|
||
throw error;
|
||
}
|
||
|
||
var rval = {
|
||
version: capture.version.charCodeAt(0),
|
||
issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
|
||
serialNumber: forge.util.createBuffer(capture.serial).toHex(),
|
||
digestAlgorithm: asn1.derToOid(capture.digestAlgorithm),
|
||
signatureAlgorithm: asn1.derToOid(capture.signatureAlgorithm),
|
||
signature: capture.signature,
|
||
authenticatedAttributes: [],
|
||
unauthenticatedAttributes: []
|
||
};
|
||
|
||
// TODO: convert attributes
|
||
var authenticatedAttributes = capture.authenticatedAttributes || [];
|
||
var unauthenticatedAttributes = capture.unauthenticatedAttributes || [];
|
||
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Converts a single signerInfo object to an ASN.1 object.
|
||
*
|
||
* @param obj the signerInfo object.
|
||
*
|
||
* @return the ASN.1 representation of a SignerInfo.
|
||
*/
|
||
function _signerToAsn1(obj) {
|
||
// SignerInfo
|
||
var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// version
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
asn1.integerToDer(obj.version).getBytes()),
|
||
// issuerAndSerialNumber
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// name
|
||
forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
|
||
// serial
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
||
forge.util.hexToBytes(obj.serialNumber))
|
||
]),
|
||
// digestAlgorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(obj.digestAlgorithm).getBytes()),
|
||
// parameters (null)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
])
|
||
]);
|
||
|
||
// authenticatedAttributes (OPTIONAL)
|
||
if(obj.authenticatedAttributesAsn1) {
|
||
// add ASN.1 previously generated during signing
|
||
rval.value.push(obj.authenticatedAttributesAsn1);
|
||
}
|
||
|
||
// digestEncryptionAlgorithm
|
||
rval.value.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(obj.signatureAlgorithm).getBytes()),
|
||
// parameters (null)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||
]));
|
||
|
||
// encryptedDigest
|
||
rval.value.push(asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, obj.signature));
|
||
|
||
// unauthenticatedAttributes (OPTIONAL)
|
||
if(obj.unauthenticatedAttributes.length > 0) {
|
||
// [1] IMPLICIT
|
||
var attrsAsn1 = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, []);
|
||
for(var i = 0; i < obj.unauthenticatedAttributes.length; ++i) {
|
||
var attr = obj.unauthenticatedAttributes[i];
|
||
attrsAsn1.values.push(_attributeToAsn1(attr));
|
||
}
|
||
rval.value.push(attrsAsn1);
|
||
}
|
||
|
||
return rval;
|
||
}
|
||
|
||
/**
|
||
* Map a set of SignerInfo ASN.1 objects to an array of signer objects.
|
||
*
|
||
* @param signerInfoAsn1s an array of ASN.1 SignerInfos (i.e. SET OF).
|
||
*
|
||
* @return an array of signers objects.
|
||
*/
|
||
function _signersFromAsn1(signerInfoAsn1s) {
|
||
var ret = [];
|
||
for(var i = 0; i < signerInfoAsn1s.length; ++i) {
|
||
ret.push(_signerFromAsn1(signerInfoAsn1s[i]));
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* Map an array of signer objects to ASN.1 objects.
|
||
*
|
||
* @param signers an array of signer objects.
|
||
*
|
||
* @return an array of ASN.1 SignerInfos.
|
||
*/
|
||
function _signersToAsn1(signers) {
|
||
var ret = [];
|
||
for(var i = 0; i < signers.length; ++i) {
|
||
ret.push(_signerToAsn1(signers[i]));
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* Convert an attribute object to an ASN.1 Attribute.
|
||
*
|
||
* @param attr the attribute object.
|
||
*
|
||
* @return the ASN.1 Attribute.
|
||
*/
|
||
function _attributeToAsn1(attr) {
|
||
var value;
|
||
|
||
// TODO: generalize to support more attributes
|
||
if(attr.type === forge.pki.oids.contentType) {
|
||
value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(attr.value).getBytes());
|
||
} else if(attr.type === forge.pki.oids.messageDigest) {
|
||
value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
attr.value.bytes());
|
||
} else if(attr.type === forge.pki.oids.signingTime) {
|
||
/* Note per RFC 2985: Dates between 1 January 1950 and 31 December 2049
|
||
(inclusive) MUST be encoded as UTCTime. Any dates with year values
|
||
before 1950 or after 2049 MUST be encoded as GeneralizedTime. [Further,]
|
||
UTCTime values MUST be expressed in Greenwich Mean Time (Zulu) and MUST
|
||
include seconds (i.e., times are YYMMDDHHMMSSZ), even where the
|
||
number of seconds is zero. Midnight (GMT) must be represented as
|
||
"YYMMDD000000Z". */
|
||
// TODO: make these module-level constants
|
||
var jan_1_1950 = new Date('Jan 1, 1950 00:00:00Z');
|
||
var jan_1_2050 = new Date('Jan 1, 2050 00:00:00Z');
|
||
var date = attr.value;
|
||
if(typeof date === 'string') {
|
||
// try to parse date
|
||
var timestamp = Date.parse(date);
|
||
if(!isNaN(timestamp)) {
|
||
date = new Date(timestamp);
|
||
} else if(date.length === 13) {
|
||
// YYMMDDHHMMSSZ (13 chars for UTCTime)
|
||
date = asn1.utcTimeToDate(date);
|
||
} else {
|
||
// assume generalized time
|
||
date = asn1.generalizedTimeToDate(date);
|
||
}
|
||
}
|
||
|
||
if(date >= jan_1_1950 && date < jan_1_2050) {
|
||
value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
|
||
asn1.dateToUtcTime(date));
|
||
} else {
|
||
value = asn1.create(
|
||
asn1.Class.UNIVERSAL, asn1.Type.GENERALIZEDTIME, false,
|
||
asn1.dateToGeneralizedTime(date));
|
||
}
|
||
}
|
||
|
||
// TODO: expose as common API call
|
||
// create a RelativeDistinguishedName set
|
||
// each value in the set is an AttributeTypeAndValue first
|
||
// containing the type (an OID) and second the value
|
||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// AttributeType
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(attr.type).getBytes()),
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||
// AttributeValue
|
||
value
|
||
])
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Map messages encrypted content to ASN.1 objects.
|
||
*
|
||
* @param ec The encryptedContent object of the message.
|
||
*
|
||
* @return ASN.1 representation of the encryptedContent object (SEQUENCE).
|
||
*/
|
||
function _encryptedContentToAsn1(ec) {
|
||
return [
|
||
// ContentType, always Data for the moment
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(forge.pki.oids.data).getBytes()),
|
||
// ContentEncryptionAlgorithmIdentifier
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||
// Algorithm
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
||
asn1.oidToDer(ec.algorithm).getBytes()),
|
||
// Parameters (IV)
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
ec.parameter.getBytes())
|
||
]),
|
||
// [0] EncryptedContent
|
||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
||
ec.content.getBytes())
|
||
])
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
|
||
*
|
||
* This function reads the "common part" of the PKCS#7 content blocks
|
||
* EncryptedData and EnvelopedData, i.e. version number and symmetrically
|
||
* encrypted content block.
|
||
*
|
||
* The result of the ASN.1 validate and capture process is returned
|
||
* to allow the caller to extract further data, e.g. the list of recipients
|
||
* in case of a EnvelopedData object.
|
||
*
|
||
* @param msg the PKCS#7 object to read the data to.
|
||
* @param obj the ASN.1 representation of the content block.
|
||
* @param validator the ASN.1 structure validator object to use.
|
||
*
|
||
* @return the value map captured by validator object.
|
||
*/
|
||
function _fromAsn1(msg, obj, validator) {
|
||
var capture = {};
|
||
var errors = [];
|
||
if(!asn1.validate(obj, validator, capture, errors)) {
|
||
var error = new Error('Cannot read PKCS#7 message. ' +
|
||
'ASN.1 object is not a supported PKCS#7 message.');
|
||
error.errors = error;
|
||
throw error;
|
||
}
|
||
|
||
// Check contentType, so far we only support (raw) Data.
|
||
var contentType = asn1.derToOid(capture.contentType);
|
||
if(contentType !== forge.pki.oids.data) {
|
||
throw new Error('Unsupported PKCS#7 message. ' +
|
||
'Only wrapped ContentType Data supported.');
|
||
}
|
||
|
||
if(capture.encryptedContent) {
|
||
var content = '';
|
||
if(forge.util.isArray(capture.encryptedContent)) {
|
||
for(var i = 0; i < capture.encryptedContent.length; ++i) {
|
||
if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
|
||
throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
|
||
'content constructed of only OCTET STRING objects.');
|
||
}
|
||
content += capture.encryptedContent[i].value;
|
||
}
|
||
} else {
|
||
content = capture.encryptedContent;
|
||
}
|
||
msg.encryptedContent = {
|
||
algorithm: asn1.derToOid(capture.encAlgorithm),
|
||
parameter: forge.util.createBuffer(capture.encParameter.value),
|
||
content: forge.util.createBuffer(content)
|
||
};
|
||
}
|
||
|
||
if(capture.content) {
|
||
var content = '';
|
||
if(forge.util.isArray(capture.content)) {
|
||
for(var i = 0; i < capture.content.length; ++i) {
|
||
if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
|
||
throw new Error('Malformed PKCS#7 message, expecting ' +
|
||
'content constructed of only OCTET STRING objects.');
|
||
}
|
||
content += capture.content[i].value;
|
||
}
|
||
} else {
|
||
content = capture.content;
|
||
}
|
||
msg.content = forge.util.createBuffer(content);
|
||
}
|
||
|
||
msg.version = capture.version.charCodeAt(0);
|
||
msg.rawCapture = capture;
|
||
|
||
return capture;
|
||
}
|
||
|
||
/**
|
||
* Decrypt the symmetrically encrypted content block of the PKCS#7 message.
|
||
*
|
||
* Decryption is skipped in case the PKCS#7 message object already has a
|
||
* (decrypted) content attribute. The algorithm, key and cipher parameters
|
||
* (probably the iv) are taken from the encryptedContent attribute of the
|
||
* message object.
|
||
*
|
||
* @param The PKCS#7 message object.
|
||
*/
|
||
function _decryptContent(msg) {
|
||
if(msg.encryptedContent.key === undefined) {
|
||
throw new Error('Symmetric key not available.');
|
||
}
|
||
|
||
if(msg.content === undefined) {
|
||
var ciph;
|
||
|
||
switch(msg.encryptedContent.algorithm) {
|
||
case forge.pki.oids['aes128-CBC']:
|
||
case forge.pki.oids['aes192-CBC']:
|
||
case forge.pki.oids['aes256-CBC']:
|
||
ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
|
||
break;
|
||
|
||
case forge.pki.oids['desCBC']:
|
||
case forge.pki.oids['des-EDE3-CBC']:
|
||
ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
|
||
break;
|
||
|
||
default:
|
||
throw new Error('Unsupported symmetric cipher, OID ' +
|
||
msg.encryptedContent.algorithm);
|
||
}
|
||
ciph.start(msg.encryptedContent.parameter);
|
||
ciph.update(msg.encryptedContent.content);
|
||
|
||
if(!ciph.finish()) {
|
||
throw new Error('Symmetric decryption failed.');
|
||
}
|
||
|
||
msg.content = ciph.output;
|
||
}
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'pkcs7';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/pkcs7',[
|
||
'require',
|
||
'module',
|
||
'./aes',
|
||
'./asn1',
|
||
'./des',
|
||
'./oids',
|
||
'./pem',
|
||
'./pkcs7asn1',
|
||
'./random',
|
||
'./util',
|
||
'./x509'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Functions to output keys in SSH-friendly formats.
|
||
*
|
||
* This is part of the Forge project which may be used under the terms of
|
||
* either the BSD License or the GNU General Public License (GPL) Version 2.
|
||
*
|
||
* See: https://github.com/digitalbazaar/forge/blob/cbebca3780658703d925b61b2caffb1d263a6c1d/LICENSE
|
||
*
|
||
* @author https://github.com/shellac
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
var ssh = forge.ssh = forge.ssh || {};
|
||
|
||
/**
|
||
* Encodes (and optionally encrypts) a private RSA key as a Putty PPK file.
|
||
*
|
||
* @param privateKey the key.
|
||
* @param passphrase a passphrase to protect the key (falsy for no encryption).
|
||
* @param comment a comment to include in the key file.
|
||
*
|
||
* @return the PPK file as a string.
|
||
*/
|
||
ssh.privateKeyToPutty = function(privateKey, passphrase, comment) {
|
||
comment = comment || '';
|
||
passphrase = passphrase || '';
|
||
var algorithm = 'ssh-rsa';
|
||
var encryptionAlgorithm = (passphrase === '') ? 'none' : 'aes256-cbc';
|
||
|
||
var ppk = 'PuTTY-User-Key-File-2: ' + algorithm + '\r\n';
|
||
ppk += 'Encryption: ' + encryptionAlgorithm + '\r\n';
|
||
ppk += 'Comment: ' + comment + '\r\n';
|
||
|
||
// public key into buffer for ppk
|
||
var pubbuffer = forge.util.createBuffer();
|
||
_addStringToBuffer(pubbuffer, algorithm);
|
||
_addBigIntegerToBuffer(pubbuffer, privateKey.e);
|
||
_addBigIntegerToBuffer(pubbuffer, privateKey.n);
|
||
|
||
// write public key
|
||
var pub = forge.util.encode64(pubbuffer.bytes(), 64);
|
||
var length = Math.floor(pub.length / 66) + 1; // 66 = 64 + \r\n
|
||
ppk += 'Public-Lines: ' + length + '\r\n';
|
||
ppk += pub;
|
||
|
||
// private key into a buffer
|
||
var privbuffer = forge.util.createBuffer();
|
||
_addBigIntegerToBuffer(privbuffer, privateKey.d);
|
||
_addBigIntegerToBuffer(privbuffer, privateKey.p);
|
||
_addBigIntegerToBuffer(privbuffer, privateKey.q);
|
||
_addBigIntegerToBuffer(privbuffer, privateKey.qInv);
|
||
|
||
// optionally encrypt the private key
|
||
var priv;
|
||
if(!passphrase) {
|
||
// use the unencrypted buffer
|
||
priv = forge.util.encode64(privbuffer.bytes(), 64);
|
||
} else {
|
||
// encrypt RSA key using passphrase
|
||
var encLen = privbuffer.length() + 16 - 1;
|
||
encLen -= encLen % 16;
|
||
|
||
// pad private key with sha1-d data -- needs to be a multiple of 16
|
||
var padding = _sha1(privbuffer.bytes());
|
||
|
||
padding.truncate(padding.length() - encLen + privbuffer.length());
|
||
privbuffer.putBuffer(padding);
|
||
|
||
var aeskey = forge.util.createBuffer();
|
||
aeskey.putBuffer(_sha1('\x00\x00\x00\x00', passphrase));
|
||
aeskey.putBuffer(_sha1('\x00\x00\x00\x01', passphrase));
|
||
|
||
// encrypt some bytes using CBC mode
|
||
// key is 40 bytes, so truncate *by* 8 bytes
|
||
var cipher = forge.aes.createEncryptionCipher(aeskey.truncate(8), 'CBC');
|
||
cipher.start(forge.util.createBuffer().fillWithByte(0, 16));
|
||
cipher.update(privbuffer.copy());
|
||
cipher.finish();
|
||
var encrypted = cipher.output;
|
||
|
||
// Note: this appears to differ from Putty -- is forge wrong, or putty?
|
||
// due to padding we finish as an exact multiple of 16
|
||
encrypted.truncate(16); // all padding
|
||
|
||
priv = forge.util.encode64(encrypted.bytes(), 64);
|
||
}
|
||
|
||
// output private key
|
||
length = Math.floor(priv.length / 66) + 1; // 64 + \r\n
|
||
ppk += '\r\nPrivate-Lines: ' + length + '\r\n';
|
||
ppk += priv;
|
||
|
||
// MAC
|
||
var mackey = _sha1('putty-private-key-file-mac-key', passphrase);
|
||
|
||
var macbuffer = forge.util.createBuffer();
|
||
_addStringToBuffer(macbuffer, algorithm);
|
||
_addStringToBuffer(macbuffer, encryptionAlgorithm);
|
||
_addStringToBuffer(macbuffer, comment);
|
||
macbuffer.putInt32(pubbuffer.length());
|
||
macbuffer.putBuffer(pubbuffer);
|
||
macbuffer.putInt32(privbuffer.length());
|
||
macbuffer.putBuffer(privbuffer);
|
||
|
||
var hmac = forge.hmac.create();
|
||
hmac.start('sha1', mackey);
|
||
hmac.update(macbuffer.bytes());
|
||
|
||
ppk += '\r\nPrivate-MAC: ' + hmac.digest().toHex() + '\r\n';
|
||
|
||
return ppk;
|
||
};
|
||
|
||
/**
|
||
* Encodes a public RSA key as an OpenSSH file.
|
||
*
|
||
* @param key the key.
|
||
* @param comment a comment.
|
||
*
|
||
* @return the public key in OpenSSH format.
|
||
*/
|
||
ssh.publicKeyToOpenSSH = function(key, comment) {
|
||
var type = 'ssh-rsa';
|
||
comment = comment || '';
|
||
|
||
var buffer = forge.util.createBuffer();
|
||
_addStringToBuffer(buffer, type);
|
||
_addBigIntegerToBuffer(buffer, key.e);
|
||
_addBigIntegerToBuffer(buffer, key.n);
|
||
|
||
return type + ' ' + forge.util.encode64(buffer.bytes()) + ' ' + comment;
|
||
};
|
||
|
||
/**
|
||
* Encodes a private RSA key as an OpenSSH file.
|
||
*
|
||
* @param key the key.
|
||
* @param passphrase a passphrase to protect the key (falsy for no encryption).
|
||
*
|
||
* @return the public key in OpenSSH format.
|
||
*/
|
||
ssh.privateKeyToOpenSSH = function(privateKey, passphrase) {
|
||
if(!passphrase) {
|
||
return forge.pki.privateKeyToPem(privateKey);
|
||
}
|
||
// OpenSSH private key is just a legacy format, it seems
|
||
return forge.pki.encryptRsaPrivateKey(privateKey, passphrase,
|
||
{legacy: true, algorithm: 'aes128'});
|
||
};
|
||
|
||
/**
|
||
* Gets the SSH fingerprint for the given public key.
|
||
*
|
||
* @param options the options to use.
|
||
* [md] the message digest object to use (defaults to forge.md.md5).
|
||
* [encoding] an alternative output encoding, such as 'hex'
|
||
* (defaults to none, outputs a byte buffer).
|
||
* [delimiter] the delimiter to use between bytes for 'hex' encoded
|
||
* output, eg: ':' (defaults to none).
|
||
*
|
||
* @return the fingerprint as a byte buffer or other encoding based on options.
|
||
*/
|
||
ssh.getPublicKeyFingerprint = function(key, options) {
|
||
options = options || {};
|
||
var md = options.md || forge.md.md5.create();
|
||
|
||
var type = 'ssh-rsa';
|
||
var buffer = forge.util.createBuffer();
|
||
_addStringToBuffer(buffer, type);
|
||
_addBigIntegerToBuffer(buffer, key.e);
|
||
_addBigIntegerToBuffer(buffer, key.n);
|
||
|
||
// hash public key bytes
|
||
md.start();
|
||
md.update(buffer.getBytes());
|
||
var digest = md.digest();
|
||
if(options.encoding === 'hex') {
|
||
var hex = digest.toHex();
|
||
if(options.delimiter) {
|
||
return hex.match(/.{2}/g).join(options.delimiter);
|
||
}
|
||
return hex;
|
||
} else if(options.encoding === 'binary') {
|
||
return digest.getBytes();
|
||
} else if(options.encoding) {
|
||
throw new Error('Unknown encoding "' + options.encoding + '".');
|
||
}
|
||
return digest;
|
||
};
|
||
|
||
/**
|
||
* Adds len(val) then val to a buffer.
|
||
*
|
||
* @param buffer the buffer to add to.
|
||
* @param val a big integer.
|
||
*/
|
||
function _addBigIntegerToBuffer(buffer, val) {
|
||
var hexVal = val.toString(16);
|
||
// ensure 2s complement +ve
|
||
if(hexVal[0] >= '8') {
|
||
hexVal = '00' + hexVal;
|
||
}
|
||
var bytes = forge.util.hexToBytes(hexVal);
|
||
buffer.putInt32(bytes.length);
|
||
buffer.putBytes(bytes);
|
||
}
|
||
|
||
/**
|
||
* Adds len(val) then val to a buffer.
|
||
*
|
||
* @param buffer the buffer to add to.
|
||
* @param val a string.
|
||
*/
|
||
function _addStringToBuffer(buffer, val) {
|
||
buffer.putInt32(val.length);
|
||
buffer.putString(val);
|
||
}
|
||
|
||
/**
|
||
* Hashes the arguments into one value using SHA-1.
|
||
*
|
||
* @return the sha1 hash of the provided arguments.
|
||
*/
|
||
function _sha1() {
|
||
var sha = forge.md.sha1.create();
|
||
var num = arguments.length;
|
||
for (var i = 0; i < num; ++i) {
|
||
sha.update(arguments[i]);
|
||
}
|
||
return sha.digest();
|
||
}
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'ssh';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/ssh',[
|
||
'require',
|
||
'module',
|
||
'./aes',
|
||
'./hmac',
|
||
'./md5',
|
||
'./sha1',
|
||
'./util'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Support for concurrent task management and synchronization in web
|
||
* applications.
|
||
*
|
||
* @author Dave Longley
|
||
* @author David I. Lehn <dlehn@digitalbazaar.com>
|
||
*
|
||
* Copyright (c) 2009-2013 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
/* ########## Begin module implementation ########## */
|
||
function initModule(forge) {
|
||
|
||
// logging category
|
||
var cat = 'forge.task';
|
||
|
||
// verbose level
|
||
// 0: off, 1: a little, 2: a whole lot
|
||
// Verbose debug logging is surrounded by a level check to avoid the
|
||
// performance issues with even calling the logging code regardless if it
|
||
// is actually logged. For performance reasons this should not be set to 2
|
||
// for production use.
|
||
// ex: if(sVL >= 2) forge.log.verbose(....)
|
||
var sVL = 0;
|
||
|
||
// track tasks for debugging
|
||
var sTasks = {};
|
||
var sNextTaskId = 0;
|
||
// debug access
|
||
forge.debug.set(cat, 'tasks', sTasks);
|
||
|
||
// a map of task type to task queue
|
||
var sTaskQueues = {};
|
||
// debug access
|
||
forge.debug.set(cat, 'queues', sTaskQueues);
|
||
|
||
// name for unnamed tasks
|
||
var sNoTaskName = '?';
|
||
|
||
// maximum number of doNext() recursions before a context swap occurs
|
||
// FIXME: might need to tweak this based on the browser
|
||
var sMaxRecursions = 30;
|
||
|
||
// time slice for doing tasks before a context swap occurs
|
||
// FIXME: might need to tweak this based on the browser
|
||
var sTimeSlice = 20;
|
||
|
||
/**
|
||
* Task states.
|
||
*
|
||
* READY: ready to start processing
|
||
* RUNNING: task or a subtask is running
|
||
* BLOCKED: task is waiting to acquire N permits to continue
|
||
* SLEEPING: task is sleeping for a period of time
|
||
* DONE: task is done
|
||
* ERROR: task has an error
|
||
*/
|
||
var READY = 'ready';
|
||
var RUNNING = 'running';
|
||
var BLOCKED = 'blocked';
|
||
var SLEEPING = 'sleeping';
|
||
var DONE = 'done';
|
||
var ERROR = 'error';
|
||
|
||
/**
|
||
* Task actions. Used to control state transitions.
|
||
*
|
||
* STOP: stop processing
|
||
* START: start processing tasks
|
||
* BLOCK: block task from continuing until 1 or more permits are released
|
||
* UNBLOCK: release one or more permits
|
||
* SLEEP: sleep for a period of time
|
||
* WAKEUP: wakeup early from SLEEPING state
|
||
* CANCEL: cancel further tasks
|
||
* FAIL: a failure occured
|
||
*/
|
||
var STOP = 'stop';
|
||
var START = 'start';
|
||
var BLOCK = 'block';
|
||
var UNBLOCK = 'unblock';
|
||
var SLEEP = 'sleep';
|
||
var WAKEUP = 'wakeup';
|
||
var CANCEL = 'cancel';
|
||
var FAIL = 'fail';
|
||
|
||
/**
|
||
* State transition table.
|
||
*
|
||
* nextState = sStateTable[currentState][action]
|
||
*/
|
||
var sStateTable = {};
|
||
|
||
sStateTable[READY] = {};
|
||
sStateTable[READY][STOP] = READY;
|
||
sStateTable[READY][START] = RUNNING;
|
||
sStateTable[READY][CANCEL] = DONE;
|
||
sStateTable[READY][FAIL] = ERROR;
|
||
|
||
sStateTable[RUNNING] = {};
|
||
sStateTable[RUNNING][STOP] = READY;
|
||
sStateTable[RUNNING][START] = RUNNING;
|
||
sStateTable[RUNNING][BLOCK] = BLOCKED;
|
||
sStateTable[RUNNING][UNBLOCK] = RUNNING;
|
||
sStateTable[RUNNING][SLEEP] = SLEEPING;
|
||
sStateTable[RUNNING][WAKEUP] = RUNNING;
|
||
sStateTable[RUNNING][CANCEL] = DONE;
|
||
sStateTable[RUNNING][FAIL] = ERROR;
|
||
|
||
sStateTable[BLOCKED] = {};
|
||
sStateTable[BLOCKED][STOP] = BLOCKED;
|
||
sStateTable[BLOCKED][START] = BLOCKED;
|
||
sStateTable[BLOCKED][BLOCK] = BLOCKED;
|
||
sStateTable[BLOCKED][UNBLOCK] = BLOCKED;
|
||
sStateTable[BLOCKED][SLEEP] = BLOCKED;
|
||
sStateTable[BLOCKED][WAKEUP] = BLOCKED;
|
||
sStateTable[BLOCKED][CANCEL] = DONE;
|
||
sStateTable[BLOCKED][FAIL] = ERROR;
|
||
|
||
sStateTable[SLEEPING] = {};
|
||
sStateTable[SLEEPING][STOP] = SLEEPING;
|
||
sStateTable[SLEEPING][START] = SLEEPING;
|
||
sStateTable[SLEEPING][BLOCK] = SLEEPING;
|
||
sStateTable[SLEEPING][UNBLOCK] = SLEEPING;
|
||
sStateTable[SLEEPING][SLEEP] = SLEEPING;
|
||
sStateTable[SLEEPING][WAKEUP] = SLEEPING;
|
||
sStateTable[SLEEPING][CANCEL] = DONE;
|
||
sStateTable[SLEEPING][FAIL] = ERROR;
|
||
|
||
sStateTable[DONE] = {};
|
||
sStateTable[DONE][STOP] = DONE;
|
||
sStateTable[DONE][START] = DONE;
|
||
sStateTable[DONE][BLOCK] = DONE;
|
||
sStateTable[DONE][UNBLOCK] = DONE;
|
||
sStateTable[DONE][SLEEP] = DONE;
|
||
sStateTable[DONE][WAKEUP] = DONE;
|
||
sStateTable[DONE][CANCEL] = DONE;
|
||
sStateTable[DONE][FAIL] = ERROR;
|
||
|
||
sStateTable[ERROR] = {};
|
||
sStateTable[ERROR][STOP] = ERROR;
|
||
sStateTable[ERROR][START] = ERROR;
|
||
sStateTable[ERROR][BLOCK] = ERROR;
|
||
sStateTable[ERROR][UNBLOCK] = ERROR;
|
||
sStateTable[ERROR][SLEEP] = ERROR;
|
||
sStateTable[ERROR][WAKEUP] = ERROR;
|
||
sStateTable[ERROR][CANCEL] = ERROR;
|
||
sStateTable[ERROR][FAIL] = ERROR;
|
||
|
||
/**
|
||
* Creates a new task.
|
||
*
|
||
* @param options options for this task
|
||
* run: the run function for the task (required)
|
||
* name: the run function for the task (optional)
|
||
* parent: parent of this task (optional)
|
||
*
|
||
* @return the empty task.
|
||
*/
|
||
var Task = function(options) {
|
||
// task id
|
||
this.id = -1;
|
||
|
||
// task name
|
||
this.name = options.name || sNoTaskName;
|
||
|
||
// task has no parent
|
||
this.parent = options.parent || null;
|
||
|
||
// save run function
|
||
this.run = options.run;
|
||
|
||
// create a queue of subtasks to run
|
||
this.subtasks = [];
|
||
|
||
// error flag
|
||
this.error = false;
|
||
|
||
// state of the task
|
||
this.state = READY;
|
||
|
||
// number of times the task has been blocked (also the number
|
||
// of permits needed to be released to continue running)
|
||
this.blocks = 0;
|
||
|
||
// timeout id when sleeping
|
||
this.timeoutId = null;
|
||
|
||
// no swap time yet
|
||
this.swapTime = null;
|
||
|
||
// no user data
|
||
this.userData = null;
|
||
|
||
// initialize task
|
||
// FIXME: deal with overflow
|
||
this.id = sNextTaskId++;
|
||
sTasks[this.id] = this;
|
||
if(sVL >= 1) {
|
||
forge.log.verbose(cat, '[%s][%s] init', this.id, this.name, this);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Logs debug information on this task and the system state.
|
||
*/
|
||
Task.prototype.debug = function(msg) {
|
||
msg = msg || '';
|
||
forge.log.debug(cat, msg,
|
||
'[%s][%s] task:', this.id, this.name, this,
|
||
'subtasks:', this.subtasks.length,
|
||
'queue:', sTaskQueues);
|
||
};
|
||
|
||
/**
|
||
* Adds a subtask to run after task.doNext() or task.fail() is called.
|
||
*
|
||
* @param name human readable name for this task (optional).
|
||
* @param subrun a function to run that takes the current task as
|
||
* its first parameter.
|
||
*
|
||
* @return the current task (useful for chaining next() calls).
|
||
*/
|
||
Task.prototype.next = function(name, subrun) {
|
||
// juggle parameters if it looks like no name is given
|
||
if(typeof(name) === 'function') {
|
||
subrun = name;
|
||
|
||
// inherit parent's name
|
||
name = this.name;
|
||
}
|
||
// create subtask, set parent to this task, propagate callbacks
|
||
var subtask = new Task({
|
||
run: subrun,
|
||
name: name,
|
||
parent: this
|
||
});
|
||
// start subtasks running
|
||
subtask.state = RUNNING;
|
||
subtask.type = this.type;
|
||
subtask.successCallback = this.successCallback || null;
|
||
subtask.failureCallback = this.failureCallback || null;
|
||
|
||
// queue a new subtask
|
||
this.subtasks.push(subtask);
|
||
|
||
return this;
|
||
};
|
||
|
||
/**
|
||
* Adds subtasks to run in parallel after task.doNext() or task.fail()
|
||
* is called.
|
||
*
|
||
* @param name human readable name for this task (optional).
|
||
* @param subrun functions to run that take the current task as
|
||
* their first parameter.
|
||
*
|
||
* @return the current task (useful for chaining next() calls).
|
||
*/
|
||
Task.prototype.parallel = function(name, subrun) {
|
||
// juggle parameters if it looks like no name is given
|
||
if(forge.util.isArray(name)) {
|
||
subrun = name;
|
||
|
||
// inherit parent's name
|
||
name = this.name;
|
||
}
|
||
// Wrap parallel tasks in a regular task so they are started at the
|
||
// proper time.
|
||
return this.next(name, function(task) {
|
||
// block waiting for subtasks
|
||
var ptask = task;
|
||
ptask.block(subrun.length);
|
||
|
||
// we pass the iterator from the loop below as a parameter
|
||
// to a function because it is otherwise included in the
|
||
// closure and changes as the loop changes -- causing i
|
||
// to always be set to its highest value
|
||
var startParallelTask = function(pname, pi) {
|
||
forge.task.start({
|
||
type: pname,
|
||
run: function(task) {
|
||
subrun[pi](task);
|
||
},
|
||
success: function(task) {
|
||
ptask.unblock();
|
||
},
|
||
failure: function(task) {
|
||
ptask.unblock();
|
||
}
|
||
});
|
||
};
|
||
|
||
for(var i = 0; i < subrun.length; i++) {
|
||
// Type must be unique so task starts in parallel:
|
||
// name + private string + task id + sub-task index
|
||
// start tasks in parallel and unblock when the finish
|
||
var pname = name + '__parallel-' + task.id + '-' + i;
|
||
var pi = i;
|
||
startParallelTask(pname, pi);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Stops a running task.
|
||
*/
|
||
Task.prototype.stop = function() {
|
||
this.state = sStateTable[this.state][STOP];
|
||
};
|
||
|
||
/**
|
||
* Starts running a task.
|
||
*/
|
||
Task.prototype.start = function() {
|
||
this.error = false;
|
||
this.state = sStateTable[this.state][START];
|
||
|
||
// try to restart
|
||
if(this.state === RUNNING) {
|
||
this.start = new Date();
|
||
this.run(this);
|
||
runNext(this, 0);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Blocks a task until it one or more permits have been released. The
|
||
* task will not resume until the requested number of permits have
|
||
* been released with call(s) to unblock().
|
||
*
|
||
* @param n number of permits to wait for(default: 1).
|
||
*/
|
||
Task.prototype.block = function(n) {
|
||
n = typeof(n) === 'undefined' ? 1 : n;
|
||
this.blocks += n;
|
||
if(this.blocks > 0) {
|
||
this.state = sStateTable[this.state][BLOCK];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Releases a permit to unblock a task. If a task was blocked by
|
||
* requesting N permits via block(), then it will only continue
|
||
* running once enough permits have been released via unblock() calls.
|
||
*
|
||
* If multiple processes need to synchronize with a single task then
|
||
* use a condition variable (see forge.task.createCondition). It is
|
||
* an error to unblock a task more times than it has been blocked.
|
||
*
|
||
* @param n number of permits to release (default: 1).
|
||
*
|
||
* @return the current block count (task is unblocked when count is 0)
|
||
*/
|
||
Task.prototype.unblock = function(n) {
|
||
n = typeof(n) === 'undefined' ? 1 : n;
|
||
this.blocks -= n;
|
||
if(this.blocks === 0 && this.state !== DONE) {
|
||
this.state = RUNNING;
|
||
runNext(this, 0);
|
||
}
|
||
return this.blocks;
|
||
};
|
||
|
||
/**
|
||
* Sleep for a period of time before resuming tasks.
|
||
*
|
||
* @param n number of milliseconds to sleep (default: 0).
|
||
*/
|
||
Task.prototype.sleep = function(n) {
|
||
n = typeof(n) === 'undefined' ? 0 : n;
|
||
this.state = sStateTable[this.state][SLEEP];
|
||
var self = this;
|
||
this.timeoutId = setTimeout(function() {
|
||
self.timeoutId = null;
|
||
self.state = RUNNING;
|
||
runNext(self, 0);
|
||
}, n);
|
||
};
|
||
|
||
/**
|
||
* Waits on a condition variable until notified. The next task will
|
||
* not be scheduled until notification. A condition variable can be
|
||
* created with forge.task.createCondition().
|
||
*
|
||
* Once cond.notify() is called, the task will continue.
|
||
*
|
||
* @param cond the condition variable to wait on.
|
||
*/
|
||
Task.prototype.wait = function(cond) {
|
||
cond.wait(this);
|
||
};
|
||
|
||
/**
|
||
* If sleeping, wakeup and continue running tasks.
|
||
*/
|
||
Task.prototype.wakeup = function() {
|
||
if(this.state === SLEEPING) {
|
||
cancelTimeout(this.timeoutId);
|
||
this.timeoutId = null;
|
||
this.state = RUNNING;
|
||
runNext(this, 0);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Cancel all remaining subtasks of this task.
|
||
*/
|
||
Task.prototype.cancel = function() {
|
||
this.state = sStateTable[this.state][CANCEL];
|
||
// remove permits needed
|
||
this.permitsNeeded = 0;
|
||
// cancel timeouts
|
||
if(this.timeoutId !== null) {
|
||
cancelTimeout(this.timeoutId);
|
||
this.timeoutId = null;
|
||
}
|
||
// remove subtasks
|
||
this.subtasks = [];
|
||
};
|
||
|
||
/**
|
||
* Finishes this task with failure and sets error flag. The entire
|
||
* task will be aborted unless the next task that should execute
|
||
* is passed as a parameter. This allows levels of subtasks to be
|
||
* skipped. For instance, to abort only this tasks's subtasks, then
|
||
* call fail(task.parent). To abort this task's subtasks and its
|
||
* parent's subtasks, call fail(task.parent.parent). To abort
|
||
* all tasks and simply call the task callback, call fail() or
|
||
* fail(null).
|
||
*
|
||
* The task callback (success or failure) will always, eventually, be
|
||
* called.
|
||
*
|
||
* @param next the task to continue at, or null to abort entirely.
|
||
*/
|
||
Task.prototype.fail = function(next) {
|
||
// set error flag
|
||
this.error = true;
|
||
|
||
// finish task
|
||
finish(this, true);
|
||
|
||
if(next) {
|
||
// propagate task info
|
||
next.error = this.error;
|
||
next.swapTime = this.swapTime;
|
||
next.userData = this.userData;
|
||
|
||
// do next task as specified
|
||
runNext(next, 0);
|
||
} else {
|
||
if(this.parent !== null) {
|
||
// finish root task (ensures it is removed from task queue)
|
||
var parent = this.parent;
|
||
while(parent.parent !== null) {
|
||
// propagate task info
|
||
parent.error = this.error;
|
||
parent.swapTime = this.swapTime;
|
||
parent.userData = this.userData;
|
||
parent = parent.parent;
|
||
}
|
||
finish(parent, true);
|
||
}
|
||
|
||
// call failure callback if one exists
|
||
if(this.failureCallback) {
|
||
this.failureCallback(this);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Asynchronously start a task.
|
||
*
|
||
* @param task the task to start.
|
||
*/
|
||
var start = function(task) {
|
||
task.error = false;
|
||
task.state = sStateTable[task.state][START];
|
||
setTimeout(function() {
|
||
if(task.state === RUNNING) {
|
||
task.swapTime = +new Date();
|
||
task.run(task);
|
||
runNext(task, 0);
|
||
}
|
||
}, 0);
|
||
};
|
||
|
||
/**
|
||
* Run the next subtask or finish this task.
|
||
*
|
||
* @param task the task to process.
|
||
* @param recurse the recursion count.
|
||
*/
|
||
var runNext = function(task, recurse) {
|
||
// get time since last context swap (ms), if enough time has passed set
|
||
// swap to true to indicate that doNext was performed asynchronously
|
||
// also, if recurse is too high do asynchronously
|
||
var swap =
|
||
(recurse > sMaxRecursions) ||
|
||
(+new Date() - task.swapTime) > sTimeSlice;
|
||
|
||
var doNext = function(recurse) {
|
||
recurse++;
|
||
if(task.state === RUNNING) {
|
||
if(swap) {
|
||
// update swap time
|
||
task.swapTime = +new Date();
|
||
}
|
||
|
||
if(task.subtasks.length > 0) {
|
||
// run next subtask
|
||
var subtask = task.subtasks.shift();
|
||
subtask.error = task.error;
|
||
subtask.swapTime = task.swapTime;
|
||
subtask.userData = task.userData;
|
||
subtask.run(subtask);
|
||
if(!subtask.error) {
|
||
runNext(subtask, recurse);
|
||
}
|
||
} else {
|
||
finish(task);
|
||
|
||
if(!task.error) {
|
||
// chain back up and run parent
|
||
if(task.parent !== null) {
|
||
// propagate task info
|
||
task.parent.error = task.error;
|
||
task.parent.swapTime = task.swapTime;
|
||
task.parent.userData = task.userData;
|
||
|
||
// no subtasks left, call run next subtask on parent
|
||
runNext(task.parent, recurse);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
if(swap) {
|
||
// we're swapping, so run asynchronously
|
||
setTimeout(doNext, 0);
|
||
} else {
|
||
// not swapping, so run synchronously
|
||
doNext(recurse);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Finishes a task and looks for the next task in the queue to start.
|
||
*
|
||
* @param task the task to finish.
|
||
* @param suppressCallbacks true to suppress callbacks.
|
||
*/
|
||
var finish = function(task, suppressCallbacks) {
|
||
// subtask is now done
|
||
task.state = DONE;
|
||
|
||
delete sTasks[task.id];
|
||
if(sVL >= 1) {
|
||
forge.log.verbose(cat, '[%s][%s] finish',
|
||
task.id, task.name, task);
|
||
}
|
||
|
||
// only do queue processing for root tasks
|
||
if(task.parent === null) {
|
||
// report error if queue is missing
|
||
if(!(task.type in sTaskQueues)) {
|
||
forge.log.error(cat,
|
||
'[%s][%s] task queue missing [%s]',
|
||
task.id, task.name, task.type);
|
||
} else if(sTaskQueues[task.type].length === 0) {
|
||
// report error if queue is empty
|
||
forge.log.error(cat,
|
||
'[%s][%s] task queue empty [%s]',
|
||
task.id, task.name, task.type);
|
||
} else if(sTaskQueues[task.type][0] !== task) {
|
||
// report error if this task isn't the first in the queue
|
||
forge.log.error(cat,
|
||
'[%s][%s] task not first in queue [%s]',
|
||
task.id, task.name, task.type);
|
||
} else {
|
||
// remove ourselves from the queue
|
||
sTaskQueues[task.type].shift();
|
||
// clean up queue if it is empty
|
||
if(sTaskQueues[task.type].length === 0) {
|
||
if(sVL >= 1) {
|
||
forge.log.verbose(cat, '[%s][%s] delete queue [%s]',
|
||
task.id, task.name, task.type);
|
||
}
|
||
/* Note: Only a task can delete a queue of its own type. This
|
||
is used as a way to synchronize tasks. If a queue for a certain
|
||
task type exists, then a task of that type is running.
|
||
*/
|
||
delete sTaskQueues[task.type];
|
||
} else {
|
||
// dequeue the next task and start it
|
||
if(sVL >= 1) {
|
||
forge.log.verbose(cat,
|
||
'[%s][%s] queue start next [%s] remain:%s',
|
||
task.id, task.name, task.type,
|
||
sTaskQueues[task.type].length);
|
||
}
|
||
sTaskQueues[task.type][0].start();
|
||
}
|
||
}
|
||
|
||
if(!suppressCallbacks) {
|
||
// call final callback if one exists
|
||
if(task.error && task.failureCallback) {
|
||
task.failureCallback(task);
|
||
} else if(!task.error && task.successCallback) {
|
||
task.successCallback(task);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/* Tasks API */
|
||
forge.task = forge.task || {};
|
||
|
||
/**
|
||
* Starts a new task that will run the passed function asynchronously.
|
||
*
|
||
* In order to finish the task, either task.doNext() or task.fail()
|
||
* *must* be called.
|
||
*
|
||
* The task must have a type (a string identifier) that can be used to
|
||
* synchronize it with other tasks of the same type. That type can also
|
||
* be used to cancel tasks that haven't started yet.
|
||
*
|
||
* To start a task, the following object must be provided as a parameter
|
||
* (each function takes a task object as its first parameter):
|
||
*
|
||
* {
|
||
* type: the type of task.
|
||
* run: the function to run to execute the task.
|
||
* success: a callback to call when the task succeeds (optional).
|
||
* failure: a callback to call when the task fails (optional).
|
||
* }
|
||
*
|
||
* @param options the object as described above.
|
||
*/
|
||
forge.task.start = function(options) {
|
||
// create a new task
|
||
var task = new Task({
|
||
run: options.run,
|
||
name: options.name || sNoTaskName
|
||
});
|
||
task.type = options.type;
|
||
task.successCallback = options.success || null;
|
||
task.failureCallback = options.failure || null;
|
||
|
||
// append the task onto the appropriate queue
|
||
if(!(task.type in sTaskQueues)) {
|
||
if(sVL >= 1) {
|
||
forge.log.verbose(cat, '[%s][%s] create queue [%s]',
|
||
task.id, task.name, task.type);
|
||
}
|
||
// create the queue with the new task
|
||
sTaskQueues[task.type] = [task];
|
||
start(task);
|
||
} else {
|
||
// push the task onto the queue, it will be run after a task
|
||
// with the same type completes
|
||
sTaskQueues[options.type].push(task);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Cancels all tasks of the given type that haven't started yet.
|
||
*
|
||
* @param type the type of task to cancel.
|
||
*/
|
||
forge.task.cancel = function(type) {
|
||
// find the task queue
|
||
if(type in sTaskQueues) {
|
||
// empty all but the current task from the queue
|
||
sTaskQueues[type] = [sTaskQueues[type][0]];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Creates a condition variable to synchronize tasks. To make a task wait
|
||
* on the condition variable, call task.wait(condition). To notify all
|
||
* tasks that are waiting, call condition.notify().
|
||
*
|
||
* @return the condition variable.
|
||
*/
|
||
forge.task.createCondition = function() {
|
||
var cond = {
|
||
// all tasks that are blocked
|
||
tasks: {}
|
||
};
|
||
|
||
/**
|
||
* Causes the given task to block until notify is called. If the task
|
||
* is already waiting on this condition then this is a no-op.
|
||
*
|
||
* @param task the task to cause to wait.
|
||
*/
|
||
cond.wait = function(task) {
|
||
// only block once
|
||
if(!(task.id in cond.tasks)) {
|
||
task.block();
|
||
cond.tasks[task.id] = task;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Notifies all waiting tasks to wake up.
|
||
*/
|
||
cond.notify = function() {
|
||
// since unblock() will run the next task from here, make sure to
|
||
// clear the condition's blocked task list before unblocking
|
||
var tmp = cond.tasks;
|
||
cond.tasks = {};
|
||
for(var id in tmp) {
|
||
tmp[id].unblock();
|
||
}
|
||
};
|
||
|
||
return cond;
|
||
};
|
||
|
||
} // end module implementation
|
||
|
||
/* ########## Begin module wrapper ########## */
|
||
var name = 'task';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
forge = {};
|
||
}
|
||
return initModule(forge);
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
}).concat(initModule);
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge[name];
|
||
};
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/task',['require', 'module', './debug', './log', './util'], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
/**
|
||
* Node.js module for Forge.
|
||
*
|
||
* @author Dave Longley
|
||
*
|
||
* Copyright 2011-2014 Digital Bazaar, Inc.
|
||
*/
|
||
(function() {
|
||
var name = 'forge';
|
||
if(typeof define !== 'function') {
|
||
// NodeJS -> AMD
|
||
if(typeof module === 'object' && module.exports) {
|
||
var nodeJS = true;
|
||
define = function(ids, factory) {
|
||
factory(require, module);
|
||
};
|
||
} else {
|
||
// <script>
|
||
if(typeof forge === 'undefined') {
|
||
// set to true to disable native code if even it's available
|
||
forge = {disableNativeCode: false};
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
// AMD
|
||
var deps;
|
||
var defineFunc = function(require, module) {
|
||
module.exports = function(forge) {
|
||
var mods = deps.map(function(dep) {
|
||
return require(dep);
|
||
});
|
||
// handle circular dependencies
|
||
forge = forge || {};
|
||
forge.defined = forge.defined || {};
|
||
if(forge.defined[name]) {
|
||
return forge[name];
|
||
}
|
||
forge.defined[name] = true;
|
||
for(var i = 0; i < mods.length; ++i) {
|
||
mods[i](forge);
|
||
}
|
||
return forge;
|
||
};
|
||
// set to true to disable native code if even it's available
|
||
module.exports.disableNativeCode = true;
|
||
module.exports(module.exports);
|
||
};
|
||
var tmpDefine = define;
|
||
define = function(ids, factory) {
|
||
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
|
||
if(nodeJS) {
|
||
delete define;
|
||
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
}
|
||
define = tmpDefine;
|
||
return define.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
};
|
||
define('js/forge',[
|
||
'require',
|
||
'module',
|
||
'./aes',
|
||
'./aesCipherSuites',
|
||
'./asn1',
|
||
'./cipher',
|
||
'./cipherModes',
|
||
'./debug',
|
||
'./des',
|
||
'./hmac',
|
||
'./kem',
|
||
'./log',
|
||
'./md',
|
||
'./mgf1',
|
||
'./pbkdf2',
|
||
'./pem',
|
||
'./pkcs7',
|
||
'./pkcs1',
|
||
'./pkcs12',
|
||
'./pki',
|
||
'./prime',
|
||
'./prng',
|
||
'./pss',
|
||
'./random',
|
||
'./rc2',
|
||
'./ssh',
|
||
'./task',
|
||
'./tls',
|
||
'./util'
|
||
], function() {
|
||
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
|
||
});
|
||
})();
|
||
|
||
|
||
return require('js/forge');
|
||
|
||
});
|
||
/**
|
||
* @fileoverview Intel(r) AMT Certificate functions
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.2.0b
|
||
*/
|
||
|
||
// Check which key pair matches the public key in the certificate
|
||
function amtcert_linkCertPrivateKey(certs, keys) {
|
||
for (var i in certs) {
|
||
var cert = certs[i];
|
||
try {
|
||
if (xxCertPrivateKeys.length == 0) return;
|
||
var publicKeyPEM = forge.pki.publicKeyToPem(forge.pki.certificateFromAsn1(forge.asn1.fromDer(cert.X509Certificate)).publicKey).substring(28 + 32).replace(/(\r\n|\n|\r)/gm, "");
|
||
for (var j = 0; j < keys.length; j++) {
|
||
if (publicKeyPEM === (keys[j]['DERKey'] + '-----END PUBLIC KEY-----')) {
|
||
keys[j].XCert = cert; // Link the key pair to the certificate
|
||
cert.XPrivateKey = keys[j]; // Link the certificate to the key pair
|
||
}
|
||
}
|
||
} catch (e) { console.log(e); }
|
||
}
|
||
}
|
||
|
||
// Load a P12 file, decodes it using the password and returns the private key handle
|
||
function amtcert_loadP12File(file, password, func) {
|
||
try {
|
||
// Encode in Base64 so Forge API can parse it.
|
||
var p12Der = window.forge.util.decode64(btoa(file));
|
||
var p12Asn1 = window.forge.asn1.fromDer(p12Der);
|
||
var p12 = window.forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
|
||
|
||
// Private key is stored in a shrouded key bag
|
||
var bags = p12.getBags({ bagType: window.forge.pki.oids.pkcs8ShroudedKeyBag });
|
||
console.assert(bags[window.forge.pki.oids.pkcs8ShroudedKeyBag] && bags[window.forge.pki.oids.pkcs8ShroudedKeyBag].length > 0);
|
||
|
||
// Import the Forge private key structure into Web Crypto
|
||
var privateKey = bags[window.forge.pki.oids.pkcs8ShroudedKeyBag][0].key;
|
||
var rsaPrivateKey = window.forge.pki.privateKeyToAsn1(privateKey);
|
||
var privateKeyInfo = window.forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
|
||
var pkcs8 = window.forge.asn1.toDer(privateKeyInfo).getBytes();
|
||
|
||
// Get the issuer attributes
|
||
var certBags = p12.getBags({ bagType: window.forge.pki.oids.certBag });
|
||
var issuerAttributes = certBags[window.forge.pki.oids.certBag][0].cert.subject.attributes;
|
||
|
||
var bags1 = p12.getBags({ bagType: forge.pki.oids.certBag });
|
||
var cert = bags1[forge.pki.oids.certBag][0].cert;
|
||
|
||
func(privateKey, issuerAttributes, cert);
|
||
return true;
|
||
} catch (ex) { }
|
||
return false;
|
||
}
|
||
|
||
function amtcert_signWithCaKey(DERKey, caPrivateKey, certAttributes, issuerAttributes, extKeyUsage) {
|
||
if (!caPrivateKey || caPrivateKey == null) {
|
||
var certAndKey = amtcert_createCertificate(issuerAttributes);
|
||
caPrivateKey = certAndKey.key;
|
||
}
|
||
return amtcert_createCertificate(certAttributes, caPrivateKey, DERKey, issuerAttributes, extKeyUsage);
|
||
}
|
||
|
||
// --- Extended Key Usage OID's ---
|
||
// 1.3.6.1.5.5.7.3.1 = TLS Server certificate
|
||
// 1.3.6.1.5.5.7.3.2 = TLS Client certificate
|
||
// 2.16.840.1.113741.1.2.1 = Intel AMT Remote Console
|
||
// 2.16.840.1.113741.1.2.2 = Intel AMT Local Console
|
||
// 2.16.840.1.113741.1.2.3 = Intel AMT Client Setup Certificate (Zero-Touch)
|
||
|
||
// Generate a certificate with a set of attributes signed by a rootCert. If the rootCert is obmitted, the generated certificate is self-signed.
|
||
function amtcert_createCertificate(certAttributes, caPrivateKey, DERKey, issuerAttributes, extKeyUsage) {
|
||
// Generate a keypair and create an X.509v3 certificate
|
||
var keys, cert = forge.pki.createCertificate();
|
||
if (!DERKey) {
|
||
keys = forge.pki.rsa.generateKeyPair(2048);
|
||
cert.publicKey = keys.publicKey;
|
||
} else {
|
||
cert.publicKey = forge.pki.publicKeyFromPem('-----BEGIN PUBLIC KEY-----' + DERKey + '-----END PUBLIC KEY-----');
|
||
}
|
||
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1);
|
||
cert.validity.notBefore = new Date();
|
||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1); // Create a certificate that is valid one year before, to make sure out-of-sync clocks don't reject this cert.
|
||
cert.validity.notAfter = new Date();
|
||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 30);
|
||
var attrs = [];
|
||
if (certAttributes['CN']) attrs.push({ name: 'commonName', value: certAttributes['CN'] });
|
||
if (certAttributes['C']) attrs.push({ name: 'countryName', value: certAttributes['C'] });
|
||
if (certAttributes['ST']) attrs.push({ shortName: 'ST', value: certAttributes['ST'] });
|
||
if (certAttributes['O']) attrs.push({ name: 'organizationName', value: certAttributes['O'] });
|
||
cert.setSubject(attrs);
|
||
|
||
if (caPrivateKey) {
|
||
// Use root attributes
|
||
var rootattrs = [];
|
||
if (issuerAttributes['CN']) rootattrs.push({ name: 'commonName', value: issuerAttributes['CN'] });
|
||
if (issuerAttributes['C']) rootattrs.push({ name: 'countryName', value: issuerAttributes['C'] });
|
||
if (issuerAttributes['ST']) rootattrs.push({ shortName: 'ST', value: issuerAttributes['ST'] });
|
||
if (issuerAttributes['O']) rootattrs.push({ name: 'organizationName', value: issuerAttributes['O'] });
|
||
cert.setIssuer(rootattrs);
|
||
} else {
|
||
// Use our own attributes
|
||
cert.setIssuer(attrs);
|
||
}
|
||
|
||
if (caPrivateKey == undefined) {
|
||
// Create a root certificate
|
||
cert.setExtensions([{
|
||
name: 'basicConstraints',
|
||
cA: true
|
||
}, {
|
||
name: 'nsCertType',
|
||
sslCA: true,
|
||
emailCA: true,
|
||
objCA: true
|
||
}, {
|
||
name: 'subjectKeyIdentifier'
|
||
}]);
|
||
} else {
|
||
if (extKeyUsage == null) { extKeyUsage = { name: 'extKeyUsage', serverAuth: true, } } else { extKeyUsage.name = 'extKeyUsage'; }
|
||
|
||
/*
|
||
{
|
||
name: 'extKeyUsage',
|
||
serverAuth: true,
|
||
clientAuth: true,
|
||
codeSigning: true,
|
||
emailProtection: true,
|
||
timeStamping: true,
|
||
'2.16.840.1.113741.1.2.1': true
|
||
}
|
||
*/
|
||
|
||
// Create a leaf certificate
|
||
cert.setExtensions([{
|
||
name: 'basicConstraints'
|
||
}, {
|
||
name: 'keyUsage',
|
||
keyCertSign: true,
|
||
digitalSignature: true,
|
||
nonRepudiation: true,
|
||
keyEncipherment: true,
|
||
dataEncipherment: true
|
||
}, extKeyUsage, {
|
||
name: 'nsCertType',
|
||
client: true,
|
||
server: true,
|
||
email: true,
|
||
objsign: true,
|
||
}, {
|
||
name: 'subjectKeyIdentifier'
|
||
}]);
|
||
}
|
||
|
||
// Self-sign certificate
|
||
if (caPrivateKey) {
|
||
cert.sign(caPrivateKey, forge.md.sha256.create());
|
||
} else {
|
||
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||
}
|
||
|
||
if (DERKey) {
|
||
return cert;
|
||
} else {
|
||
return { 'cert': cert, 'key': keys.privateKey };
|
||
}
|
||
}
|
||
|
||
function _stringToArrayBuffer(str) {
|
||
var buf = new ArrayBuffer(str.length);
|
||
var bufView = new Uint8Array(buf);
|
||
for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); }
|
||
return buf;
|
||
}
|
||
|
||
function _arrayBufferToString(buffer) {
|
||
var binary = '';
|
||
var bytes = new Uint8Array(buffer);
|
||
var len = bytes.byteLength;
|
||
for (var i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); }
|
||
return binary;
|
||
}
|
||
/**
|
||
* @fileoverview Script Compiler / Decompiler / Runner
|
||
* @author Ylian Saint-Hilaire
|
||
* @version v0.1.0e
|
||
*/
|
||
|
||
// Core functions
|
||
script_functionTable1 = ['nop', 'jump', 'set', 'print', 'dialog', 'getitem', 'substr', 'indexof', 'split', 'join', 'length', 'jsonparse', 'jsonstr', 'add', 'substract', 'parseint', 'wsbatchenum', 'wsput', 'wscreate', 'wsdelete', 'wsexec', 'scriptspeed', 'wssubscribe', 'wsunsubscribe', 'readchar', 'signwithdummyca'];
|
||
|
||
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
script_functionTable2 = ['encodeuri', 'decodeuri', 'passwordcheck', 'atob', 'btoa', 'hex2str', 'str2hex', 'random', 'md5', 'maketoarray', 'readshort', 'readshortx', 'readint', 'readsint', 'readintx', 'shorttostr', 'shorttostrx', 'inttostr', 'inttostrx'];
|
||
|
||
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
script_functionTableX2 = [encodeURI, decodeURI, passwordcheck, window.atob.bind(window), window.btoa.bind(window), hex2rstr, rstr2hex, random, rstr_md5, MakeToArray, ReadShort, ReadShortX, ReadInt, ReadSInt, ReadIntX, ShortToStr, ShortToStrX, IntToStr, IntToStrX];
|
||
|
||
// Optional functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
script_functionTable3 = ['pullsystemstatus', 'pulleventlog', 'pullauditlog', 'pullcertificates', 'pullwatchdog', 'pullsystemdefense', 'pullhardware', 'pulluserinfo', 'pullremoteaccess', 'highlightblock', 'disconnect', 'getsidstring', 'getsidbytearray', 'pulleventsubscriptions'];
|
||
|
||
// Optional functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
script_functionTableX3 = [
|
||
PullSystemStatus
|
||
,
|
||
|
||
PullEventLog
|
||
,
|
||
|
||
PullAuditLog
|
||
,
|
||
|
||
PullCertificates
|
||
,
|
||
|
||
PullWatchdog
|
||
,
|
||
|
||
PullSystemDefense
|
||
,
|
||
|
||
PullHardware
|
||
,
|
||
PullUserInfo
|
||
,
|
||
|
||
PullRemoteAccess
|
||
,
|
||
|
||
script_HighlightBlock
|
||
,
|
||
,
|
||
function (runner, x) { return GetSidString(x); }
|
||
,
|
||
function (runner, x) { return GetSidByteArray(x); }
|
||
,
|
||
|
||
PullEventSubscriptions
|
||
];
|
||
|
||
// Setup the script state
|
||
function script_setup(binary, startvars) {
|
||
var obj = { startvars:startvars };
|
||
if (binary.length < 6) { console.error('Invalid script length'); return null; } // Script must have at least 6 byte header
|
||
if (ReadInt(binary, 0) != 0x247D2945) { console.error('Invalid binary script'); return null; } // Check the script magic header
|
||
if (ReadShort(binary, 4) > 1) { console.error('Unsupported script version'); return null; } // Check the script version
|
||
obj.script = binary.substring(6);
|
||
// obj.onStep;
|
||
// obj.onConsole;
|
||
|
||
// Reset the script to the start
|
||
obj.reset = function (stepspeed) {
|
||
obj.stop();
|
||
obj.ip = 0;
|
||
obj.variables = startvars;
|
||
obj.state = 1;
|
||
}
|
||
|
||
// Start the script
|
||
obj.start = function (stepspeed) {
|
||
obj.stop();
|
||
obj.stepspeed = stepspeed;
|
||
if (stepspeed > 0) { obj.timer = setInterval(function () { obj.step() }, stepspeed); }
|
||
}
|
||
|
||
// Stop the script
|
||
obj.stop = function () {
|
||
if (obj.timer != null) { clearInterval(obj.timer); }
|
||
obj.timer = null;
|
||
obj.stepspeed = 0;
|
||
}
|
||
|
||
// function used to load and store variable values
|
||
obj.getVar = function (name) { if (name == undefined) return undefined; return obj.getVarEx(name.split('.'), obj.variables); }
|
||
obj.getVarEx = function (name, val) { try { if (name == undefined) return undefined; if (name.length == 0) return val; return obj.getVarEx(name.slice(1), val[name[0]]); } catch (e) { return null; } }
|
||
obj.setVar = function (name, val) { obj.setVarEx(name.split('.'), obj.variables, val); }
|
||
obj.setVarEx = function (name, vars, val) { if (name.length == 1) { vars[name[0]] = val; } else { obj.setVarEx(name.slice(1), vars[name[0]], val); } }
|
||
|
||
// Run the script one step forward
|
||
obj.step = function () {
|
||
if (obj.state != 1) return;
|
||
if (obj.ip < obj.script.length) {
|
||
var cmdid = ReadShort(obj.script, obj.ip);
|
||
var cmdlen = ReadShort(obj.script, obj.ip + 2);
|
||
var argcount = ReadShort(obj.script, obj.ip + 4);
|
||
var argptr = obj.ip + 6;
|
||
var args = [];
|
||
|
||
// Clear all temp variables (This is optional)
|
||
for (var i in obj.variables) { if (i.startsWith('__')) { delete obj.variables[i]; } }
|
||
|
||
// Loop on each argument, moving forward by the argument length each time
|
||
for (var i = 0; i < argcount; i++) {
|
||
var arglen = ReadShort(obj.script, argptr);
|
||
var argval = obj.script.substring(argptr + 2, argptr + 2 + arglen);
|
||
var argtyp = argval.charCodeAt(0);
|
||
argval = argval.substring(1);
|
||
if (argtyp < 2) {
|
||
// Get the value and replace all {var} with variable values
|
||
while (argval.split("{").length > 1) { var t = argval.split("{").pop().split("}").shift(); argval = argval.replace('{' + t + '}', obj.getVar(t)); }
|
||
if (argtyp == 1) { obj.variables['__' + i] = decodeURI(argval); argval = '__' + i; } // If argtyp is 1, this is a literal. Store in temp variable.
|
||
args.push(argval);
|
||
}
|
||
if (argtyp == 2 || argtyp == 3) {
|
||
obj.variables['__' + i] = ReadSInt(argval, 0);
|
||
args.push('__' + i);
|
||
}
|
||
argptr += (2 + arglen);
|
||
}
|
||
|
||
// Move instruction pointer forward by command size
|
||
obj.ip += cmdlen;
|
||
|
||
// Get all variable values
|
||
var argsval = [];
|
||
for (var i = 0; i < 10; i++) { argsval.push(obj.getVar(args[i])); }
|
||
var storeInArg0;
|
||
|
||
try {
|
||
if (cmdid < 10000) {
|
||
// Lets run the actual command
|
||
switch (cmdid) {
|
||
case 0: // nop
|
||
break;
|
||
case 1: // jump(label) or jump(label, a, compare, b)
|
||
if (argsval[2]) {
|
||
if (
|
||
(argsval[2] == '<' && argsval[1] < argsval[3]) ||
|
||
(argsval[2] == '<=' && argsval[1] <= argsval[3]) ||
|
||
(argsval[2] == '!=' && argsval[1] != argsval[3]) ||
|
||
(argsval[2] == '=' && argsval[1] == argsval[3]) ||
|
||
(argsval[2] == '>=' && argsval[1] >= argsval[3]) ||
|
||
(argsval[2] == '>' && argsval[1] > argsval[3])
|
||
) { obj.ip = argsval[0]; }
|
||
} else {
|
||
obj.ip = argsval[0]; // Set the instruction pointer to the new location in the script
|
||
}
|
||
break;
|
||
case 2: // set(variable, value)
|
||
if (args[1] == undefined) delete obj.variables[args[0]]; else obj.setVar(args[0], argsval[1]);
|
||
break;
|
||
case 3: // print(message)
|
||
if (obj.onConsole) { obj.onConsole(obj.toString(argsval[0]), obj); } else { console.log(obj.toString(argsval[0])); }
|
||
// Q(obj.consoleid).value += () + '\n'); Q(obj.console).scrollTop = Q(obj.console).scrollHeight;
|
||
break;
|
||
case 4: // dialog(title, content, buttons)
|
||
obj.state = 2;
|
||
obj.dialog = true;
|
||
setDialogMode(11, argsval[0], argsval[2], obj.xxStepDialogOk, argsval[1], obj);
|
||
break;
|
||
case 5: // getitem(a, b, c)
|
||
for (var i in argsval[1]) { if (argsval[1][i][argsval[2]] == argsval[3]) { storeInArg0 = i; } };
|
||
break;
|
||
case 6: // substr(variable_dest, variable_src, index, len)
|
||
storeInArg0 = argsval[1].substr(argsval[2], argsval[3]);
|
||
break;
|
||
case 7: // indexOf(variable_dest, variable_src, index, len)
|
||
storeInArg0 = argsval[1].indexOf(argsval[2]);
|
||
break;
|
||
case 8: // split(variable_dest, variable_src, separator)
|
||
storeInArg0 = argsval[1].split(argsval[2]);
|
||
break;
|
||
case 9: // join(variable_dest, variable_src, separator)
|
||
storeInArg0 = argsval[1].join(argsval[2]);
|
||
break;
|
||
case 10: // length(variable_dest, variable_src)
|
||
storeInArg0 = argsval[1].length;
|
||
break;
|
||
case 11: // jsonparse(variable_dest, json)
|
||
storeInArg0 = JSON.parse(argsval[1]);
|
||
break;
|
||
case 12: // jsonstr(variable_dest, variable_src)
|
||
storeInArg0 = JSON.stringify(argsval[1]);
|
||
break;
|
||
case 13: // add(variable_dest, variable_src, value)
|
||
storeInArg0 = (argsval[1] + argsval[2]);
|
||
break;
|
||
case 14: // substract(variable_dest, variable_src, value)
|
||
storeInArg0 = (argsval[1] - argsval[2]);
|
||
break;
|
||
case 15: // parseInt(variable_dest, variable_src)
|
||
storeInArg0 = parseInt(argsval[1]);
|
||
break;
|
||
case 16: // wsbatchenum(name, objectList)
|
||
obj.state = 2;
|
||
obj.amtstack.BatchEnum(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
|
||
break;
|
||
case 17: // wsput(name, args)
|
||
obj.state = 2;
|
||
obj.amtstack.Put(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
|
||
break;
|
||
case 18: // wscreate(name, args)
|
||
obj.state = 2;
|
||
obj.amtstack.Create(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
|
||
break;
|
||
case 19: // wsdelete(name, args)
|
||
obj.state = 2;
|
||
obj.amtstack.Delete(argsval[0], argsval[1], obj.xxWsmanReturn, obj);
|
||
break;
|
||
case 20: // wsexec(name, method, args, selectors)
|
||
obj.state = 2;
|
||
obj.amtstack.Exec(argsval[0], argsval[1], argsval[2], obj.xxWsmanReturn, obj, 0, argsval[3]);
|
||
break;
|
||
case 21: // Script Speed
|
||
obj.stepspeed = argsval[0];
|
||
if (obj.timer != null) { clearInterval(obj.timer); obj.timer = setInterval(function () { obj.step() }, obj.stepspeed); }
|
||
break;
|
||
case 22: // wssubscribe(name, delivery, url, selectors, opaque, user, pass)
|
||
obj.state = 2;
|
||
obj.amtstack.Subscribe(argsval[0], argsval[1], argsval[2], obj.xxWsmanReturn, obj, 0, argsval[3], argsval[4], argsval[5], argsval[6]);
|
||
break;
|
||
case 23: // wsunsubscribe(name, selectors)
|
||
obj.state = 2;
|
||
obj.amtstack.UnSubscribe(argsval[0], obj.xxWsmanReturn, obj, 0, argsval[1]);
|
||
break;
|
||
case 24: // readchar(str, pos)
|
||
console.log(argsval[1], argsval[2], argsval[1].charCodeAt(argsval[2]));
|
||
storeInArg0 = argsval[1].charCodeAt(argsval[2]);
|
||
break;
|
||
case 25: // signWithDummyCa
|
||
|
||
obj.state = 2;
|
||
// DERKey, xxCaPrivateKey, certattributes, issuerattributes
|
||
amtcert_signWithCaKey(argsval[0], null, argsval[1], { 'CN': 'Untrusted Root Certificate' }, obj.xxSignWithDummyCaReturn);
|
||
break;
|
||
default: {
|
||
obj.state = 9;
|
||
console.error("Script Error, unknown command: " + cmdid);
|
||
}
|
||
}
|
||
} else {
|
||
if (cmdid < 20000) {
|
||
// functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
storeInArg0 = script_functionTableX2[cmdid - 10000](argsval[1], argsval[2], argsval[3], argsval[4], argsval[5], argsval[6]);
|
||
} else {
|
||
// Optional functions of type ARG1 = func(ARG2, ARG3, ARG4, ARG5, ARG6)
|
||
if (script_functionTableX3 && script_functionTableX3[cmdid - 20000]) {
|
||
storeInArg0 = script_functionTableX3[cmdid - 20000](obj, argsval[1], argsval[2], argsval[3], argsval[4], argsval[5], argsval[6]); // Note that optional calls start with "obj" as first argument.
|
||
}
|
||
}
|
||
}
|
||
|
||
if (storeInArg0 != undefined) obj.setVar(args[0], storeInArg0);
|
||
} catch (e) {
|
||
if (typeof e == 'object') { e = e.message; }
|
||
obj.setVar('_exception', e);
|
||
}
|
||
}
|
||
|
||
if (obj.state == 1 && obj.ip >= obj.script.length) { obj.state = 0; obj.stop(); }
|
||
if (obj.onStep) obj.onStep(obj);
|
||
return obj;
|
||
}
|
||
|
||
obj.xxStepDialogOk = function (button) {
|
||
obj.variables['DialogSelect'] = button;
|
||
obj.state = 1;
|
||
obj.dialog = false;
|
||
if (obj.onStep) obj.onStep(obj);
|
||
}
|
||
|
||
|
||
obj.xxWsmanReturn = function (stack, name, responses, status) {
|
||
obj.setVar(name, responses);
|
||
obj.setVar('wsman_result', status);
|
||
obj.setVar('wsman_result_str', ((httpErrorTable[status]) ? (httpErrorTable[status]) : ('Error #' + status)));
|
||
obj.state = 1;
|
||
if (obj.onStep) obj.onStep(obj);
|
||
}
|
||
|
||
|
||
obj.xxSignWithDummyCaReturn = function (cert) {
|
||
obj.setVar('signed_cert', btoa(_arrayBufferToString(cert)));
|
||
obj.state = 1;
|
||
if (obj.onStep) obj.onStep(obj);
|
||
}
|
||
|
||
obj.toString = function (x) { if (typeof x == 'object') return JSON.stringify(x); return x; }
|
||
|
||
obj.reset();
|
||
return obj;
|
||
}
|
||
|
||
// Argument types: 0 = Variable, 1 = String, 2 = Integer, 3 = Label
|
||
function script_compile(script, onmsg) {
|
||
var r = '', scriptlines = script.split('\n'), labels = {}, labelswap = [], swaps = [];
|
||
// Go thru each script line and encode it
|
||
for (var i in scriptlines) {
|
||
var scriptline = scriptlines[i];
|
||
if (scriptline.startsWith('##SWAP ')) { var x = scriptline.split(' '); if (x.length == 3) { swaps[x[1]] = x[2]; } } // Add a swap instance
|
||
if (scriptline[0] == '#' || scriptline.length == 0) continue; // Skip comments & blank lines
|
||
for (var x in swaps) { scriptline = scriptline.split(x).join(swaps[x]); } // Apply all swaps
|
||
var keywords = scriptline.match(/"[^"]*"|[^\s"]+/g);
|
||
if (keywords.length == 0) continue; // Skip blank lines
|
||
if (scriptline[0] == ':') { labels[keywords[0].toUpperCase()] = r.length; continue; } // Mark a label position
|
||
var funcIndex = script_functionTable1.indexOf(keywords[0].toLowerCase());
|
||
if (funcIndex == -1) { funcIndex = script_functionTable2.indexOf(keywords[0].toLowerCase()); if (funcIndex >= 0) funcIndex += 10000; }
|
||
if (funcIndex == -1) { funcIndex = script_functionTable3.indexOf(keywords[0].toLowerCase()); if (funcIndex >= 0) funcIndex += 20000; } // Optional methods
|
||
if (funcIndex == -1) { if (onmsg) { onmsg("Unabled to compile, unknown command: " + keywords[0]); } return ''; }
|
||
// Encode CommandId, CmdSize, ArgCount, Arg1Len, Arg1, Arg2Len, Arg2...
|
||
var cmd = ShortToStr(keywords.length - 1);
|
||
for (var j in keywords) {
|
||
if (j == 0) continue;
|
||
if (keywords[j][0] == ':') {
|
||
labelswap.push([keywords[j], r.length + cmd.length + 7]); // Add a label swap
|
||
cmd += ShortToStr(5) + String.fromCharCode(3) + IntToStr(0xFFFFFFFF); // Put an empty label
|
||
} else {
|
||
var argint = parseInt(keywords[j]);
|
||
if (argint == keywords[j]) {
|
||
cmd += ShortToStr(5) + String.fromCharCode(2) + IntToStr(argint);
|
||
} else {
|
||
if (keywords[j][0] == '"' && keywords[j][keywords[j].length - 1] == '"') {
|
||
cmd += ShortToStr(keywords[j].length - 1) + String.fromCharCode(1) + keywords[j].substring(1, keywords[j].length - 1);
|
||
} else {
|
||
cmd += ShortToStr(keywords[j].length + 1) + String.fromCharCode(0) + keywords[j];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
cmd = ShortToStr(funcIndex) + ShortToStr(cmd.length + 4) + cmd;
|
||
r += cmd;
|
||
}
|
||
// Perform all the needed label swaps
|
||
for (i in labelswap) {
|
||
var label = labelswap[i][0].toUpperCase(), position = labelswap[i][1], target = labels[label];
|
||
if (target == undefined) { if (onmsg) { onmsg("Unabled to compile, unknown label: " + label); } return ''; }
|
||
r = r.substr(0, position) + IntToStr(target) + r.substr(position + 4);
|
||
}
|
||
return IntToStr(0x247D2945) + ShortToStr(1) + r;
|
||
}
|
||
|
||
// Decompile the script, intended for debugging only
|
||
function script_decompile(binary, onecmd) {
|
||
var r = '', ptr = 6, labelcount = 0, labels = {};
|
||
if (onecmd >= 0) {
|
||
ptr = onecmd; // If we are decompiling just one command, set the ptr to that command.
|
||
} else {
|
||
if (binary.length < 6) { return '# Invalid script length'; }
|
||
var magic = ReadInt(binary, 0);
|
||
var version = ReadShort(binary, 4);
|
||
if (magic != 0x247D2945) { return '# Invalid binary script: ' + magic; }
|
||
if (version != 1) { return '# Invalid script version'; }
|
||
}
|
||
// Loop on each command, moving forward by the command length each time.
|
||
while (ptr < binary.length) {
|
||
var cmdid = ReadShort(binary, ptr);
|
||
var cmdlen = ReadShort(binary, ptr + 2);
|
||
var argcount = ReadShort(binary, ptr + 4);
|
||
var argptr = ptr + 6;
|
||
var argstr = '';
|
||
if (!(onecmd >= 0)) r += ":label" + (ptr - 6) + "\n";
|
||
// Loop on each argument, moving forward by the argument length each time
|
||
for (var i = 0; i < argcount; i++) {
|
||
var arglen = ReadShort(binary, argptr);
|
||
var argval = binary.substring(argptr + 2, argptr + 2 + arglen);
|
||
var argtyp = argval.charCodeAt(0);
|
||
if (argtyp == 0) { argstr += ' ' + argval.substring(1); } // Variable
|
||
else if (argtyp == 1) { argstr += ' \"' + argval.substring(1) + '\"'; } // String
|
||
else if (argtyp == 2) { argstr += ' ' + ReadInt(argval, 1); } // Integer
|
||
else if (argtyp == 3) { // Label
|
||
var target = ReadInt(argval, 1);
|
||
var label = labels[target];
|
||
if (!label) { label = ":label" + target; labels[label] = target; }
|
||
argstr += ' ' + label;
|
||
}
|
||
argptr += (2 + arglen);
|
||
}
|
||
// Go in the script function table to decode the function
|
||
if (cmdid < 10000) {
|
||
r += script_functionTable1[cmdid] + argstr + "\n";
|
||
} else {
|
||
if (cmdid >= 20000) {
|
||
r += script_functionTable3[cmdid - 20000] + argstr + "\n"; // Optional methods
|
||
} else {
|
||
r += script_functionTable2[cmdid - 10000] + argstr + "\n";
|
||
}
|
||
}
|
||
ptr += cmdlen;
|
||
if (onecmd >= 0) return r; // If we are decompiling just one command, exit now
|
||
}
|
||
// Remove all unused labels
|
||
var scriptlines = r.split('\n');
|
||
r = '';
|
||
for (var i in scriptlines) {
|
||
var line = scriptlines[i];
|
||
if (line[0] != ':') { r += line + '\n'; } else { if (labels[line]) { r += line + '\n'; } }
|
||
}
|
||
return r;
|
||
}
|
||
/* FileSaver.js
|
||
* A saveAs() FileSaver implementation.
|
||
* 1.1.20151003
|
||
*
|
||
* By Eli Grey, http://eligrey.com
|
||
* License: MIT
|
||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||
*/
|
||
|
||
/*global self */
|
||
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
|
||
|
||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
||
|
||
var saveAs = saveAs || (function (view) {
|
||
"use strict";
|
||
// IE <10 is explicitly unsupported
|
||
if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
||
return;
|
||
}
|
||
var
|
||
doc = view.document
|
||
// only get URL when necessary in case Blob.js hasn't overridden it yet
|
||
, get_URL = function () {
|
||
return view.URL || view.webkitURL || view;
|
||
}
|
||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||
, can_use_save_link = "download" in save_link
|
||
, click = function (node) {
|
||
var event = new MouseEvent("click");
|
||
node.dispatchEvent(event);
|
||
}
|
||
, is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
|
||
, webkit_req_fs = view.webkitRequestFileSystem
|
||
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
|
||
, throw_outside = function (ex) {
|
||
(view.setImmediate || view.setTimeout)(function () {
|
||
throw ex;
|
||
}, 0);
|
||
}
|
||
, force_saveable_type = "application/octet-stream"
|
||
, fs_min_size = 0
|
||
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
|
||
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
|
||
// for the reasoning behind the timeout and revocation flow
|
||
, arbitrary_revoke_timeout = 500 // in ms
|
||
, revoke = function (file) {
|
||
var revoker = function () {
|
||
if (typeof file === "string") { // file is an object URL
|
||
get_URL().revokeObjectURL(file);
|
||
} else { // file is a File
|
||
file.remove();
|
||
}
|
||
};
|
||
if (view.chrome) {
|
||
revoker();
|
||
} else {
|
||
setTimeout(revoker, arbitrary_revoke_timeout);
|
||
}
|
||
}
|
||
, dispatch = function (filesaver, event_types, event) {
|
||
event_types = [].concat(event_types);
|
||
var i = event_types.length;
|
||
while (i--) {
|
||
var listener = filesaver["on" + event_types[i]];
|
||
if (typeof listener === "function") {
|
||
try {
|
||
listener.call(filesaver, event || filesaver);
|
||
} catch (ex) {
|
||
throw_outside(ex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
, auto_bom = function (blob) {
|
||
// prepend BOM for UTF-8 XML and text types (including HTML)
|
||
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||
return new Blob(["\ufeff", blob], { type: blob.type });
|
||
}
|
||
return blob;
|
||
}
|
||
, FileSaver = function (blob, name, no_auto_bom) {
|
||
if (!no_auto_bom) {
|
||
blob = auto_bom(blob);
|
||
}
|
||
// First try a.download, then web filesystem, then object URLs
|
||
var
|
||
filesaver = this
|
||
, type = blob.type
|
||
, blob_changed = false
|
||
, object_url
|
||
, target_view
|
||
, dispatch_all = function () {
|
||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
||
}
|
||
// on any filesys errors revert to saving with object URLs
|
||
, fs_error = function () {
|
||
if (target_view && is_safari && typeof FileReader !== "undefined") {
|
||
// Safari doesn't allow downloading of blob urls
|
||
var reader = new FileReader();
|
||
reader.onloadend = function () {
|
||
var base64Data = reader.result;
|
||
target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
|
||
filesaver.readyState = filesaver.DONE;
|
||
dispatch_all();
|
||
};
|
||
reader.readAsDataURL(blob);
|
||
filesaver.readyState = filesaver.INIT;
|
||
return;
|
||
}
|
||
// don't create more object URLs than needed
|
||
if (blob_changed || !object_url) {
|
||
object_url = get_URL().createObjectURL(blob);
|
||
}
|
||
if (target_view) {
|
||
target_view.location.href = object_url;
|
||
} else {
|
||
var new_tab = view.open(object_url, "_blank");
|
||
if (new_tab == undefined && is_safari) {
|
||
//Apple do not allow window.open, see http://bit.ly/1kZffRI
|
||
view.location.href = object_url
|
||
}
|
||
}
|
||
filesaver.readyState = filesaver.DONE;
|
||
dispatch_all();
|
||
revoke(object_url);
|
||
}
|
||
, abortable = function (func) {
|
||
return function () {
|
||
if (filesaver.readyState !== filesaver.DONE) {
|
||
return func.apply(this, arguments);
|
||
}
|
||
};
|
||
}
|
||
, create_if_not_found = { create: true, exclusive: false }
|
||
, slice
|
||
;
|
||
filesaver.readyState = filesaver.INIT;
|
||
if (!name) {
|
||
name = "download";
|
||
}
|
||
if (can_use_save_link) {
|
||
object_url = get_URL().createObjectURL(blob);
|
||
save_link.href = object_url;
|
||
save_link.download = name;
|
||
setTimeout(function () {
|
||
click(save_link);
|
||
dispatch_all();
|
||
revoke(object_url);
|
||
filesaver.readyState = filesaver.DONE;
|
||
});
|
||
return;
|
||
}
|
||
// Object and web filesystem URLs have a problem saving in Google Chrome when
|
||
// viewed in a tab, so I force save with application/octet-stream
|
||
// http://code.google.com/p/chromium/issues/detail?id=91158
|
||
// Update: Google errantly closed 91158, I submitted it again:
|
||
// https://code.google.com/p/chromium/issues/detail?id=389642
|
||
if (view.chrome && type && type !== force_saveable_type) {
|
||
slice = blob.slice || blob.webkitSlice;
|
||
blob = slice.call(blob, 0, blob.size, force_saveable_type);
|
||
blob_changed = true;
|
||
}
|
||
// Since I can't be sure that the guessed media type will trigger a download
|
||
// in WebKit, I append .download to the filename.
|
||
// https://bugs.webkit.org/show_bug.cgi?id=65440
|
||
if (webkit_req_fs && name !== "download") {
|
||
name += ".download";
|
||
}
|
||
if (type === force_saveable_type || webkit_req_fs) {
|
||
target_view = view;
|
||
}
|
||
if (!req_fs) {
|
||
fs_error();
|
||
return;
|
||
}
|
||
fs_min_size += blob.size;
|
||
req_fs(view.TEMPORARY, fs_min_size, abortable(function (fs) {
|
||
fs.root.getDirectory("saved", create_if_not_found, abortable(function (dir) {
|
||
var save = function () {
|
||
dir.getFile(name, create_if_not_found, abortable(function (file) {
|
||
file.createWriter(abortable(function (writer) {
|
||
writer.onwriteend = function (event) {
|
||
target_view.location.href = file.toURL();
|
||
filesaver.readyState = filesaver.DONE;
|
||
dispatch(filesaver, "writeend", event);
|
||
revoke(file);
|
||
};
|
||
writer.onerror = function () {
|
||
var error = writer.error;
|
||
if (error.code !== error.ABORT_ERR) {
|
||
fs_error();
|
||
}
|
||
};
|
||
"writestart progress write abort".split(" ").forEach(function (event) {
|
||
writer["on" + event] = filesaver["on" + event];
|
||
});
|
||
writer.write(blob);
|
||
filesaver.abort = function () {
|
||
writer.abort();
|
||
filesaver.readyState = filesaver.DONE;
|
||
};
|
||
filesaver.readyState = filesaver.WRITING;
|
||
}), fs_error);
|
||
}), fs_error);
|
||
};
|
||
dir.getFile(name, { create: false }, abortable(function (file) {
|
||
// delete file if it already exists
|
||
file.remove();
|
||
save();
|
||
}), abortable(function (ex) {
|
||
if (ex.code === ex.NOT_FOUND_ERR) {
|
||
save();
|
||
} else {
|
||
fs_error();
|
||
}
|
||
}));
|
||
}), fs_error);
|
||
}), fs_error);
|
||
}
|
||
, FS_proto = FileSaver.prototype
|
||
, saveAs = function (blob, name, no_auto_bom) {
|
||
return new FileSaver(blob, name, no_auto_bom);
|
||
}
|
||
;
|
||
// IE 10+ (native saveAs)
|
||
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
||
return function (blob, name, no_auto_bom) {
|
||
if (!no_auto_bom) {
|
||
blob = auto_bom(blob);
|
||
}
|
||
return navigator.msSaveOrOpenBlob(blob, name || "download");
|
||
};
|
||
}
|
||
|
||
FS_proto.abort = function () {
|
||
var filesaver = this;
|
||
filesaver.readyState = filesaver.DONE;
|
||
dispatch(filesaver, "abort");
|
||
};
|
||
FS_proto.readyState = FS_proto.INIT = 0;
|
||
FS_proto.WRITING = 1;
|
||
FS_proto.DONE = 2;
|
||
|
||
FS_proto.error =
|
||
FS_proto.onwritestart =
|
||
FS_proto.onprogress =
|
||
FS_proto.onwrite =
|
||
FS_proto.onabort =
|
||
FS_proto.onerror =
|
||
FS_proto.onwriteend =
|
||
null;
|
||
|
||
return saveAs;
|
||
}(
|
||
typeof self !== "undefined" && self
|
||
|| typeof window !== "undefined" && window
|
||
|| this.content
|
||
));
|
||
// `self` is undefined in Firefox for Android content script context
|
||
// while `this` is nsIContentFrameMessageManager
|
||
// with an attribute `content` that corresponds to the window
|
||
|
||
if (typeof module !== "undefined" && module.exports) {
|
||
module.exports.saveAs = saveAs;
|
||
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
|
||
define([], function () {
|
||
return saveAs;
|
||
});
|
||
}
|
||
|
||
var version = '0.6.0';
|
||
var urlvars = null;
|
||
var amtstack;
|
||
var wsstack = null;
|
||
var AllWsman = "AMT_8021xCredentialContext,AMT_8021XProfile,AMT_ActiveFilterStatistics,AMT_AgentPresenceCapabilities,AMT_AgentPresenceInterfacePolicy,AMT_AgentPresenceService,AMT_AgentPresenceWatchdog,AMT_AgentPresenceWatchdogAction,AMT_AlarmClockService,IPS_AlarmClockOccurrence,AMT_AssetTable,AMT_AssetTableService,AMT_AuditLog,AMT_AuditPolicyRule,AMT_AuthorizationService,AMT_BootCapabilities,AMT_BootSettingData,AMT_ComplexFilterEntryBase,AMT_CRL,AMT_CryptographicCapabilities,AMT_EACCredentialContext,AMT_EndpointAccessControlService,AMT_EnvironmentDetectionInterfacePolicy,AMT_EnvironmentDetectionSettingData,AMT_EthernetPortSettings,AMT_EventLogEntry,AMT_EventManagerService,AMT_EventSubscriber,AMT_FilterEntryBase,AMT_FilterInSystemDefensePolicy,AMT_GeneralSettings,AMT_GeneralSystemDefenseCapabilities,AMT_Hdr8021Filter,AMT_HeuristicPacketFilterInterfacePolicy,AMT_HeuristicPacketFilterSettings,AMT_HeuristicPacketFilterStatistics,AMT_InterfacePolicy,AMT_IPHeadersFilter,AMT_KerberosSettingData,AMT_ManagementPresenceRemoteSAP,AMT_MessageLog,AMT_MPSUsernamePassword,AMT_NetworkFilter,AMT_NetworkPortDefaultSystemDefensePolicy,AMT_NetworkPortSystemDefenseCapabilities,AMT_NetworkPortSystemDefensePolicy,AMT_PCIDevice,AMT_PETCapabilities,AMT_PETFilterForTarget,AMT_PETFilterSetting,AMT_ProvisioningCertificateHash,AMT_PublicKeyCertificate,AMT_PublicKeyManagementCapabilities,AMT_PublicKeyManagementService,AMT_PublicPrivateKeyPair,AMT_RedirectionService,AMT_RemoteAccessCapabilities,AMT_RemoteAccessCredentialContext,AMT_RemoteAccessPolicyAppliesToMPS,AMT_RemoteAccessPolicyRule,AMT_RemoteAccessService,AMT_SetupAndConfigurationService,AMT_SNMPEventSubscriber,AMT_StateTransitionCondition,AMT_SystemDefensePolicy,AMT_SystemDefensePolicyInService,AMT_SystemDefenseService,AMT_SystemPowerScheme,AMT_ThirdPartyDataStorageAdministrationService,AMT_ThirdPartyDataStorageService,AMT_TimeSynchronizationService,AMT_TLSCredentialContext,AMT_TLSProtocolEndpoint,AMT_TLSProtocolEndpointCollection,AMT_TLSSettingData,AMT_TrapTargetForService,AMT_UserInitiatedConnectionService,AMT_WebUIService,AMT_WiFiPortConfigurationService,CIM_AbstractIndicationSubscription,CIM_Account,CIM_AccountManagementCapabilities,CIM_AccountManagementService,CIM_AccountOnSystem,CIM_AdminDomain,CIM_AlertIndication,CIM_AssignedIdentity,CIM_AssociatedPowerManagementService,CIM_AuthenticationService,CIM_AuthorizationService,CIM_BIOSElement,CIM_BIOSFeature,CIM_BIOSFeatureBIOSElements,CIM_BootConfigSetting,CIM_BootService,CIM_BootSettingData,CIM_BootSourceSetting,CIM_Capabilities,CIM_Card,CIM_Chassis,CIM_Chip,CIM_Collection,CIM_Component,CIM_ComputerSystem,CIM_ComputerSystemPackage,CIM_ConcreteComponent,CIM_ConcreteDependency,CIM_Controller,CIM_CoolingDevice,CIM_Credential,CIM_CredentialContext,CIM_CredentialManagementService,CIM_Dependency,CIM_DeviceSAPImplementation,CIM_ElementCapabilities,CIM_ElementConformsToProfile,CIM_ElementLocation,CIM_ElementSettingData,CIM_ElementSoftwareIdentity,CIM_ElementStatisticalData,CIM_EnabledLogicalElement,CIM_EnabledLogicalElementCapabilities,CIM_EthernetPort,CIM_Fan,CIM_FilterCollection,CIM_FilterCollectionSubscription,CIM_HostedAccessPoint,CIM_HostedDependency,CIM_HostedService,CIM_Identity,CIM_IEEE8021xCapabilities,CIM_IEEE8021xSettings,CIM_Indication,CIM_IndicationService,CIM_InstalledSoftwareIdentity,CIM_KVMRedirectionSAP,CIM_LANEndpoint,CIM_ListenerDestination,CIM_ListenerDestinationWSManagement,CIM_Location,CIM_Log,CIM_LogEntry,CIM_LogicalDevice,CIM_LogicalElement,CIM_LogicalPort,CIM_LogicalPortCapabilities,CIM_LogManagesRecord,CIM_ManagedCredential,CIM_ManagedElement,CIM_ManagedSystemElement,CIM_MediaAccessDevice,CIM_MemberOfCollection,CIM_Memory,CIM_MessageLog,CIM_NetworkPort,CIM_NetworkPortCapabilities,CIM_NetworkPortConfigurationService,CIM_OrderedComponent,CIM_OwningCollectionElement,CIM_OwningJobElement,CIM_PCIController,CIM_PhysicalComponent,CIM_PhysicalElement,CIM_PhysicalElementLocation,CIM_PhysicalFrame,CIM_PhysicalMemory,CIM_PhysicalPackage,CIM_Policy,CIM_PolicyAction,CIM_PolicyCondition,CIM_PolicyInSystem,CIM_PolicyRule,CIM_PolicyRuleInSystem,CIM_PolicySet,CIM_PolicySetAppliesToElement,CIM_PolicySetInSystem,CIM_PowerManagementCapabilities,CIM_PowerManagementService,CIM_PowerSupply,CIM_Privilege,CIM_PrivilegeManagementCapabilities,CIM_PrivilegeManagementService,CIM_ProcessIndication,CIM_Processor,CIM_ProtocolEndpoint,CIM_ProvidesServiceToElement,CIM_Realizes,CIM_RecordForLog,CIM_RecordLog,CIM_RedirectionService,CIM_ReferencedProfile,CIM_RegisteredProfile,CIM_RemoteAccessAvailableToElement,CIM_RemoteIdentity,CIM_RemotePort,CIM_RemoteServiceAccessPoint,CIM_Role,CIM_RoleBasedAuthorizationService,CIM_RoleBasedManagementCapabilities,CIM_RoleLimitedToTarget,CIM_SAPAvailableForElement,CIM_SecurityService,CIM_Sensor,CIM_Service,CIM_ServiceAccessBySAP,CIM_ServiceAccessPoint,CIM_ServiceAffectsElement,CIM_ServiceAvailableToElement,CIM_ServiceSAPDependency,CIM_ServiceServiceDependency,CIM_SettingData,CIM_SharedCredential,CIM_SoftwareElement,CIM_SoftwareFeature,CIM_SoftwareFeatureSoftwareElements,CIM_SoftwareIdentity,CIM_StatisticalData,CIM_StorageExtent,CIM_System,CIM_SystemBIOS,CIM_SystemComponent,CIM_SystemDevice,CIM_SystemPackaging,CIM_UseOfLog,CIM_Watchdog,CIM_WiFiEndpoint,CIM_WiFiEndpointCapabilities,CIM_WiFiEndpointSettings,CIM_WiFiPort,CIM_WiFiPortCapabilities,IPS_AdminProvisioningRecord,IPS_ClientProvisioningRecord,IPS_HostBasedSetupService,IPS_HostIPSettings,IPS_IderSessionUsingPort,IPS_IPv6PortSettings,IPS_KVMRedirectionSettingData,IPS_KvmSessionUsingPort,IPS_ManualProvisioningRecord,IPS_OptInService,IPS_ProvisioningAuditRecord,IPS_ProvisioningRecordLog,IPS_RasSessionUsingPort,IPS_ScreenSettingData,IPS_SecIOService,IPS_SessionUsingPort,IPS_SolSessionUsingPort,IPS_TLSProvisioningRecord".split(',');
|
||
var disconnecturl = null;
|
||
var currentView = 0;
|
||
var LoadingHtml = "<div style=text-align:center;padding-top:20px>Loading...<div>";
|
||
var amtversion = 0;
|
||
var amtversionmin = 0;
|
||
var amtFirstPull = 0; // Bitmask, Set this when the first pull request is made on a device: 1 = HardwareInfo, 2 = WirelessInfo, 4 = SystemDefense, 8 = Storage, 16 = EventLog, 32 = AuditLog
|
||
|
||
var amtwirelessif = -1; // Set to the interface index for the wireless interface, -1 if no wireless.
|
||
|
||
var currentMeshNode = null;
|
||
var webcompilerfeatures = ['AgentPresence','Alarms','AuditLog','Certificates','ComputerSelectorToolbar','EventLog','EventSubscriptions','FileSaver','HardwareInfo','Look-MeshCentral','Mode-MeshCentral2','NetworkSettings','PowerControl','PowerControl-Advanced','RemoteAccess','Scripting','Scripting-Editor','Storage','SystemDefense','VersionWarning','Wireless','WsmanBrowser'];
|
||
var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected'];
|
||
|
||
var scriptstate;
|
||
var t, t2; // Global temporary variable
|
||
|
||
function startup() {
|
||
// This is a bit freeky, but all HTML input elements are just going to be accessible directly.
|
||
var allelements = document.getElementsByTagName('input');
|
||
for (t = 0; t < allelements.length; t++) { if (allelements[t].id) { window[allelements[t].id] = allelements[t]; } }
|
||
|
||
|
||
urlvars = getUrlVars();
|
||
|
||
|
||
|
||
|
||
|
||
// Add all WSMAN objects to WSMAN browser
|
||
for (var w in AllWsman) { var option = document.createElement("option"); option.text = AllWsman[w]; option.id = 'WSB-' + AllWsman[w]; Q('id_QuerySelect').add(option); }
|
||
|
||
|
||
|
||
// Master drag & drop
|
||
document.addEventListener('dragover', haltEvent, false);
|
||
document.addEventListener('dragleave', haltEvent, false);
|
||
document.addEventListener('drop', documentFileSelectHandler, false);
|
||
|
||
|
||
// Setup Terminal drag & drop
|
||
Q('p16').addEventListener('dragover', haltEvent, false);
|
||
Q('p16').addEventListener('dragleave', haltEvent, false);
|
||
Q('p16').addEventListener('drop', cert_FileSelectHandler, false);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
document.onkeyup = handleKeyUp;
|
||
document.onkeydown = handleKeyDown;
|
||
document.onkeypress = handleKeyPress;
|
||
window.onresize = center;
|
||
center();
|
||
|
||
|
||
|
||
|
||
scriptLoadStartingBlocks();
|
||
|
||
}
|
||
|
||
function documentFileSelectHandler(e) {
|
||
haltEvent(e);
|
||
if (e.dataTransfer == null || e.dataTransfer.files.length != 1) return;
|
||
var f = null, filename = e.dataTransfer.files[0].name.toLowerCase();
|
||
|
||
if (currentView == 21) { UploadToStorage(e.dataTransfer.files[0], filename); return; }
|
||
|
||
if (wsstack != null && (filename.endsWith('.mescript') || filename.endsWith('.meblocks'))) { f = script_onScriptRead; }
|
||
if (f != null) { var reader = new FileReader(); reader.onload = f; reader.readAsBinaryString(e.dataTransfer.files[0]); }
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
function connectButtonfunction() {
|
||
if (!wsstack || wsstack.socketState == 0) {
|
||
meshcentral2credCallback();
|
||
} else {
|
||
disconnect();
|
||
}
|
||
}
|
||
|
||
function connectButtonfunctionEx() {
|
||
currentMeshNode = parent.getCurrentNode();
|
||
connect(currentMeshNode._id, 16992, null, null, 0);
|
||
Q('xconnectbutton1').value = 'Disconnect';
|
||
}
|
||
|
||
function getCurrentMeshNode() { return currentMeshNode; }
|
||
function setConnectionState(isConnectable) {
|
||
QE('xconnectbutton1', isConnectable);
|
||
if (isConnectable == false) { disconnect(); }
|
||
}
|
||
function setFrameHeight(height) {
|
||
//console.log('setFrameHeight', height);
|
||
//QS('entireBody').height = height;
|
||
}
|
||
function setAuthCallback(updateAmtCredentials) {
|
||
meshcentral2credCallback = updateAmtCredentials;
|
||
}
|
||
|
||
function cleanup() {
|
||
}
|
||
|
||
function handleKeyUp(e) {
|
||
//console.log('handleKeyUp', e);
|
||
if (xxdialogMode) return;
|
||
}
|
||
|
||
function handleKeyDown(e) {
|
||
//console.log('handleKeyDown', e);
|
||
if (xxdialogMode) return;
|
||
}
|
||
|
||
function handleKeyPress(e) {
|
||
//console.log('handleKeyPress', e);
|
||
if (xxdialogMode) return;
|
||
}
|
||
|
||
function connect(host, port, user, pass, tls) {
|
||
go(0);
|
||
|
||
|
||
wsstack = WsmanStackCreateService(host, port, user, pass, tls);
|
||
amtstack = AmtStackCreateService(wsstack);
|
||
amtstack.onProcessChanged = onProcessChanged;
|
||
|
||
// Setup TLS checking
|
||
|
||
// Hide TLS indicator
|
||
|
||
// Hide most of the left menu options, we will see them as we load data
|
||
for (var i = 2; i < 24; i++) { QV('go' + i, false); }
|
||
QV('go8', true); // Network Settings
|
||
QV('go15', false); // Audit Log
|
||
|
||
QV('go12', true); // WSMAN Browser
|
||
|
||
QV('go16', false); // Security Settings
|
||
|
||
QV('go17', false); // Remote Access
|
||
|
||
QV('go20', true); // Scripting Editor
|
||
|
||
|
||
QH('storagelinks', '');
|
||
|
||
// Clear everything, make sure all connection state is reset.
|
||
amtversion = amtversionmin = amtFirstPull = 0;
|
||
amtsysstate = amtdeltatime = amtlogicalelements = HardwareInventory = undefined;
|
||
amtPowerBootCapabilities = null;
|
||
xxAccountFetch = 999;
|
||
|
||
|
||
// Show loading on all panels
|
||
QH('id_TableSysStatus', LoadingHtml);
|
||
|
||
QH('id_TableNetworkSettingsSpan', LoadingHtml);
|
||
amtwirelessif = -1;
|
||
|
||
xxWireless = undefined;
|
||
QH('id_TableWifi2', '');
|
||
|
||
QH('id_TableSysInfo', LoadingHtml);
|
||
|
||
xxAccountAdminName = null;
|
||
xxAccountRealmInfo = {};
|
||
QH('id_TableUserAccounts', LoadingHtml);
|
||
|
||
eventmessages = null;
|
||
QH('id_TableEventLog','');
|
||
QH('id_TableEventLog2',LoadingHtml);
|
||
|
||
auditLog = null;
|
||
QH('id_TableAuditLog1', '');
|
||
QH('id_TableAuditLog2', LoadingHtml);
|
||
|
||
xxCertificates = null;
|
||
QH('id_TableCerts', LoadingHtml);
|
||
|
||
QH('id_wsresults', '');
|
||
|
||
xxRemoteAccess = null;
|
||
xxEnvironementDetection = null;
|
||
xxCiraServers = null;
|
||
xxUserInitiatedCira = null;
|
||
xxRemoteAccessCredentiaLinks = null;
|
||
xxMPSUserPass = null;
|
||
xxPolicies = null;
|
||
QH('id_TableRemoteAccess', LoadingHtml);
|
||
|
||
QH('id_TableSystemAgentPresence', LoadingHtml);
|
||
|
||
xxSystemDefense = null;
|
||
xxSystemDefenceLinkedPolicy = {};
|
||
xxUpdatingDefenseStats = false;
|
||
xxFilterStatistics = [{}, {}]; // Wired and wireless interface stats
|
||
xxFilterStatisticsTimer = null;
|
||
xxFilterStatisticsTimerActive = false;
|
||
QH('id_TableSystemDefense', LoadingHtml);
|
||
|
||
|
||
// Start pulling Intel AMT information
|
||
amtstack.BatchEnum("", ["CIM_SoftwareIdentity", "*AMT_SetupAndConfigurationService"], processSystemVersion); // Get Intel AMT version information and plenty more
|
||
//amtstack.Enum("CIM_LogicalElement", processSystemVersion); // Get Intel AMT version information and plenty more
|
||
|
||
|
||
QV('id_versionWarning', false);
|
||
|
||
|
||
fupdatescript();
|
||
}
|
||
|
||
function disconnect() {
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
StopDefenseStatsTimer();
|
||
|
||
|
||
script_Stop();
|
||
|
||
breakScriptButton();
|
||
|
||
dialogclose(0);
|
||
|
||
if (amtstack) {
|
||
amtstack.onProcessChanged = null; // Un-hook progress indicator
|
||
amtstack.CancelAllQueries(999); // Fail all pending WSMAN calls. Set to 999 to indicate not to call back any of the pending calls with errors.
|
||
amtstack = null;
|
||
}
|
||
cleanup();
|
||
wsstack = null;
|
||
delete amtstack;
|
||
onProcessChanged(0, 1);
|
||
go(101);
|
||
|
||
|
||
|
||
Q('xconnectbutton1').value = 'Connect';
|
||
QH('id_messageviewstr', 'Disconnected');
|
||
go(100);
|
||
}
|
||
|
||
function onProcessChanged(a, b) {
|
||
QS('id_progressbar').width = ((a * 100) / b) + "%";
|
||
if (a == 0) refreshButtons(true); // If nothing is being done, re-enable refresh buttons
|
||
|
||
// If there is nothing being done now, see if we can pull more information
|
||
if (a != 0 || !amtstack) return;
|
||
if ((amtversion > 0) && ((amtFirstPull & 64) == 0)) {
|
||
amtFirstPull |= 64;
|
||
PullPowerPolicy();
|
||
|
||
subscriptionsFilters = null;
|
||
PullEventSubscriptions();
|
||
|
||
PullWatchdog();
|
||
|
||
|
||
scriptViewButton(script_BuildingBlocks?1:0);
|
||
|
||
if (amtversion > 5) {
|
||
|
||
PullCertificates();
|
||
|
||
PullRemoteAccess();
|
||
}
|
||
return;
|
||
}
|
||
|
||
if ((amtFirstPull & 1) == 0) { PullHardware(); return; }
|
||
|
||
if ((amtFirstPull & 16) == 0) { PullEventLog(); return; }
|
||
|
||
if ((amtFirstPull & 32) == 0) { PullAuditLog(); return; }
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
//
|
||
// BOTTOM INFORMATION STRING
|
||
//
|
||
|
||
|
||
// Returns true if Intel AMT needs to be updated
|
||
function checkAmtVersion(version) {
|
||
var vSplit = version.split('.');
|
||
var v1 = parseInt(vSplit[0]);
|
||
var v2 = parseInt(vSplit[1]);
|
||
var v3 = parseInt(vSplit[2]);
|
||
var vx = ((v2 * 1000) + v3);
|
||
var ok = 0;
|
||
|
||
if ((v1 <= 5) || (v1 >= 12)) { ok = 1; } // Intel AMT less then v5 and v12 and beyond, all ok.
|
||
else if ((v1 == 6) && (vx >= 2061)) { ok = 1; } // 1st Gen Core
|
||
else if ((v1 == 7) && (vx >= 1091)) { ok = 1; } // 2st Gen Core
|
||
else if ((v1 == 8) && (vx >= 1071)) { ok = 1; } // 3st Gen Core
|
||
else if ((v1 == 9)) { if ((v2 < 5) && (vx >= 1041)) { ok = 1; } else if (vx >= 5061) { ok = 1; } } // 4st Gen Core
|
||
else if ((v1 == 10) && (vx >= 55)) { ok = 1; } // 5st Gen Core
|
||
else if (v1 == 11) {
|
||
if ((v2 < 5) && (vx >= 25)) { ok = 1; } // 6st Gen Core
|
||
else if (vx >= 6027) { ok = 1; } // 7st Gen Core
|
||
}
|
||
return (ok == 0);
|
||
}
|
||
|
||
function processSystemVersion(stack, name, responses, status) {
|
||
if (errcheck(status, stack)) return;
|
||
if (status == 200) {
|
||
//amtlogicalelements = responses;
|
||
amtlogicalelements = [];
|
||
if (responses != null)
|
||
{
|
||
if (responses["CIM_SoftwareIdentity"] != null && responses["CIM_SoftwareIdentity"].responses != null) {
|
||
amtlogicalelements = responses["CIM_SoftwareIdentity"].responses;
|
||
if (responses["AMT_SetupAndConfigurationService"] != null && responses["AMT_SetupAndConfigurationService"].response != null) {
|
||
amtlogicalelements.push(responses["AMT_SetupAndConfigurationService"].response);
|
||
}
|
||
}
|
||
}
|
||
if (amtlogicalelements.length == 0) { console.error('ERROR: Could not get Intel AMT version.'); disconnect(); return; } // Could not get Intel AMT version, disconnect();
|
||
var v = getInstance(amtlogicalelements, "AMT")["VersionString"];
|
||
amtversion = parseInt(v.split('.')[0]);
|
||
amtversionmin = parseInt(v.split('.')[1]);
|
||
|
||
QV('id_versionWarning', checkAmtVersion(v));
|
||
|
||
//if (amtversion > 10) { wsstack.comm.MaxActiveAjaxCount = 4; amtstack.MaxActiveEnumsCount = 1; console.log('HTTP Pipelining activated'); } // With Intel AMT 11 and higher, use HTTP pipelining.
|
||
PullUserInfo();
|
||
PullSystemStatus();
|
||
updateSystemStatus();
|
||
|
||
|
||
if (amtversion >= 8) PullAlarms();
|
||
|
||
|
||
|
||
|
||
}
|
||
}
|
||
|
||
// Control the state of all refresh buttons
|
||
var refreshButtonsState = true;
|
||
function refreshButtons(x) {
|
||
if (refreshButtonsState == x) return;
|
||
refreshButtonsState = x;
|
||
var i = 0, e = document.getElementsByTagName('input');
|
||
for (; i < e.length; i++) { if (e[i].name == 'refreshbtn') { e[i].disabled = !x; } }
|
||
}
|
||
|
||
// Display TLS certificate
|
||
|
||
//
|
||
// SYSTEM STATUS PANEL
|
||
//
|
||
|
||
|
||
function PullPowerState() {
|
||
if (amtstack && amtstack.GetPendingActions() == 0 && amtsysstate && amtsysstate['CIM_ServiceAvailableToElement']) { amtstack.Enum('CIM_ServiceAvailableToElement', function(stack, name, responses, status) { if (errcheck(status, stack)) return; amtsysstate['CIM_ServiceAvailableToElement'].responses = responses; updateSystemStatus(); }); }
|
||
}
|
||
|
||
function PullSystemStatus(x) {
|
||
refreshButtons(false);
|
||
amtstack.AMT_TimeSynchronizationService_GetLowAccuracyTimeSynch(processSystemTime);
|
||
|
||
var query = ["CIM_ServiceAvailableToElement", "*AMT_GeneralSettings", "AMT_EthernetPortSettings", "*AMT_RedirectionService", "CIM_ElementSettingData"];
|
||
if (amtversion > 5) query.push("IPS_IPv6PortSettings", "*CIM_KVMRedirectionSAP", "*IPS_OptInService","*IPS_KVMRedirectionSettingData");
|
||
//if (amtversion > 5) query.push("IPS_IPv6PortSettings", "*CIM_KVMRedirectionSAP", "*IPS_OptInService");
|
||
amtstack.BatchEnum("", query, processSystemStatus, true);
|
||
|
||
if (x == 1) PullWireless();
|
||
}
|
||
|
||
function processSystemTime(stack, name, responses, status) {
|
||
if (errcheck(status, stack)) return;
|
||
if (status == 200) {
|
||
// Convert ms to time and adjust for the timezone
|
||
t = new Date();
|
||
t2 = new Date();
|
||
t.setTime(responses.Body["Ta0"] * 1000 + (t.getTimezoneOffset() * 60 * 1000));
|
||
amtdeltatime = t - t2;
|
||
updateSystemStatus();
|
||
}
|
||
}
|
||
|
||
var amtdeltatime;
|
||
var amtsysstate;
|
||
var amtlogicalelements; // processSystemVersion
|
||
var amtfeatures = {};
|
||
function processSystemStatus(stack, name, responses, status) {
|
||
// If this computer has no KVM, ignore and continue (Standard Manageability)
|
||
if ((responses['IPS_KVMRedirectionSettingData'] == undefined) || (responses['IPS_KVMRedirectionSettingData'].status == 400)) { responses['IPS_KVMRedirectionSettingData'] = null; }
|
||
if ((responses['CIM_KVMRedirectionSAP'] == undefined) || (responses['CIM_KVMRedirectionSAP'].status == 400)) { responses['CIM_KVMRedirectionSAP'] = null; }
|
||
if ((responses['IPS_OptInService'] == undefined) || (responses['IPS_OptInService'].status == 400)) { responses['IPS_OptInService'] = null; }
|
||
|
||
// Reset the status after possible KVM removal
|
||
status = 0;
|
||
for (var i in responses) { if ((responses[i] != null) && (responses[i].status > status)) { status = responses[i].status; } }
|
||
|
||
if (errcheck(status, stack)) return;
|
||
amtsysstate = responses;
|
||
updateSystemStatus();
|
||
}
|
||
|
||
|
||
var DMTFPowerStates = ["", "", "Power on", "Light sleep", "Deep sleep", "Power cycle (Soft off)", "Off - Hard", "Hibernate (Off soft)", "Soft off", "Power cycle (Off-hard)", "Master bus reset", "Diagnostic interrupt (NMI)", "Not applicable", "Off - Soft graceful", "Off - Hard graceful", "Master bus reset graceful", "Power cycle (Off - Soft graceful)", "Power cycle (Off - Hard graceful)", "Diagnostic interrupt (INIT)"];
|
||
function updateSystemStatus() {
|
||
if ((!amtsysstate) || (currentView > 99)) return;
|
||
|
||
// System Status Table
|
||
var systemdefense = 0, host, y, x = TableStart(), features = '', gs = amtsysstate['AMT_GeneralSettings'].response;
|
||
|
||
t = "Unknown";
|
||
if (amtsysstate['CIM_ServiceAvailableToElement'] != null) { t = DMTFPowerStates[amtsysstate['CIM_ServiceAvailableToElement'].responses[0]["PowerState"]]; }
|
||
if (gs['PowerSource']) t += [", Plugged-in", ", On Battery"][gs['PowerSource']];
|
||
x += TableEntry("Power", addLink(t, "showPowerActionDlg()"));
|
||
|
||
host = gs["HostName"];
|
||
y = gs["DomainName"];
|
||
if (y != null && y.length > 0) host += "." + y;
|
||
if (host.length == 0) { host = "<i>None</i>"; } else { host = EscapeHtml(host); }
|
||
x += TableEntry("Name & Domain", addLinkConditional(host, 'showEditNameDlg()', xxAccountAdminName));
|
||
if (HardwareInventory) x += TableEntry("System ID", guidToStr(HardwareInventory['CIM_ComputerSystemPackage'].response["PlatformGUID"].toLowerCase()));
|
||
if (amtlogicalelements) {
|
||
var mode = '', scs = getItem(amtlogicalelements, "CreationClassName", "AMT_SetupAndConfigurationService");
|
||
|
||
if ((scs["ProvisioningState"] == 2) && (amtversion > 5)) {
|
||
mode = ' activated in Admin Control Mode (ACM)';
|
||
if (scs["ProvisioningMode"] == 4) { mode = ' activated in Client Control Mode (CCM)'; systemdefense = 9; }
|
||
}
|
||
|
||
|
||
x += TableEntry("Intel® ME", "v" + getItem(amtlogicalelements, "InstanceID", "AMT")["VersionString"] + mode);
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
// Intel AMT power state warning. If computer is not powered on, display a warning in terminal and desktop panels.
|
||
QV('id_p13warning2', amtsysstate['CIM_ServiceAvailableToElement'].responses[0]["PowerState"] != 2);
|
||
QV('id_p14warning2', amtsysstate['CIM_ServiceAvailableToElement'].responses[0]["PowerState"] != 2);
|
||
|
||
|
||
// Intel AMT Features
|
||
var redir = amtfeatures[0] = (amtsysstate['AMT_RedirectionService'].response["ListenerEnabled"] == true);
|
||
var sol = amtfeatures[1] = ((amtsysstate['AMT_RedirectionService'].response["EnabledState"] & 2) != 0);
|
||
var ider = amtfeatures[2] = ((amtsysstate['AMT_RedirectionService'].response["EnabledState"] & 1) != 0);
|
||
var kvm = amtfeatures[3] = undefined;
|
||
if ((amtversion > 5) && (amtsysstate['CIM_KVMRedirectionSAP'] != null)) {
|
||
kvm = amtfeatures[3] = ((amtsysstate['CIM_KVMRedirectionSAP'].response["EnabledState"] == 6 && amtsysstate['CIM_KVMRedirectionSAP'].response["RequestedState"] == 2) || amtsysstate['CIM_KVMRedirectionSAP'].response["EnabledState"] == 2 || amtsysstate['CIM_KVMRedirectionSAP'].response["EnabledState"] == 6);
|
||
}
|
||
if (redir) features += ", Redirection Port";
|
||
if (sol) features += ", Serial-over-LAN";
|
||
if (ider) features += ", IDE-Redirect";
|
||
if (kvm) features += ", KVM";
|
||
if (features == '') features = " None";
|
||
x += TableEntry("Active Features", addLinkConditional(features.substring(2), 'showFeaturesDlg()', xxAccountAdminName));
|
||
if ((amtsysstate['IPS_KVMRedirectionSettingData'] != null) && (amtsysstate['IPS_KVMRedirectionSettingData'].response)) {
|
||
var desktopSettings = amtsysstate['IPS_KVMRedirectionSettingData'].response;
|
||
var screenname = 'Primary display';
|
||
if ((amtversion > 7) && (desktopSettings['DefaultScreen'] !== undefined) && (desktopSettings['DefaultScreen'] < 255)) { screenname = ['Primary display', 'Secondary display', '3rd display'][desktopSettings['DefaultScreen']]; }
|
||
features = '<span title="The default remote display is the ' + screenname.toLowerCase() + '">' + screenname + "</span>";
|
||
if (desktopSettings['Is5900PortEnabled'] == true) { features += ", Port 5900 enabled"; }
|
||
if (desktopSettings['OptInPolicy'] == true) { features += ", " + desktopSettings['OptInPolicyTimeout'] + " second" + ((desktopSettings['OptInPolicyTimeout'] > 0)?'s':'') + " opt-in"; }
|
||
features += ", " + desktopSettings['SessionTimeout'] + " minute" + ((desktopSettings['SessionTimeout'] > 0)?'s':'') + " session timeout";
|
||
|
||
x += TableEntry("Remote Desktop", features);
|
||
}
|
||
|
||
// Intel AMT user Consent
|
||
if ((amtversion > 5) && (amtsysstate['IPS_OptInService'] != null) && (amtsysstate['IPS_OptInService'].response != undefined)) {
|
||
features = "Unknown state";
|
||
var optinrequired = amtsysstate['IPS_OptInService'].response["OptInRequired"];
|
||
if (optinrequired == 0) { features = "Not Required"; }
|
||
if (optinrequired == 1) { features = "Required for KVM only"; }
|
||
if (optinrequired == 0xFFFFFFFF) { features = "Always Required"; }
|
||
if (amtsysstate['IPS_OptInService'].response["CanModifyOptInPolicy"] == true) features = addLinkConditional(features, 'showConsentDlg()', xxAccountAdminName); // If we can modify user consent settings, add the link to do it.
|
||
x += TableEntry("User Consent", features);
|
||
}
|
||
|
||
// Intel AMT Power policy
|
||
if (AmtSystemPowerSchemes != null) {
|
||
var elementSettings = amtsysstate['CIM_ElementSettingData'].responses;
|
||
for (var i = 0; i < elementSettings.length; i++) {
|
||
if (elementSettings[i]['SettingData'] && elementSettings[i]['IsCurrent'] == 1 && elementSettings[i]['SettingData']['ReferenceParameters']['ResourceURI'] == 'http://intel.com/wbem/wscim/1/amt-schema/1/AMT_SystemPowerScheme') {
|
||
// Found the active Intel AMT power scheme
|
||
var powerguid = elementSettings[i]['SettingData']['ReferenceParameters']['SelectorSet']['Selector'][1]['Value'];
|
||
// Now find the name of the power schema in the AMT_SystemPowerScheme table
|
||
for (var j = 0; j < AmtSystemPowerSchemes.length; j++) {
|
||
if (AmtSystemPowerSchemes[j]['SchemeGUID'] == powerguid) {
|
||
// Found the power scheme name
|
||
x += TableEntry("Power Policy", addLinkConditional(AmtSystemPowerSchemes[j]['Description'].split(':' )[1], "showPowerPolicyDlg(\"" + powerguid + "\")", xxAccountAdminName));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (amtdeltatime) { x += TableEntry("Date & Time", new Date(new Date().getTime() + amtdeltatime).toLocaleString()); }
|
||
var buttons = AddRefreshButton("PullSystemStatus()") + " ";
|
||
|
||
buttons += AddButton("Power Actions...", "showPowerActionDlg()") + " ";
|
||
|
||
buttons += AddButton("Save State...", "saveEntireAmtState()") + " ";
|
||
|
||
buttons += AddButton("Run Script...", "script_runScriptDlg()") + " ";
|
||
x += TableEnd(buttons);
|
||
QH('id_TableSysStatus', x);
|
||
|
||
|
||
var x = "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px>";
|
||
x += TableEnd("<div> " + AddRefreshButton("PullSystemStatus(1)")
|
||
|
||
+ ' Changing network settings may cause this page to becaume unavailable.'
|
||
);
|
||
|
||
// General settings
|
||
x += "<br><h2>General Settings</h2>";
|
||
x += TableStart();
|
||
|
||
// Network state
|
||
// if (gs["AMTNetworkEnabled"]) { x += TableEntry("Network state", (gs["AMTNetworkEnabled"]==1)?"Enabled":"Disabled"); }
|
||
|
||
// Shared FQDN
|
||
var fqdnshare = '';
|
||
if (host != "<i>None</i>") {
|
||
if (gs["SharedFQDN"] == true) fqdnshare = ', shared with OS';
|
||
if (gs["SharedFQDN"] == false) fqdnshare = ', different from OS';
|
||
}
|
||
x += TableEntry("Name & Domain", addLinkConditional(host + fqdnshare, 'showEditNameDlg(1)', xxAccountAdminName));
|
||
|
||
// Dynamic DNS
|
||
var ddns = 'Disabled';
|
||
if (gs["DDNSUpdateEnabled"] == true) {
|
||
ddns = "Enabled each " + gs["DDNSPeriodicUpdateInterval"] + " minutes, TTL is " + gs["DDNSTTL"] + " minutes";
|
||
} else if (gs["DDNSUpdateByDHCPServerEnabled"] == true) {
|
||
ddns = "Update by DHCP server";
|
||
}
|
||
x += TableEntry("Dynamic DNS", addLinkConditional(ddns, 'showEditDnsDlg()', xxAccountAdminName));
|
||
x += TableEnd();
|
||
|
||
for (var y in amtsysstate['AMT_EthernetPortSettings'].responses) {
|
||
var z = amtsysstate['AMT_EthernetPortSettings'].responses[y];
|
||
if (z['WLANLinkProtectionLevel'] || (y == 1)) { amtwirelessif = y; } // Set the wireless interface, this seems to cover new wireless only computers and older computers with dual interfaces.
|
||
if ((y == 0) && (amtwirelessif != y) && (z["MACAddress"] == "00-00-00-00-00-00")) { continue; } // On computers with only wireless, the wired interface will have a null MAC, skip it.
|
||
if (y == 0) systemdefense++;
|
||
x += "<br><h2>" + ((amtwirelessif == y)?'Wireless':'Wired') + " Interface</h2>";
|
||
x += TableStart();
|
||
|
||
/*
|
||
var lp = [], linkpolicy = MakeToArray(z["LinkPolicy"]);
|
||
for (var p in linkpolicy) {
|
||
var ap = linkpolicy[p];
|
||
if (ap == 1) lp.push("S0/AC");
|
||
if (ap == 14) lp.push("Sx/AC");
|
||
if (ap == 16) lp.push("S0/DC");
|
||
if (ap == 224) lp.push("Sx/DC");
|
||
}
|
||
x += TableEntry("Link state", (z["LinkIsUp"] == true?'Link is up':'Link is down') + ", available in " + lp.join(', '));
|
||
*/
|
||
x += TableEntry("Link state", (z["LinkIsUp"] == true?'Link is up':'Link is down'));
|
||
if (z["MACAddress"] != "00-00-00-00-00-00") { x += TableEntry("MAC address", z["MACAddress"]); }
|
||
|
||
|
||
if ((amtwirelessif == y) && xxWireless && xxWireless['CIM_WiFiPortCapabilities'].response) {
|
||
// Start and state
|
||
x += TableEntry("State", addLinkConditional(xxWifiState[xxWireless['CIM_WiFiPort'].response["EnabledState"]], 'showWifiStateDlg()', xxAccountAdminName));
|
||
|
||
/* This is not that useful
|
||
// Capabilities
|
||
var s = '', sc = xxWireless['CIM_WiFiPortCapabilities'].response, supportedTypes = sc["SupportedPortTypes"];
|
||
for (i in supportedTypes) { s += ", 802.11" + "abgn"[supportedTypes[i] - 70]; }
|
||
x += TableEntry("Capabilites", s.substring(2));
|
||
*/
|
||
|
||
// Current radio state
|
||
s = xxWireless['CIM_WiFiEndpoint'].response['LANID'];
|
||
x += TableEntry("Radio State", xxRadioState[xxWireless['CIM_WiFiEndpoint'].response['EnabledState']] + ", SSID: " + (s?s:"<i>None</i>"));
|
||
}
|
||
|
||
if (amtwirelessif != y) {
|
||
// Things that are specific to the wired interface
|
||
x += TableEntry("Respond to ping", addLinkConditional(["Disabled","ICMP response","RMCP response","ICMP & RMCP response"][gs["PingResponseEnabled"] + (gs["RmcpPingResponseEnabled"] << 1)], "showPingActionDlg()", xxAccountAdminName));
|
||
x += TableEntry("IPv4 state", addLinkConditional(z["DHCPEnabled"] == true?"Automatic using DHCP server":"Static IP address", "showIPSetupDlg()", xxAccountAdminName));
|
||
}
|
||
|
||
// Display IPv4 current settings
|
||
x += TableEntry("IPv4 address", isIpAddress(z["IPAddress"],"None"));
|
||
if (isIpAddress(z["DefaultGateway"])) { x += TableEntry("IPv4 gateway / Mask", z["DefaultGateway"] + " / " + isIpAddress(z["SubnetMask"],"None")); }
|
||
|
||
var dns = z["PrimaryDNS"];
|
||
if (isIpAddress(dns)) {
|
||
if (z["SecondaryDNS"]) dns += " / " + z["SecondaryDNS"];
|
||
x += TableEntry("IPv4 domain name server", dns);
|
||
}
|
||
|
||
if (amtversion > 5) {
|
||
// Check if IPv6 is enabled for this interface
|
||
var zz = amtsysstate['IPS_IPv6PortSettings'].responses[y];
|
||
var ipv6state = 'Disabled', ipv6, ipv6manual, elementSettings = amtsysstate['CIM_ElementSettingData'].responses;
|
||
for (var i = 0; i < elementSettings.length; i++) {
|
||
if (elementSettings[i]['SettingData'] && elementSettings[i]['SettingData']['ReferenceParameters']['SelectorSet']['Selector']['Value'] == 'Intel(r) IPS IPv6 Settings ' + y) {
|
||
ipv6 = (elementSettings[i]['IsCurrent'] == 1);
|
||
}
|
||
}
|
||
|
||
// Check if manual addresses have been added
|
||
if (ipv6 == true) {
|
||
ipv6manual = isIpAddress(zz["IPv6Address"]) || isIpAddress(zz["DefaultRouter"]) || isIpAddress(zz["PrimaryDNS"]) || isIpAddress(zz["SecondaryDNS"]);
|
||
ipv6state = 'Enabled, Automatic ' + (ipv6manual?"& manual":"") + " addresses";
|
||
}
|
||
|
||
// Display IPv6 current settings
|
||
x += TableEntry("IPv6 state", addLinkConditional(ipv6state, "showIPv6StateDlg(" + y + "," + ipv6 + ")", xxAccountAdminName));
|
||
if (ipv6 == true) {
|
||
if (zz["CurrentAddressInfo"] && zz["CurrentAddressInfo"].length > 0) {
|
||
zz["CurrentAddressInfo"] = MakeToArray(zz["CurrentAddressInfo"]); // Because our WSMAN stack does not know if this is a array, we need to make it an array if it contains 1 element.
|
||
ipv6addr = '';
|
||
for (var i = 0; i < zz["CurrentAddressInfo"].length; i++) { if (ipv6addr.length > 0) ipv6addr += ", "; ipv6addr += zz["CurrentAddressInfo"][i].split(',')[0]; }
|
||
x += TableEntry("IPv6 address", addLink(ipv6addr, "showIPv6AddrDlg(" + y + ",\"" + zz["CurrentAddressInfo"] + "\")"));
|
||
} else {
|
||
x += TableEntry("IPv6 address", 'None');
|
||
}
|
||
if (isIpAddress(zz["CurrentDefaultRouter"])) { x += TableEntry("IPv6 default router", zz["CurrentDefaultRouter"]); }
|
||
if (isIpAddress(zz["CurrentPrimaryDNS"])) {
|
||
var dns = zz["CurrentPrimaryDNS"];
|
||
if (isIpAddress(zz["CurrentSecondaryDNS"])) dns += " / " + zz["CurrentSecondaryDNS"];
|
||
x += TableEntry("IPv6 domain name server", dns);
|
||
}
|
||
}
|
||
}
|
||
|
||
x += TableEnd();
|
||
}
|
||
|
||
|
||
// If there is a wireless interface, get the data for it. If not, show that there is not.
|
||
if (amtwirelessif != -1) { if ((amtFirstPull & 2) == 0) PullWireless(); }
|
||
QH('id_TableNetworkSettingsSpan', x);
|
||
|
||
|
||
// If in ACM mode and we have a wired interface, pull System Defense information
|
||
if ((systemdefense == 1) && ((amtFirstPull & 4) == 0)) PullSystemDefense();
|
||
|
||
|
||
if (((amtFirstPull & 8) == 0) && (amtversion > 11 || (amtversion == 11 && amtversionmin > 5))) { PullStorage(); }
|
||
|
||
if (currentView == 0) go(1, 1); // If we are at the loading screen, most to System Status screen
|
||
}
|
||
|
||
function isIpAddress(t, x) { return (t && t != null && t.length > 0 && t != '::' && t != '::0')?t:x; }
|
||
|
||
|
||
var IntelAmtEntireState;
|
||
var IntelAmtEntireStateCalls;
|
||
function saveEntireAmtState() {
|
||
if (xxdialogMode) return;
|
||
var n = '', d = new Date();
|
||
if (amtsysstate) { n = "-" + amtsysstate['AMT_GeneralSettings'].response['HostName']; }
|
||
n += '-' + d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2) + "-" + ("0" + d.getHours()).slice(-2) + "-" + ("0" + d.getMinutes()).slice(-2);;
|
||
idx_d19savestatefilename.value = 'amtstate' + n + '.json';
|
||
setDialogMode(19, "Save Entire Intel® AMT State", 3, saveEntireAmtStateOk);
|
||
}
|
||
|
||
function saveEntireAmtStateOk() {
|
||
IntelAmtEntireState = { 'webappversion':version,'localtime':Date(),'utctime':new Date().toUTCString(),'isotime':new Date().toISOString() };
|
||
QH('id_dialogMessage', 'Fetching entire state, please wait...');
|
||
setDialogMode(1, "Save Entire Intel® AMT State", 0, null);
|
||
|
||
// Fetch everything!
|
||
IntelAmtEntireStateCalls = 3;
|
||
amtstack.BatchEnum(null, AllWsman, saveEntireAmtStateOk2, null, true);
|
||
amtstack.GetAuditLog(saveEntireAmtStateOk3);
|
||
amtstack.GetMessageLog(saveEntireAmtStateOk4);
|
||
}
|
||
|
||
function saveEntireAmtStateOk2(stack, name, responses, status) { IntelAmtEntireState['wsmanenums'] = responses; saveEntireAmtStateDone(); }
|
||
function saveEntireAmtStateOk3(stack, messages) { IntelAmtEntireState['auditlog'] = messages; saveEntireAmtStateDone(); }
|
||
function saveEntireAmtStateOk4(stack, messages) { IntelAmtEntireState['eventlog'] = messages; saveEntireAmtStateDone(); }
|
||
|
||
function saveEntireAmtStateDone() {
|
||
if (--IntelAmtEntireStateCalls != 0) return;
|
||
setDialogMode();
|
||
|
||
saveAs(data2blob(JSON.stringify(IntelAmtEntireState, null, ' ').replace(/\n/g, '\r\n')), idx_d19savestatefilename.value);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// EVENT LOG PANEL
|
||
//
|
||
|
||
function PullEventLog(button) {
|
||
if (button == 1 && xxdialogMode) return;
|
||
amtFirstPull |= 16;
|
||
amtstack.Enum("AMT_MessageLog", processMessageLog0);
|
||
amtstack.GetMessageLog(processMessageLog1);
|
||
}
|
||
|
||
var processMessageLog0responses = null;
|
||
function processMessageLog0(stack, name, responses, status) {
|
||
if (status != 200) return;
|
||
if (status) QV('go6', true); // Show event log left panel option
|
||
if (responses) processMessageLog0responses = responses;
|
||
var fr = '', x = "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px>";
|
||
if (processMessageLog0responses != null) { fr = ((processMessageLog0responses[0]["IsFrozen"] == true) ? AddButton("Un-freeze Log", "FreezeLog(0)") : AddButton("Freeze Log", "FreezeLog(1)")); }
|
||
|
||
|
||
x += TableEnd("<div style=float:right><input id=eventFilter placeholder=Filter style=margin:4px onkeyup=eventFilter()> </div><div> " + AddRefreshButton("PullEventLog(1)") + AddButton("Clear Log", "ClearLog()") + AddButton("Save...", "SaveEventLog()") + fr);
|
||
QH('id_TableEventLog', x + '<br>');
|
||
}
|
||
|
||
|
||
function SaveEventLog() {
|
||
if (xxdialogMode || eventmessages == null) return;
|
||
SaveJsonFile('IntelAmtEventlog', 'events', 'Intel AMT Event Log', eventmessages);
|
||
}
|
||
|
||
var eventmessages = null;
|
||
function processMessageLog1(stack, messages) {
|
||
eventmessages = messages;
|
||
var i, y = 0, x = "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td width=80px><p><td><td><td>";
|
||
x += "<tr><td class=r1 style=width:90px><b> Event</b><td class=r1 style=width:110px><b>Time</b><td class=r1 style=width:160px><b>Source</b><td class=r1><b>Description</b>";
|
||
for (i in messages) {
|
||
y++;
|
||
var icon = 1, m = messages[i];
|
||
if (m['EventSeverity'] >= 8) { icon = 2; } if (m['EventSeverity'] >= 16) { icon = 3; }
|
||
x += "<tr id=xamtevent" + i + " class=r3 onclick=showEventDetails(" + i + ")><td class=r1><p><div class=icon" + icon + " style=display:block;float:left;margin-left:5px;margin-right:5px></div>" + (parseInt(i) + 1) + "<td class=r1 title='" + m['Time'].toLocaleString() + "'>" + m['Time'].toLocaleDateString('en', { year: "numeric", month: "2-digit", day: "numeric" }) + "<br>" + m['Time'].toLocaleTimeString('en', { hour:"2-digit", minute:"2-digit", second:"2-digit" }) + "<td class=r1>" + m['EntityStr'].replace("(r)", "®") + "<td class=r1>" + m['Desc'];
|
||
}
|
||
x += TableEnd(y == 0 ? " " : "");
|
||
QH('id_TableEventLog2', x + '<br>');
|
||
processMessageLog0();
|
||
}
|
||
|
||
function FreezeLog(x) { if (!xxdialogMode) { amtstack.AMT_MessageLog_FreezeLog(x, function () { amtstack.Enum("AMT_MessageLog", processMessageLog0); }) } }
|
||
function ClearLog(x) { if (!xxdialogMode) { QH('id_dialogMessage', 'Clear event log?'); setDialogMode(1, "Event Log", 3, ClearLogEx); } }
|
||
function ClearLogEx() { amtstack.AMT_MessageLog_ClearLog(function (stack, name, responses, status) { if (status != 200) messagebox("Event Log", "Unable to clear, Error: " + status); else PullEventLog(); }) }
|
||
|
||
function showEventDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var m = eventmessages[h];
|
||
var x = '<div style=text-align:left>';
|
||
x += addHtmlValue("Time", m['Time'].toLocaleString());
|
||
x += addHtmlValue("Source", m['EntityStr'].replace("(r)", "®"));
|
||
x += addHtmlValue("Description", m['Desc']);
|
||
x += MoreStart();
|
||
x += addHtmlValue("Device Address", m['DeviceAddress']);
|
||
x += addHtmlValue("Entity", m['Entity']);
|
||
x += addHtmlValue("Entity Instance", m['EntityInstance']);
|
||
var e = '';
|
||
for (var i in m['EventData']) { if (e.length > 0) e +=','; e += m['EventData'][i]; }
|
||
x += addHtmlValue("Data", e);
|
||
x += addHtmlValue("Offset", m['EventOffset']);
|
||
x += addHtmlValue("Sensor Type", m['EventSensorType']);
|
||
x += addHtmlValue("Severity", m['EventSeverity']);
|
||
x += addHtmlValue("Source Type", m['EventSourceType']);
|
||
x += addHtmlValue("Type", m['EventType']);
|
||
x += addHtmlValue("Sensor Number", m['SensorNumber']);
|
||
x += MoreEnd();
|
||
x += "</div>";
|
||
messagebox("Event #" + (h + 1) + " Details", x);
|
||
}
|
||
|
||
function eventFilter() {
|
||
var filter = Q('eventFilter').value.toLowerCase();
|
||
for (var w in eventmessages) { QV('xamtevent' + w, filter == '' || eventmessages[w]['Desc'].toLowerCase().indexOf(filter) >= 0 || eventmessages[w]['EntityStr'].toLowerCase().indexOf(filter) >= 0); }
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// EVENT SUBSCRIPTION PANEL
|
||
//
|
||
|
||
var subscriptionsFilters = null; // List of event filters
|
||
var subscriptionsListeners = null; // List of subscribers
|
||
|
||
function PullEventSubscriptions() {
|
||
if (subscriptionsFilters == null) { amtstack.Enum("CIM_FilterCollection", processEventSubscriptions0); }
|
||
amtstack.BatchEnum(null, ["CIM_ListenerDestination", "CIM_FilterCollectionSubscription"], processEventSubscriptions1);
|
||
}
|
||
|
||
function processEventSubscriptions0(stack, name, responses, status) {
|
||
if (status == 408 || status == 400) return;
|
||
if (status && errcheck(status, stack)) return;
|
||
subscriptionsFilters = responses;
|
||
}
|
||
|
||
function processEventSubscriptions1(stack, name, responses, status) {
|
||
if (status == 408 || status == 400) return;
|
||
if (status && errcheck(status, stack)) return;
|
||
if (status) QV('go22', true); // Show event subscription left panel option
|
||
if (responses) subscriptionsListeners = responses;
|
||
var c= 0, x = TableStart2() + "<tr><td class=r1 style=padding-left:15px><br>Manage event subscribers.<br><br>";
|
||
for (var i in subscriptionsListeners['CIM_ListenerDestination']['responses']) {
|
||
var f = subscriptionsListeners['CIM_FilterCollectionSubscription']['responses'][i]['Filter']['ReferenceParameters']['SelectorSet']['Selector']['Value'].replace('(r)', '®');
|
||
var r = subscriptionsListeners['CIM_ListenerDestination']['responses'][i];
|
||
x += "<div class=itemBar onclick='showSubscriptionDetails(" + i + ")'><div style=float:right></div><div style=padding-top:3px;overflow-x:hidden title='" + f + " - " + r['Destination'] + "'><b>" + f + "</b> - " + r['Destination'] + "</div><div style=padding-top:3px></div></div>";
|
||
c++;
|
||
}
|
||
if (c == 0) x += "<div style=padding-left:15px><br><i>No subscriptions found.</i></div><br>";
|
||
var y = "<div> " + AddRefreshButton("PullEventSubscriptions()");
|
||
if (xxAccountAdminName) y += AddButton("New Subscription", "newSubscriptionButton()");
|
||
x += "<br><td class=r1>" + TableEnd(y + "</div>");
|
||
QH('id_TableEventSubscriptions', x);
|
||
}
|
||
|
||
var subscriptionDeliveryModes = { 2:'Push', 3:'Push with ACK', 4:'Events', 5:'Pull' };
|
||
function showSubscriptionDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var f = subscriptionsListeners['CIM_FilterCollectionSubscription']['responses'][h]['Filter']['ReferenceParameters']['SelectorSet']['Selector']['Value'].replace('(r)', '®');
|
||
var r = subscriptionsListeners['CIM_ListenerDestination']['responses'][h];
|
||
var x = '<div style=text-align:left>';
|
||
x += addHtmlValue("Destination", r['Destination']);
|
||
x += addHtmlValue("Filter", f);
|
||
x += addHtmlValue("Delivery Mode", subscriptionDeliveryModes[r['DeliveryMode']]);
|
||
x += "</div>";
|
||
setDialogMode(11, "Subscription " + (h + 1), xxAccountAdminName?5:1, deleteSubscriptionButton, x, h);
|
||
}
|
||
|
||
function deleteSubscriptionButton(button, h) {
|
||
if (button != 2) return;
|
||
var name = subscriptionsListeners['CIM_ListenerDestination']['responses'][h]['Name'];
|
||
var filter = subscriptionsListeners['CIM_FilterCollectionSubscription']['responses'][h]['Filter']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
|
||
amtstack.UnSubscribe('CIM_FilterCollectionSubscription', PullEventSubscriptions, null, 1, {'Filter':'<a:EndpointReference><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address><a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_FilterCollection</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + filter + '</w:Selector></w:SelectorSet></a:ReferenceParameters></a:EndpointReference>', 'Handler':'<a:EndpointReference><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address><a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ListenerDestinationWSManagement</w:ResourceURI><w:SelectorSet><w:Selector Name="CreationClassName">CIM_ListenerDestinationWSMAN</w:Selector><w:Selector Name="Name">' + name + '</w:Selector><w:Selector Name="SystemCreationClassName">CIM_ComputerSystem</w:Selector><w:Selector Name="SystemName">Intel(r) AMT</w:Selector></w:SelectorSet></a:ReferenceParameters></a:EndpointReference>'});
|
||
}
|
||
|
||
function newSubscriptionButton() {
|
||
if (xxdialogMode || (subscriptionsFilters == null)) return;
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><select id=subtype style=float:right;width:260px><option value=Push>Push</option><option value=PushWithAck>Push with ACK</option></select><div style=padding-top:4px>Type</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=subfilter style=float:right;width:260px>";
|
||
for (var i in subscriptionsFilters) { x += "<option value='" + subscriptionsFilters[i]['InstanceID'] + "'>" + subscriptionsFilters[i]['CollectionName'].substring(13) + "</option>"; }
|
||
x += "</select><div style=padding-top:4px>Filter</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input id=suburl style=float:right;width:260px maxlength=253 onkeyup=newSubscriptionUpdate() value='http://'><div style=padding-top:4px>URL</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=subauth style=float:right;width:260px onchange=newSubscriptionUpdate()><option value=0>None</option><option value=1>Digest</option></select><div style=padding-top:4px>Authentication</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px id=subxuser><input id=subuser style=float:right;width:260px maxlength=32 onkeyup=newSubscriptionUpdate()><div style=padding-top:4px>Username</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px id=subxpass><input id=subpass style=float:right;width:260px maxlength=32 onkeyup=newSubscriptionUpdate()><div style=padding-top:4px>Password</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input id=subargs style=float:right;width:260px maxlength=128><div style=padding-top:4px>Arguments</div></div>";
|
||
setDialogMode(11, 'Add Event Subscription', 3, newSubscriptionButtonOk, x);
|
||
newSubscriptionUpdate();
|
||
}
|
||
|
||
function newSubscriptionUpdate() {
|
||
QE('idx_dlgOkButton', ((Q('suburl').value.length > 0) && (Q('suburl').value.startsWith('http://')) && ((Q('subauth').value == 0) || ((Q('subuser').value.length > 0) && (Q('subpass').value.length > 0)))));
|
||
QV('subxuser', Q('subauth').value == 1);
|
||
QV('subxpass', Q('subauth').value == 1);
|
||
}
|
||
|
||
function newSubscriptionButtonOk() {
|
||
var user = (Q('subuser').value.length == 0)?undefined:Q('subuser').value, pass = (Q('subpass').value.length == 0)?undefined:Q('subpass').value;
|
||
amtstack.Subscribe('CIM_FilterCollection', Q('subtype').value, Q('suburl').value, newSubscriptionButtonOk2, null, 1, { 'InstanceID' : Q('subfilter').value }, (Q('subargs').value.length > 0)?Q('subargs').value:null, user, pass);
|
||
}
|
||
|
||
function newSubscriptionButtonOk2(stack, name, responses, status) {
|
||
if (status == 200) { PullEventSubscriptions(); }
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// AUDIT LOG PANEL
|
||
//
|
||
|
||
function PullAuditLog(button) {
|
||
if (button == 1 && xxdialogMode) return;
|
||
amtFirstPull |= 32;
|
||
amtstack.Enum("AMT_AuditLog", processAuditLog0);
|
||
}
|
||
|
||
var auditLog = null;
|
||
var auditLogEnabledStates = ["Unknown", "Other", "Enabled", "Disabled", "Shutting Down", "Not Applicable", "Enabled but Offline", "In Test", "Deferred", "Quiesce", "Starting"];
|
||
|
||
function processAuditLog0(stack, name, responses, status) {
|
||
if (status == 200) {
|
||
QV('go15', true); // Show audit log left panel option
|
||
|
||
var r = responses[0]["AuditState"];
|
||
var auditstate = (r & 0x01) ? 'Disabled' : 'Enabled';
|
||
if (r & 0x02) auditstate += ", Locked";
|
||
if (r & 0x04) auditstate += ", Almost Full";
|
||
if (r & 0x08) auditstate += ", Full";
|
||
if (r & 0x10) auditstate += ", NoKey";
|
||
|
||
var x = "<h1>Audit Log Settings</h1>" + TableStart();
|
||
x += TableEntry("State", auditstate);
|
||
x += TableEntry("Storage", responses[0]["CurrentNumberOfRecords"] + " record(s), " + responses[0]["PercentageFree"] + "% free");
|
||
x += TableEntry("Overwrite policy", ((responses[0]["OverwritePolicy"] == 2) ? "Wraps when full" : "Never overwrites"));
|
||
x += TableEnd();
|
||
QH('id_TableAuditLog1', x);
|
||
|
||
amtstack.GetAuditLog(processAuditLog1);
|
||
}
|
||
}
|
||
|
||
function processAuditLog1(stack, messages) {
|
||
auditLog = messages;
|
||
var i, x = "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px>";
|
||
|
||
x += TableEnd("<div style=float:right><input id=auditFilter placeholder=Filter style=margin:4px onkeyup=auditFilter()> </div><div> " + AddRefreshButton("PullAuditLog(1)") + AddButton("Save...", "SaveAuditLog()") + AddButton("Clear Log", "ClearAuditLog()") /* + AddButton("Settings...", "ShowAuditLogSettings()")*/) + "<br>";
|
||
if (messages.length == 0) {
|
||
x = "No audit log events found.";
|
||
} else {
|
||
var y = 0;
|
||
x += "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td width=80px><p><td><td><td>";
|
||
x += "<tr><td class=r1 style=width:110px> <b>Time</b><td class=r1 style=width:260px><b>Initiator</b><td class=r1><b>Action</b>";
|
||
for (i in messages) {
|
||
var m = messages[i], description = m['AuditApp'], initiator = m['Initiator'];
|
||
y++;
|
||
var addr = '';
|
||
if (m['NetAddress'].length > 0) addr = m['NetAddress'].replace('0000:0000:0000:0000:0000:0000:0000:0001','::1');
|
||
if (m['Event']) description += ", " + m['Event'];
|
||
if (m['ExStr'] != null) description += ", " + m['ExStr'];
|
||
if (initiator != '' && addr != '') initiator += ", ";
|
||
x += "<tr id=xamtaudit" + i + " class=r3 onclick=showAuditDetails(" + i + ")><td class=r1 title='" + m['Time'].toLocaleString() + "'> " + m['Time'].toLocaleDateString('en', { year: "numeric", month: "2-digit", day: "numeric" }) + "<br> " + m['Time'].toLocaleTimeString('en', { hour:"2-digit", minute:"2-digit", second:"2-digit" }) + "<td class=r1>" + initiator + addr + "<td class=r1>" + description;
|
||
}
|
||
x += TableEnd(y == 0 ? " " : "") + "<br>";
|
||
}
|
||
QH('id_TableAuditLog2', x);
|
||
}
|
||
|
||
function auditFilter() {
|
||
var filter = Q('auditFilter').value.toLowerCase();
|
||
for (var w in auditLog) { QV('xamtaudit' + w, filter == '' || JSON.stringify(auditLog[w]).toLowerCase().indexOf(filter) >= 0); }
|
||
}
|
||
|
||
|
||
function SaveAuditLog() {
|
||
if (xxdialogMode || auditLog == null) return;
|
||
SaveJsonFile('IntelAmtAuditlog', 'auditevents', 'Intel AMT Audit Log', auditLog);
|
||
}
|
||
|
||
function ClearAuditLog(x) { QH('id_dialogMessage', 'Clear audit log?'); setDialogMode(1, "Audit Log", 3, ClearAuditLogEx); }
|
||
function ClearAuditLogEx() {
|
||
//amtstack.AMT_AuditLog_ClearLog(function () { PullAuditLog(); })
|
||
var handle = amtstack.AMT_AuditLog_SetAuditLock(1,0, handle, function() { amtstack.AMT_AuditLog_ClearLog(function () { amtstack.AMT_AuditLog_SetAuditLock(0, 2, handle, function () { setTimeout(PullAuditLog,1000);}); }); });
|
||
}
|
||
|
||
function ShowAuditLogSettings() {
|
||
if (xxdialogMode) return;
|
||
amtstack.AMT_AuditLog_RequestStateChange(2, 0, AuditLogSettingsCompleted); // Enable
|
||
}
|
||
|
||
function AuditLogSettingsCompleted(stack, name, responses, status) {
|
||
if (status == 200) PullAuditLog(); else messagebox("Audit Log", "Error: " + status);
|
||
}
|
||
|
||
function showAuditDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var i, m = auditLog[h], x = '<div style=text-align:left>';
|
||
x += addHtmlValue("Time", m['Time'].toLocaleString());
|
||
if (m['Initiator'] != '') x += addHtmlValue("Initiator", m['Initiator']);
|
||
if (m['NetAddress'] != '') x += addHtmlValue("Address", m['NetAddress']);
|
||
x += addHtmlValue("Application", m['AuditApp']);
|
||
x += addHtmlValue("Event", m['Event']);
|
||
if (m['ExStr'] != null) {
|
||
x += addHtmlValue("Extended Data", m['ExStr']);
|
||
} else if (m['Ex'].length > 0) {
|
||
var e = '';
|
||
for (i in m['Ex']) { if (e.length > 0) e += ','; e += m['Ex'].charCodeAt(i); }
|
||
if (e != '') x += addHtmlValue("Data Values", e);
|
||
if (m['Ex'].length > 2 && ReadShort(m['Ex'], 0) == (m['Ex'].length - 2)) x += addHtmlValue("Data String", m['Ex'].substring(2));
|
||
}
|
||
x += "</div>";
|
||
messagebox("Audit Event #" + (h + 1) + " Details", x);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Certificates
|
||
//
|
||
|
||
var xxCertificates = null;
|
||
var xxCertPrivateKeys = null;
|
||
var xxTlsSettings = null;
|
||
var xxTlsCurrentCert = null;
|
||
var xxTLSCredentialContext = null;
|
||
var xxCaPrivateKey = null;
|
||
var xxCaSubjectAttributes = null;
|
||
|
||
function PullCertificates() {
|
||
// We only deal with certificates starting with Intel AMT 6 and beyond
|
||
amtstack.BatchEnum(null, ["AMT_PublicKeyCertificate", "AMT_PublicPrivateKeyPair", "AMT_TLSSettingData", "AMT_TLSCredentialContext"], processCerts1);
|
||
}
|
||
|
||
function processCerts1(stack, name, responses, status) {
|
||
if (errcheck(status, stack)) return;
|
||
QV('go16', true); // Security Settings
|
||
xxCertificates = responses["AMT_PublicKeyCertificate"].responses;
|
||
xxCertPrivateKeys = responses["AMT_PublicPrivateKeyPair"].responses;
|
||
xxTlsSettings = responses["AMT_TLSSettingData"].responses;
|
||
xxTLSCredentialContext = responses["AMT_TLSCredentialContext"].responses;
|
||
|
||
// Select the current TLS certificate in the drop down box
|
||
xxTlsCurrentCert = null;
|
||
if (xxTLSCredentialContext.length > 0) {
|
||
var certInstanceId = xxTLSCredentialContext[0]['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
|
||
for (var i in xxCertificates) { if (xxCertificates[i]['InstanceID'] == certInstanceId) { xxTlsCurrentCert = i; } }
|
||
}
|
||
|
||
// Setup the certificates
|
||
for (var i in xxCertificates) {
|
||
xxCertificates[i].TrustedRootCertficate = (xxCertificates[i]["TrustedRootCertficate"] == true);
|
||
xxCertificates[i].X509Certificate = window.atob(xxCertificates[i]["X509Certificate"]);
|
||
xxCertificates[i].XIssuer = parseCertName(xxCertificates[i]["Issuer"]);
|
||
xxCertificates[i].XSubject = parseCertName(xxCertificates[i]["Subject"]);
|
||
}
|
||
amtcert_linkCertPrivateKey(xxCertificates, xxCertPrivateKeys); // This links all certificates and private keys
|
||
|
||
updateCertificates();
|
||
}
|
||
|
||
function parseCertName(x) {
|
||
var j, r = {}, xx = x.split(',');
|
||
for (var i in xx) { j = xx[i].indexOf('='); r[xx[i].substring(0, j)] = xx[i].substring(j + 1); }
|
||
return r;
|
||
}
|
||
|
||
function getTlsSecurityState(x) {
|
||
if (xxTlsSettings[x]['Enabled'] == false) return "Disabled";
|
||
var r = ((xxTlsSettings[x]['MutualAuthentication'] == true) ? 'Mutual-auth' : 'Server-auth') + ((xxTlsSettings[x]['AcceptNonSecureConnections'] == true) ? " and non-TLS" : "");
|
||
if ((xxTlsSettings[x]['MutualAuthentication'] == true) && (xxTlsSettings[x].TrustedCN)) {
|
||
var trustedCn = MakeToArray(xxTlsSettings[x].TrustedCN);
|
||
if (trustedCn.length > 0) { r += ", Trusted name" + ((trustedCn.length > 1)?'s':'') + ": " + trustedCn.join(', ') + "."; }
|
||
}
|
||
return r;
|
||
}
|
||
|
||
function updateCertificates() {
|
||
if (xxCertificates == null) return;
|
||
var x = '';
|
||
|
||
// General settings
|
||
x += TableStart();
|
||
x += TableEntry("Remote TLS security", addLinkConditional(getTlsSecurityState(1), 'showSetTlsSecurityDlg()', xxAccountAdminName));
|
||
x += TableEntry("Local TLS security", addLinkConditional(getTlsSecurityState(0), 'showSetTlsSecurityDlg()', xxAccountAdminName));
|
||
x += TableEnd();
|
||
|
||
x += "<br>";
|
||
x += TableStart2();
|
||
x += "<tr><td class=r1 style=padding-left:15px><br>Manage Intel® AMT certificates for this computer.<br><br>";
|
||
if (xxCertificates.length == 0 && xxCertPrivateKeys.length == 0) {
|
||
x += "<div style=padding-left:15px><br><i>No certificates found.</i></div><br>";
|
||
} else {
|
||
for (var i in xxCertificates) {
|
||
var desc = '';
|
||
if (xxCertificates[i].TrustedRootCertficate) desc = ', Trusted Root';
|
||
if (xxCertificates[i].XPrivateKey) desc = ', Private Key';
|
||
if (i == xxTlsCurrentCert) desc += ', TLS cert';
|
||
x += "<div class=itemBar onclick=showCertDetails(" + i + ")><div style=padding-top:3px><b>" + EscapeHtml(xxCertificates[i].XSubject["CN"]) + "</b><i>" + desc + "</i></div></div>";
|
||
}
|
||
for (var i in xxCertPrivateKeys) {
|
||
if (!xxCertPrivateKeys[i].XCert) {
|
||
x += "<div class=itemBar onclick=showKeyPairDetails(" + i + ")><div style=padding-top:3px><i>Unassigned Private Key Pair #" + i + "</i></div></div>";
|
||
}
|
||
}
|
||
}
|
||
var buttons = AddRefreshButton("PullCertificates()");
|
||
if (xxAccountAdminName) { buttons += (AddButton("Add Certificate...", "addCertButton()") + AddButton("Issue Certificate...", "issueCertButton()")); }
|
||
x += "<br><td class=r1>" + TableEnd(buttons);
|
||
QH('id_TableCerts', x);
|
||
}
|
||
|
||
function showKeyPairDetails(h) {
|
||
var x = "This is a public/private certificate key pair that does not belong to any certificates. This entry should be temporary.";
|
||
setDialogMode(11, "Key Pair #" + h, 5, function (b) { if (b == 2) { amtstack.Delete('AMT_PublicPrivateKeyPair', { 'InstanceID': xxCertPrivateKeys[h]['InstanceID'] }, PullCertificates, 0, 1); } }, x);
|
||
}
|
||
|
||
var xxCertSubjectNames = { 'CN': 'Common Name', 'O': 'Organization', 'OU': 'Org Unit', 'S': 'State/Province', 'ST': 'State/Province', 'L': 'Locality', 'C': 'Country', 'SN': 'Surname', 'GN': 'Given name' };
|
||
function showCertDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var c = xxCertificates[h], x = '<br>';
|
||
|
||
x += addHtmlValue("Certificate", c.X509Certificate.length + " bytes, <a style=cursor:pointer;color:blue onclick=downloadCert(" + h + ")>Download</a>");
|
||
x += addHtmlValue("Trusted root", c.TrustedRootCertficate ? "Yes" : "No");
|
||
if (c.TrustedRootCertficate == false && c.XPrivateKey) { x += addHtmlValue('Private key', 'Present'); }
|
||
|
||
// Show certificate usages
|
||
/*
|
||
y = [];
|
||
if (extKeyUsage != null) {
|
||
if (extKeyUsage.clientAuth == true) { y.push("TLS Client"); }
|
||
if (extKeyUsage.codeSigning == true) { y.push("Code Signing"); }
|
||
if (extKeyUsage.emailProtection == true) { y.push("EMail"); }
|
||
if (extKeyUsage.serverAuth == true) { y.push("TLS Server"); }
|
||
if (extKeyUsage["2.16.840.1.113741.1.2.1"] == true) { y.push("Intel® AMT Console"); }
|
||
if (extKeyUsage["2.16.840.1.113741.1.2.2"] == true) { y.push("Intel® AMT Agent"); }
|
||
if (extKeyUsage["2.16.840.1.113741.1.2.3"] == true) { y.push("Intel® AMT Activation"); }
|
||
if (extKeyUsage.timeStamping == true) { y.push("Time Stamping"); }
|
||
if (y.length > 0) { x += addHtmlValueNoTitle("Certificate Usage", y.join(', ') + '.') + '<br clear=all />'; }
|
||
}
|
||
*/
|
||
|
||
x += '<br><div style="border-bottom:1px solid gray"><i>Certificate Subject</i></div><br>';
|
||
for (var i in c.XSubject) { if (c.XSubject[i]) { x += addHtmlValue(xxCertSubjectNames[i] ? xxCertSubjectNames[i] : i, EscapeHtml(c.XSubject[i])); } }
|
||
// x += addHtmlValueNoTitle('Fingerprint', c.fingerprint.substring(0,29) + '<br />' + c.fingerprint.substring(30)); // TODO: Parse the certificate using Forge and get the fingerprint
|
||
x += '<br><div style="border-bottom:1px solid gray"><i>Issuer Certificate</i></div><br>';
|
||
for (var i in c.XIssuer) { if (c.XIssuer[i]) { x += addHtmlValue(xxCertSubjectNames[i] ? xxCertSubjectNames[i] : i, EscapeHtml(c.XIssuer[i])); } }
|
||
setDialogMode(11, "Certificate - " + EscapeHtml(c.XSubject["CN"]), 5, function (b) {
|
||
if (b == 2) {
|
||
// If there is a private key, delete it.
|
||
if (xxCertificates[h].XPrivateKey) amtstack.Delete('AMT_PublicPrivateKeyPair', { 'InstanceID': xxCertificates[h].XPrivateKey['InstanceID'] }, function () { }, 0, 1);
|
||
// Delete the certificate
|
||
amtstack.Delete('AMT_PublicKeyCertificate', xxCertificates[h], certificateRemoved, 0, 1);
|
||
}
|
||
}, x);
|
||
}
|
||
|
||
|
||
function downloadCert(h) {
|
||
|
||
saveAs(data2blob(xxCertificates[h].X509Certificate), xxCertificates[h].XSubject['CN'] + ".cer");
|
||
}
|
||
|
||
function cert_FileSelectHandler(e) {
|
||
haltEvent(e);
|
||
if (e.dataTransfer.files.length == 1) {
|
||
if (e.dataTransfer.files[0].name.toLowerCase().endsWith('.p12')) {
|
||
issueCertButton(e.dataTransfer.files);
|
||
} else {
|
||
addCertButton(e.dataTransfer.files);
|
||
}
|
||
}
|
||
}
|
||
|
||
var xxDragDropCertFiles = null;
|
||
function addCertButton(files) {
|
||
if (xxdialogMode || !xxAccountAdminName) return;
|
||
var x = '<div style=height:10px></div>';
|
||
xxDragDropCertFiles = files;
|
||
|
||
var input = "<input id=certopen onchange=addCertButtonUpdate() type=file style=float:right;width:260px accept='.cer,.pem'>";
|
||
if (xxDragDropCertFiles) { input = '<input style=float:right;width:260px readonly disabled value="' + xxDragDropCertFiles[0].name + '">'; }
|
||
x += "<div style=height:26px;margin-top:4px>" + input + "<div style=padding-top:4px>Certificate file</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=certtype style=float:right;width:260px><option value=0>Chain Certificate</option><option value=1>Trusted Root Certificate</option></select><div style=padding-top:4px>Certificate type</div></div>";
|
||
setDialogMode(11, "Add Certificate", 3, addCertButtonOk, x);
|
||
addCertButtonUpdate();
|
||
}
|
||
|
||
function addCertButtonUpdate() {
|
||
var certopen = getInputElement('certopen');
|
||
QE('idx_dlgOkButton', !certopen || certopen.files.length == 1);
|
||
}
|
||
|
||
function addCertButtonOk() {
|
||
|
||
var certopen = getInputElement('certopen');
|
||
var files = xxDragDropCertFiles;
|
||
if (certopen) files = certopen.files;
|
||
if (files && files.length == 1) {
|
||
var reader = new FileReader();
|
||
reader.onload = addCertButtonOk2;
|
||
reader.readAsBinaryString(files[0]);
|
||
}
|
||
}
|
||
|
||
function addCertButtonOk2(file) {
|
||
var data = file.target.result;
|
||
var i = data.indexOf('-----BEGIN CERTIFICATE-----');
|
||
if (i > 0) {
|
||
// This is a .PEM file, keep everything between BEGIN/END, clean it up and use as-is. It's already Base64.
|
||
data = data.substring(i + 27);
|
||
i = data.indexOf('-----END CERTIFICATE-----');
|
||
if (i > 0) data = data.substring(0, i)
|
||
data = data.replace(/\r\n/g, '');
|
||
} else {
|
||
// This is a .CER file, just base64 encode it and we should be ok.
|
||
data = btoa(data);
|
||
}
|
||
if (getSelectElement('certtype').value == 1) {
|
||
amtstack.AMT_PublicKeyManagementService_AddTrustedRootCertificate(data, certificateAdded);
|
||
} else {
|
||
amtstack.AMT_PublicKeyManagementService_AddCertificate(data, certificateAdded);
|
||
}
|
||
}
|
||
|
||
function issueCertButton(files) {
|
||
if (xxdialogMode || !xxAccountAdminName) return;
|
||
xxDragDropCertFiles = files;
|
||
|
||
|
||
|
||
var x = '', input = "<input id=certopen type=file style=float:right;width:230px onchange=issueCertButtonUpdate() accept='.p12'>";
|
||
if (xxDragDropCertFiles) { input = '<input style=float:right;width:230px readonly disabled value="' + xxDragDropCertFiles[0].name + '">'; }
|
||
x += "<div styleheight:26px;margin-top:14px>" + input + "<div style=padding-top:4px>Certificate file</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input onkeyup=issueCertButtonUpdate() id=certopenpass type=password autocomplete=off style=float:right;width:230px><div style=padding-top:4px>Certificate password</div></div>";
|
||
|
||
x += '<br><div style="border-bottom:1px solid gray"><i>Intel® AMT Certificate</i></div>';
|
||
x += "<div style=height:26px;margin-top:4px><input onkeyup=issueCertButtonUpdate() id=certcn style=float:right;width:230px><div style=padding-top:4px>Common Name</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input onkeyup=issueCertButtonUpdate() id=certo style=float:right;width:230px><div style=padding-top:4px>Organization</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input onkeyup=issueCertButtonUpdate() id=certst style=float:right;width:230px><div style=padding-top:4px>State/Province</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input onkeyup=issueCertButtonUpdate() id=certc style=float:right;width:230px><div style=padding-top:4px>Country</div></div>";
|
||
x += '<div>Certificate Usages</div><ul style="list-style-type:none;height:100px;overflow:auto;width:100%;border: 1px solid #000;background-color:white;overflow-x:hidden;margin:0;padding:0">';
|
||
//x += '<li><label><input type=checkbox id=d11_cu1>Intel® AMT Console</label></li>';
|
||
//x += '<li><label><input type=checkbox id=d11_cu2>Intel® AMT Agent</label></li>';
|
||
//x += '<li><label><input type=checkbox id=d11_cu3>Intel® AMT Activation</label></li>';
|
||
x += '<li><label><input type=checkbox id=d11_cu4 checked>TLS Server (HTTPS)</label></li>';
|
||
x += '<li><label><input type=checkbox id=d11_cu5>TLS Client (HTTPS)</label></li>';
|
||
x += '<li><label><input type=checkbox id=d11_cu6>Email Protection</label></li>';
|
||
x += '<li><label><input type=checkbox id=d11_cu7>Code Signing</label></li>';
|
||
x += '<li><label><input type=checkbox id=d11_cu8>Time Stamp</label></li>';
|
||
x += '</ul>';
|
||
setDialogMode(11, "Issue Certificate", 3, issueCertButtonOk, x);
|
||
issueCertButtonUpdate();
|
||
}
|
||
|
||
function issueCertButtonUpdate() {
|
||
var certopen = getInputElement('certopen');
|
||
QE('certopenpass', !certopen || (certopen && certopen.files.length == 1));
|
||
var x = (!certopen || certopen.files.length < 2);
|
||
if ((!certopen || (certopen && certopen.files.length)) == 1 && Q('certopenpass').value == '') x = false;
|
||
if (getInputElement('certcn').value == '' || getInputElement('certo').value == '' || getInputElement('certst').value == '' || getInputElement('certc').value == '') { x = false; }
|
||
QE('idx_dlgOkButton', x);
|
||
}
|
||
|
||
function issueCertButtonOk() {
|
||
|
||
var certopen = getInputElement('certopen');
|
||
var files = xxDragDropCertFiles;
|
||
if (certopen) files = certopen.files;
|
||
if (files && files.length == 1) {
|
||
// Issue a certificate using this file
|
||
var reader = new FileReader();
|
||
reader.onload = issueCertButtonOk2;
|
||
reader.readAsBinaryString(files[0]);
|
||
} else {
|
||
// Issue a certificate using a dummy CA
|
||
issueCertButtonOk3(null);
|
||
}
|
||
}
|
||
|
||
function issueCertButtonOk2(file) {
|
||
// Load the CA certificate and private key
|
||
var r = amtcert_loadP12File(file.target.result, Q('certopenpass').value, issueCertButtonOk3);
|
||
if (r == false) { messagebox("Issue Certificate", "Unable to decrypt/decode certificate."); return; }
|
||
}
|
||
|
||
function issueCertButtonOk3(privateKey, subjectAttributes, cert) {
|
||
// Ask Intel AMT to generate a key pair
|
||
xxCaPrivateKey = privateKey;
|
||
xxCaSubjectAttributes = subjectAttributes;
|
||
amtstack.AMT_PublicKeyManagementService_GenerateKeyPair(0, 2048, GenerateKeyPairResponse);
|
||
}
|
||
|
||
function GenerateKeyPairResponse(stack, serviceName, response, status) {
|
||
if (status != 200) { messagebox('Issue Certificate', 'Failed to generate key pair. Status: ' + status); return; }
|
||
if (response.Body['ReturnValue'] != 0) { messagebox('Issue Certificate', 'Failed to generate key pair, ' + response.Body['ReturnValueStr']); return; }
|
||
|
||
// Get the new key pair
|
||
amtstack.Enum("AMT_PublicPrivateKeyPair", GenerateKeyPairResponse2, response.Body['KeyPair']['ReferenceParameters']['SelectorSet']['Selector']['Value']);
|
||
}
|
||
|
||
function GenerateKeyPairResponse2(stack, serviceName, response, status, tag) {
|
||
if (status != 200) { messagebox('Issue Certificate', 'Failed to generate key pair. Status: ' + status); return; }
|
||
var DERKey = null;
|
||
for (var i in response) { if (response[i]['InstanceID'] == tag) DERKey = response[i]['DERKey']; }
|
||
|
||
// Get certificate values
|
||
var certattributes = { 'CN': getInputElement('certcn').value, 'O': getInputElement('certo').value, 'ST': getInputElement('certst').value, 'C': getInputElement('certc').value };
|
||
var issuerattributes = { 'CN': 'Untrusted Root Certificate' };
|
||
if (xxCaPrivateKey != null && xxCaSubjectAttributes) {
|
||
issuerattributes = {};
|
||
for (var i in xxCaSubjectAttributes) {
|
||
issuerattributes[xxCaSubjectAttributes[i].shortName] = xxCaSubjectAttributes[i].value; // The issuing CA's subject attributes
|
||
}
|
||
}
|
||
|
||
// Figure out the extended key usages
|
||
var extKeyUsage = { name: 'extKeyUsage' }
|
||
//if (Q('d11_cu1').checked) { extKeyUsage['2.16.840.1.113741.1.2.1'] = true; extKeyUsage.clientAuth = true; }
|
||
//if (Q('d11_cu2').checked) { extKeyUsage['2.16.840.1.113741.1.2.2'] = true; extKeyUsage.clientAuth = true; }
|
||
//if (Q('d11_cu3').checked) { extKeyUsage['2.16.840.1.113741.1.2.3'] = true; extKeyUsage.clientAuth = true; }
|
||
if (Q('d11_cu4').checked) { extKeyUsage.serverAuth = true; }
|
||
if (Q('d11_cu5').checked) { extKeyUsage.clientAuth = true; }
|
||
if (Q('d11_cu6').checked) { extKeyUsage.emailProtection = true; }
|
||
if (Q('d11_cu7').checked) { extKeyUsage.codeSigning = true; }
|
||
if (Q('d11_cu8').checked) { extKeyUsage.timeStamping = true; }
|
||
|
||
// Sign the key pair using the CA certifiate
|
||
var cert = amtcert_signWithCaKey(DERKey, xxCaPrivateKey, certattributes, issuerattributes, extKeyUsage);
|
||
if (cert == null) { messagebox('Issue Certificate', 'Unable to sign certificate.'); return; }
|
||
|
||
// Place the resulting signed certificate back into AMT
|
||
var pem = forge.pki.certificateToPem(cert).replace(/(\r\n|\n|\r)/gm, "");
|
||
amtstack.AMT_PublicKeyManagementService_AddCertificate(pem.substring(27, pem.length - 25), GenerateKeyPairResponse4);
|
||
}
|
||
|
||
function GenerateKeyPairResponse4(stack, serviceName, response, status) {
|
||
if (status != 200) { messagebox('Issue Certificate', 'Failed to generate key pair. Status: ' + status); return; }
|
||
PullCertificates();
|
||
}
|
||
|
||
function certificateAdded(stack, serviceName, response, status) { if (status != 200 || response.Body['ReturnValue'] != 0) { messagebox('Add Certificate', 'Unable to add certificate, error ' + (status != 200 ? status : response.Body['ReturnValueStr'])); } else PullCertificates(); }
|
||
function certificateRemoved(stack, serviceName, header, status) { if (status != 200) { messagebox('Remove Certificate', 'Unable to remove certificate, error ' + status); } else PullCertificates(); }
|
||
|
||
function getInputElement(id) { var allelements = document.getElementsByTagName('input'); for (t = 0; t < allelements.length; t++) { if (allelements[t].id == id) return allelements[t]; } }
|
||
function getSelectElement(id) { var allelements = document.getElementsByTagName('select'); for (t = 0; t < allelements.length; t++) { if (allelements[t].id == id) return allelements[t]; } }
|
||
|
||
function showSetTlsSecurityDlg(x) {
|
||
if (xxdialogMode) return;
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><select onchange=showSetTlsSecurityDlgUpdate() id=tlscert style=float:right;width:260px>";
|
||
x += "<option value=-1>No Certificate, TLS Disabled</option>";
|
||
for (var i in xxCertificates) {
|
||
if (xxCertificates[i].TrustedRootCertficate == false && xxCertificates[i].XPrivateKey && (xxTlsCurrentCert == null || xxTlsCurrentCert == i)) { x += "<option value=" + i + ">" + xxCertificates[i].XSubject["CN"] + "</option>"; }
|
||
}
|
||
x += "</select><div style=padding-top:4px>Certificate</div></div>";
|
||
|
||
x += "<div style=height:26px;margin-top:4px><select id=tlsremote style=float:right;width:260px onchange=showSetTlsSecurityDlgUpdate()>";
|
||
x += "<option value=0>Server-auth TLS only</option>";
|
||
x += "<option value=1>Server-auth, non-TLS allowed</option>";
|
||
x += "<option value=2>Mutual-auth TLS only</option>";
|
||
x += "<option value=3>Mutual-auth, non-TLS allowed</option>";
|
||
x += "</select><div style=padding-top:4px>Remote</div></div>";
|
||
|
||
x += "<div style=height:26px id=d11rcn title='Comma seperated list of certificate common names that will be allowed to connect remotely.'><input id=d11_rcn style=float:right;width:260px onkeyup=showSetTlsSecurityDlgUpdate() placeholder='name1, name2'><div style=padding-top:4px>Remote CN's</div></div>";
|
||
|
||
x += "<div style=height:26px;margin-top:4px><select id=tlslocal style=float:right;width:260px onchange=showSetTlsSecurityDlgUpdate()>";
|
||
x += "<option value=0>Server-auth TLS only</option>";
|
||
x += "<option value=1>Server-auth, non-TLS allowed</option>";
|
||
x += "<option value=2>Mutual-auth TLS only</option>";
|
||
x += "<option value=3>Mutual-auth, non-TLS allowed</option>";
|
||
x += "</select><div style=padding-top:4px>Local</div></div>";
|
||
|
||
x += "<div style=height:26px id=d11lcn title='Comma seperated list of certificate common names that will be allowed to connect locally.'><input id=d11_lcn style=float:right;width:260px onkeyup=showSetTlsSecurityDlgUpdate() placeholder='name1, name2'><div style=padding-top:4px>Local CN's</div></div>";
|
||
|
||
setDialogMode(11, "TLS Settings", 3, showSetTlsSecurityDlgOk, x);
|
||
|
||
// Select the current TLS certificate in the drop down box
|
||
if (xxTLSCredentialContext.length == 0) {
|
||
getSelectElement('tlscert').value = -1;
|
||
} else {
|
||
var certInstanceId = xxTLSCredentialContext[0]['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
|
||
for (var i in xxCertificates) { if (xxCertificates[i]['InstanceID'] == certInstanceId) { getSelectElement('tlscert').value = i; } }
|
||
}
|
||
|
||
// Select correct TLS options in the drop down boxes
|
||
getSelectElement('tlslocal').value = ((xxTlsSettings[0]['MutualAuthentication'] == true) ? 2 : 0) + ((xxTlsSettings[0]['AcceptNonSecureConnections'] == true) ? 1 : 0);
|
||
getSelectElement('tlsremote').value = ((xxTlsSettings[1]['MutualAuthentication'] == true) ? 2 : 0) + ((xxTlsSettings[1]['AcceptNonSecureConnections'] == true) ? 1 : 0);
|
||
if (xxTlsSettings[0].TrustedCN) { Q('d11_lcn').value = MakeToArray(xxTlsSettings[0].TrustedCN).join(', '); }
|
||
if (xxTlsSettings[1].TrustedCN) { Q('d11_rcn').value = MakeToArray(xxTlsSettings[1].TrustedCN).join(', '); }
|
||
|
||
showSetTlsSecurityDlgUpdate();
|
||
}
|
||
|
||
function showSetTlsSecurityDlgUpdate() {
|
||
var h = getSelectElement('tlscert').value;
|
||
QE('tlsremote', h != -1);
|
||
QE('tlslocal', h != -1);
|
||
QV('d11rcn', (h != -1) && (getSelectElement('tlsremote').value > 1));
|
||
QV('d11lcn', (h != -1) && (getSelectElement('tlslocal').value > 1));
|
||
var ok = true;
|
||
if ((getSelectElement('tlsremote').value > 1) && (!splitDomains(Q('d11_rcn').value))) { ok = false; }
|
||
if ((getSelectElement('tlslocal').value > 1) && (!splitDomains(Q('d11_lcn').value))) { ok = false; }
|
||
QE('idx_dlgOkButton', ok);
|
||
}
|
||
|
||
var setTlsSecurityPendingCalls;
|
||
var setTlsSecurityDeleteCredentialContext;
|
||
function showSetTlsSecurityDlgOk() {
|
||
var h = getSelectElement('tlscert').value;
|
||
var r = getSelectElement('tlsremote').value;
|
||
var l = getSelectElement('tlslocal').value;
|
||
var xxTlsSettings2 = Clone(xxTlsSettings);
|
||
setTlsSecurityPendingCalls = 0;
|
||
setTlsSecurityDeleteCredentialContext = null;
|
||
|
||
if (h != -1) {
|
||
// Set the TLS certificate
|
||
if (xxTLSCredentialContext.length > 0) {
|
||
// Modify the current context
|
||
var newTLSCredentialContext = Clone(xxTLSCredentialContext[0]);
|
||
newTLSCredentialContext['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'] = xxCertificates[h]['InstanceID'];
|
||
amtstack.Put('AMT_TLSCredentialContext', newTLSCredentialContext, setTlsSecurityResponse, 0, 1);
|
||
setTlsSecurityPendingCalls++;
|
||
} else {
|
||
// Add a new security context
|
||
amtstack.Create('AMT_TLSCredentialContext', {
|
||
'ElementInContext':'<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + xxCertificates[h]['InstanceID'] + '</w:Selector></w:SelectorSet></a:ReferenceParameters>',
|
||
'ElementProvidingContext':'<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + amtstack.CompleteName('AMT_TLSProtocolEndpointCollection') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="ElementName">TLSProtocolEndpointInstances Collection</w:Selector></w:SelectorSet></a:ReferenceParameters>' }, setTlsSecurityResponse);
|
||
setTlsSecurityPendingCalls++;
|
||
}
|
||
} else {
|
||
// Clear the TLS certificate association (MOVE THIS)
|
||
if (xxTLSCredentialContext.length > 0) { setTlsSecurityDeleteCredentialContext = Clone(xxTLSCredentialContext[0]); }
|
||
}
|
||
|
||
// Remote TLS settings
|
||
xxTlsSettings2[1]['Enabled'] = (h != -1);
|
||
xxTlsSettings2[1]['MutualAuthentication'] = (r >= 2);
|
||
xxTlsSettings2[1]['AcceptNonSecureConnections'] = ((r % 2) == 1);
|
||
xxTlsSettings2[1]['TrustedCN'] = splitDomains(Q('d11_rcn').value);
|
||
|
||
// Local TLS settings
|
||
xxTlsSettings2[0]['Enabled'] = (h != -1);
|
||
xxTlsSettings2[0]['MutualAuthentication'] = (l >= 2);
|
||
xxTlsSettings2[0]['AcceptNonSecureConnections'] = ((l % 2) == 1);
|
||
xxTlsSettings2[0]['TrustedCN'] = splitDomains(Q('d11_lcn').value);
|
||
|
||
// Update TLS settings
|
||
amtstack.Put('AMT_TLSSettingData', xxTlsSettings2[0], setTlsSecurityResponse, 0, 1, xxTlsSettings2[0]);
|
||
amtstack.Put('AMT_TLSSettingData', xxTlsSettings2[1], setTlsSecurityResponse, 0, 1, xxTlsSettings2[1]);
|
||
setTlsSecurityPendingCalls += 2;
|
||
|
||
statusbox("TLS Settings", "Applying new security settings...");
|
||
}
|
||
|
||
// Split a string into a array of domains. Return null if not a valid list.
|
||
function splitDomains(str) {
|
||
str = str.split(',');
|
||
if (str.length == 0) return;
|
||
for (var i in str) { str[i] = str[i].trim(); if ((str[i].indexOf(' ') >= 0) || (str[i].length == 0)) return; }
|
||
if (str.length > 4) return;
|
||
return str;
|
||
}
|
||
|
||
function setTlsSecurityResponse(stack, name, response, status, tag) {
|
||
if (status != 200) { messagebox("", "Failed to set TLS security, status = " + status); return; }
|
||
if (response.Body['ReturnValueStr'] && !methodcheck(response)) return;
|
||
|
||
// Check if all the calls are done
|
||
if ((--setTlsSecurityPendingCalls) == 0)
|
||
{
|
||
// Perform a commit
|
||
amtstack.AMT_SetupAndConfigurationService_CommitChanges(null, setTlsSecurityResponse2);
|
||
}
|
||
}
|
||
|
||
function setTlsSecurityResponse2(stack, name, response, status) {
|
||
if (status != 200) { messagebox("", "Failed to set TLS security, status = " + status); return; }
|
||
// if (response.Body['ReturnValueStr'] && !methodcheck(response)) return;
|
||
setTimeout(setTlsSecurityResponse3, 2000);
|
||
}
|
||
|
||
function setTlsSecurityResponse3() {
|
||
if (setTlsSecurityDeleteCredentialContext != null) { amtstack.Delete('AMT_TLSCredentialContext', setTlsSecurityDeleteCredentialContext, function () { }); }
|
||
PullCertificates();
|
||
setDialogMode();
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// AGENT PRESENCE
|
||
//
|
||
|
||
var xxWatchdog = null;
|
||
|
||
function PullWatchdog() {
|
||
amtstack.BatchEnum(null, ["*AMT_AgentPresenceCapabilities", "AMT_AgentPresenceWatchdog", "AMT_AgentPresenceWatchdogAction", "AMT_StateTransitionCondition", "CIM_ConcreteDependency"], PullWatchdogResponse);
|
||
}
|
||
|
||
function PullWatchdogResponse(stack, name, responses, status) {
|
||
if ((status == 200) && (responses['AMT_AgentPresenceCapabilities'].status == 200)) {
|
||
xxWatchdog = responses;
|
||
|
||
// Place the actions into the watchdogs for easy navigation.
|
||
for (var i in xxWatchdog['CIM_ConcreteDependency'].responses) {
|
||
var link = xxWatchdog['CIM_ConcreteDependency'].responses[i];
|
||
var x1 = getItem(link['Antecedent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'CreationClassName');
|
||
if (x1 && x1['Value'] == 'AMT_AgentPresenceWatchdog') {
|
||
var deviceid = getItem(link['Antecedent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'DeviceID')['Value'];
|
||
var policyConditionName = getItem(link['Dependent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'PolicyConditionName')['Value'];
|
||
var watchdog = getItem(xxWatchdog['AMT_AgentPresenceWatchdog'].responses, 'DeviceID', deviceid);
|
||
var transitionPolicy = getItem(xxWatchdog['AMT_StateTransitionCondition'].responses, 'PolicyConditionName', policyConditionName);
|
||
if (!watchdog.transitions) watchdog.transitions = [];
|
||
watchdog.transitions.push(transitionPolicy);
|
||
}
|
||
if (x1 && x1['Value'] == 'AMT_StateTransitionCondition') {
|
||
var policyConditionName = getItem(link['Antecedent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'PolicyConditionName')['Value'];
|
||
var policyActionName = getItem(link['Dependent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'PolicyActionName')['Value'];
|
||
var transitionPolicy = getItem(xxWatchdog['AMT_StateTransitionCondition'].responses, 'PolicyConditionName', policyConditionName);
|
||
var watchdogAction = getItem(xxWatchdog['AMT_AgentPresenceWatchdogAction'].responses, 'PolicyActionName', policyActionName);
|
||
if (!transitionPolicy.actions) transitionPolicy.actions = [];
|
||
transitionPolicy.actions.push(watchdogAction);
|
||
}
|
||
}
|
||
|
||
updateWatchdog();
|
||
QV('go19', true); // Show Agent Presence panel
|
||
}
|
||
}
|
||
|
||
var watchdogEnabledStates = [ 'Unknown', 'Other', 'Enabled', 'Disabled', 'Shutting Down', 'Not Applicable', 'Enabled but Offline', 'In Test', 'Deferred', 'Quiesce', 'Starting' ];
|
||
var watchdogMonitoredEntity = [ 'Unknown', 'Other', 'Operating System', 'Operating System Boot Process', 'Operating System Shutdown Process', 'Firmware Boot Process', 'BIOS Boot Process', 'Application', 'Service Processor' ];
|
||
function updateWatchdog() {
|
||
if (xxWatchdog == null) return;
|
||
var x = '';
|
||
|
||
// Global Agent Presense Values
|
||
x += TableStart();
|
||
x += TableEntry("Maximum Watchdogs", xxWatchdog['AMT_AgentPresenceCapabilities'].response['MaxTotalAgents'] + " watchdogs");
|
||
x += TableEntry("Maximum Total Actions", xxWatchdog['AMT_AgentPresenceCapabilities'].response['MaxTotalActions'] + " actions");
|
||
// x += TableEntry("Maximum EAC Watchdogs", xxWatchdog['AMT_AgentPresenceCapabilities'].response['MaxEacAgents'] + " watchdogs");
|
||
x += TableEnd() + "<br>";
|
||
|
||
// Watchdogs
|
||
x += TableStart2();
|
||
x += "<tr><td class=r1 style=padding-left:15px><br>Manage Intel® AMT agent presence watchdogs.<br><br>";
|
||
if ((xxWatchdog['AMT_AgentPresenceWatchdog'].responses == null) || (xxWatchdog['AMT_AgentPresenceWatchdog'].responses.length == 0)) {
|
||
x += "<div style=padding-left:15px><i>No agent presence watchdog found.</i></div><br>";
|
||
} else {
|
||
for (var i in xxWatchdog['AMT_AgentPresenceWatchdog'].responses) {
|
||
var w = xxWatchdog['AMT_AgentPresenceWatchdog'].responses[i];
|
||
var n = guidToStr(rstr2hex(atob(w['DeviceID'])));
|
||
if (w['MonitoredEntityDescription'] && w['MonitoredEntityDescription'] != '') n = EscapeHtml(w['MonitoredEntityDescription']);
|
||
x += "<div class=itemBar onclick=showWatchdogDetails(" + i + ")><input type=button style=float:right value='Add Action...' onclick=addWatchdogAction(event," + i + ")>";
|
||
if (w.transitions) { x += "<input type=button style=float:right value='Delete Actions...' onclick=deleteWatchdogActions(event," + i + ")>"; }
|
||
x += "<div style=padding-top:3px><b>" + n + "</b>, " + amtstack.WatchdogCurrentStates[w['CurrentState']] + "</div>";
|
||
var xx = '';
|
||
for (var j in w.transitions) {
|
||
var t = w.transitions[j];
|
||
if (xx != '') xx += "<br>";
|
||
xx += getWatchdogTransitionStr(t['OldState']) + " → " + getWatchdogTransitionStr(t['NewState']);
|
||
if (t.actions) {
|
||
var action = t.actions[0];
|
||
if (action['EventOnTransition'] == true) { xx += " : Event to log"; }
|
||
}
|
||
}
|
||
if (xx != '') { x += "<div style=padding:12px>" + xx + "</div>"; }
|
||
x += "</div>";
|
||
}
|
||
}
|
||
x += "<br>";
|
||
x += TableEnd(AddRefreshButton("PullWatchdog()") + AddButton("Add Watchdog...", "AddWatchdog()"));
|
||
x += "<br>";
|
||
|
||
QH('id_TableSystemAgentPresence', x);
|
||
}
|
||
|
||
function getWatchdogTransitionStr(s) {
|
||
if (s == 31) return "Any State";
|
||
var x = '';
|
||
for (var i in amtstack.WatchdogCurrentStates) { if ((s & i) != 0) { x += ', ' + amtstack.WatchdogCurrentStates[i]; } }
|
||
return x.substring(2);
|
||
}
|
||
|
||
function showWatchdogDetails(i) {
|
||
var w = xxWatchdog['AMT_AgentPresenceWatchdog'].responses[i];
|
||
|
||
var x = '';
|
||
//x += addHtmlValue('DeviceID', guidToStr(rstr2hex(atob(w['DeviceID']))));
|
||
if (w['MonitoredEntityDescription'] && w['MonitoredEntityDescription'] != '') x += addHtmlValue('Description', EscapeHtml(w['MonitoredEntityDescription']));
|
||
x += addHtmlValue('Monitored Entity', watchdogMonitoredEntity[w['MonitoredEntity']]);
|
||
x += addHtmlValue('Current State', amtstack.WatchdogCurrentStates[w['CurrentState']]);
|
||
x += addHtmlValue('Enabled State', watchdogEnabledStates[w['EnabledState']]);
|
||
x += addHtmlValue('Startup Interval', w['StartupInterval'] + ' second(s)');
|
||
x += addHtmlValue('Timeout Interval', w['TimeoutInterval'] + ' second(s)');
|
||
|
||
setDialogMode(11, 'Watchdog ' + guidToStr(rstr2hex(atob(w['DeviceID']))), 5, showWatchdogDetailsOk, x, w);
|
||
}
|
||
|
||
function showWatchdogDetailsOk(x, w) {
|
||
if (x == 2) {
|
||
amtstack.Delete('AMT_AgentPresenceWatchdog', { 'DeviceID': w['DeviceID'] }, PullWatchdog);
|
||
}
|
||
}
|
||
|
||
function AddWatchdog() {
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><input id=wgname style=float:right;width:240px maxlength=16 onkeyup=AddWatchdogUpdate()><div style=padding-top:4px>Name</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input id=wgguid style=float:right;width:240px maxlength=36 onkeyup=AddWatchdogUpdate()><div style=padding-top:4px title='Generate random DeviceID'>" + addLink("DeviceID", "GenerateWatchdogGuid()") + "</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input id=wgstart style=float:right;width:240px maxlength=8 placeholder=3600 onkeyup=AddWatchdogUpdate()><div style=padding-top:4px>Startup (seconds)</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><input id=wgtimeout style=float:right;width:240px maxlength=8 placeholder=60 onkeyup=AddWatchdogUpdate()><div style=padding-top:4px>Timeout (seconds)</div></div>";
|
||
setDialogMode(11, 'Add Watchdog', 3, AddWatchdogOk, x);
|
||
AddWatchdogUpdate();
|
||
}
|
||
|
||
function GenerateWatchdogGuid() {
|
||
var i, guid = '';
|
||
for (i = 0; i < 16; i++) { guid += String.fromCharCode(random(255)); }
|
||
Q('wgguid').value = guidToStr(rstr2hex(guid));
|
||
AddWatchdogUpdate();
|
||
}
|
||
|
||
function AddWatchdogUpdate() {
|
||
var ok = (Q('wgstart').value == '' || parseInt(Q('wgstart').value) > 0) && (Q('wgtimeout').value == '' || parseInt(Q('wgtimeout').value) > 0);
|
||
var gc = 0, g = Q('wgguid').value.toLowerCase();
|
||
for (var i = 0; i < g.length; i++) {
|
||
var c = g.charCodeAt(i);
|
||
if (c == 45) continue;
|
||
if ((c >= 48 && c <= 57) || (c >= 97 && c <= 102)) { gc++; } else { ok = false; }
|
||
}
|
||
if (gc != 32) ok = false;
|
||
QE('dlgOkButton', ok);
|
||
}
|
||
|
||
function AddWatchdogOk() {
|
||
var timeout = Q('wgtimeout').value, startup = Q('wgstart').value;
|
||
if (timeout == '') timeout = 60;
|
||
if (startup == '') startup = 3600;
|
||
var guid = btoa(hex2rstr(guidToStr(Q('wgguid').value.replace(/-/g, '')).replace(/-/g, '')));
|
||
var wdog = { 'CreationClassName': 0, 'DeviceID': guid, 'StartupInterval': startup, 'SystemCreationClassName': 0, 'SystemName': 0, 'TimeoutInterval': timeout, 'MonitoredEntityDescription': Q('wgname').value };
|
||
amtstack.Create('AMT_AgentPresenceWatchdog', wdog, AddWatchdogOk2);
|
||
}
|
||
|
||
function AddWatchdogOk2(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Watchdog', "Unable to add watchdog, error #" + status);
|
||
} else {
|
||
PullWatchdog();
|
||
}
|
||
}
|
||
|
||
function addWatchdogAction(e, i) {
|
||
var w = xxWatchdog['AMT_AgentPresenceWatchdog'].responses[i];
|
||
var x = '<table>';
|
||
x += '<td style=width:180px>From<br>'
|
||
for (var i in amtstack.WatchdogCurrentStates) {
|
||
x += '<input id=wgsa' + i + ' type=checkbox onclick=addWatchdogActionUpdate()> ' + amtstack.WatchdogCurrentStates[i] + '<br />';
|
||
}
|
||
x += '<td>To<br>'
|
||
for (var i in amtstack.WatchdogCurrentStates) {
|
||
x += '<input id=wgsb' + i + ' type=checkbox onclick=addWatchdogActionUpdate()> ' + amtstack.WatchdogCurrentStates[i] + '<br />';
|
||
}
|
||
x += '</table><br>Perform Action<br><input id=wgsevent type=checkbox checked onclick=addWatchdogActionUpdate()>Write to event log<br />';
|
||
setDialogMode(11, 'Add Watchdog Action', 3, addWatchdogActionOk, x, w);
|
||
addWatchdogActionUpdate();
|
||
haltEvent(e);
|
||
}
|
||
|
||
function addWatchdogActionUpdate() {
|
||
var tfrom = 0, tto = 0;
|
||
for (var i in amtstack.WatchdogCurrentStates) {
|
||
if (Q('wgsa' + i).checked == true) tfrom += parseInt(i);
|
||
if (Q('wgsb' + i).checked == true) tto += parseInt(i);
|
||
}
|
||
QE('dlgOkButton', tfrom > 0 && tto > 0 && Q('wgsevent').checked == true);
|
||
}
|
||
|
||
function addWatchdogActionOk(b, w) {
|
||
var tfrom = 0, tto = 0;
|
||
for (var i in amtstack.WatchdogCurrentStates) {
|
||
if (Q('wgsa' + i).checked == true) tfrom += parseInt(i);
|
||
if (Q('wgsb' + i).checked == true) tto += parseInt(i);
|
||
}
|
||
amtstack.AMT_AgentPresenceWatchdog_AddAction(tfrom, tto, Q('wgsevent').checked, null, null, AddWatchdogActionResponse, null, 0, { 'DeviceID': w['DeviceID'] });
|
||
}
|
||
|
||
function AddWatchdogActionResponse(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Watchdog Action', "Unable to add watchdog action, error #" + status);
|
||
} else {
|
||
PullWatchdog();
|
||
}
|
||
}
|
||
|
||
function deleteWatchdogActions(e, i) {
|
||
var w = xxWatchdog['AMT_AgentPresenceWatchdog'].responses[i];
|
||
setDialogMode(11, 'Delete Watchdog Actions', 3, deleteWatchdogActionsOk, "Delete all actions for this watchdog?", w);
|
||
haltEvent(e);
|
||
}
|
||
|
||
function deleteWatchdogActionsOk(b, w) {
|
||
amtstack.AMT_AgentPresenceWatchdog_DeleteAllActions(deleteWatchdogActionResponse, null, 0, { 'DeviceID': w['DeviceID'] });
|
||
}
|
||
|
||
function deleteWatchdogActionResponse(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Watchdog Action', "Unable to remove watchdog actions, error #" + status);
|
||
} else {
|
||
PullWatchdog();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// SYSTEM DEFENSE PANEL
|
||
//
|
||
|
||
var xxSystemDefense = null;
|
||
var xxSystemDefenceLinkedPolicy = {};
|
||
var xxUpdatingDefenseStats = false;
|
||
var xxFilterStatistics = [{}, {}]; // Wired and wireless interface stats
|
||
var xxFilterStatisticsTimer = null;
|
||
var xxFilterStatisticsTimerActive = false;
|
||
|
||
var xxSystemDefenceFilterEthernetTypes = { 2048: 'All IP Packets', 2054: 'All ARP Packets' };
|
||
var xxSystemDefenceFilterIPTypes = { 4: 'IPv4', 6: 'IPv6' };
|
||
var xxSystemDefenceFilterDesc = { 0: 'Allow, Count', 1: 'Drop, Count', 2: 'Rate limit', 3: 'Allow', 4: 'Drop' };
|
||
var xxSystemDefenceFilters = { 'HdrProtocolID': 1, 'HdrDestAddress': 2, 'HdrDestMask': 2, 'HdrSrcAddress': 2, 'HdrSrcMask': 2, 'HdrSrcPortStart': 1, 'HdrSrcPortEnd': 1, 'HdrDestPortStart': 1, 'HdrDestPortEnd': 1, 'HdrSrcAddressEndOfRange': 2, 'HdrDestAddressEndOfRange ': 2, 'TCPFlagsOn': 3, 'TCPFlagsOff': 3 };
|
||
|
||
function PullSystemDefense() {
|
||
amtFirstPull |= 4;
|
||
amtstack.BatchEnum(null, ["AMT_SystemDefensePolicy", "AMT_NetworkPortSystemDefensePolicy", "AMT_Hdr8021Filter", "AMT_IPHeadersFilter", "AMT_NetworkFilter"], PullSystemDefenseResponse);
|
||
}
|
||
|
||
function PullSystemDefenseResponse(stack, name, responses, status) {
|
||
if (status == 200) {
|
||
xxSystemDefense = responses;
|
||
updateSystemDefense();
|
||
QV('go18', true); // Show System Defense Panel
|
||
}
|
||
}
|
||
|
||
function updateSystemDefense() {
|
||
if (xxSystemDefense == null) return;
|
||
var x = '';
|
||
|
||
// Get active policies
|
||
xxSystemDefenceLinkedPolicy = {};
|
||
for (var i in xxSystemDefense['AMT_NetworkPortSystemDefensePolicy'].responses) {
|
||
var link = xxSystemDefense['AMT_NetworkPortSystemDefensePolicy'].responses[i];
|
||
var a = +getItem(link['Antecedent']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'DeviceID')['Value'].substring(27);
|
||
var b = link['Dependent']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
|
||
var p = getItem(xxSystemDefense['AMT_SystemDefensePolicy'].responses, 'InstanceID', b);
|
||
xxSystemDefenceLinkedPolicy[a] = p;
|
||
}
|
||
|
||
// Global System Defense settings
|
||
x += TableStart();
|
||
var policyname = '<i>None</i>';
|
||
if (xxSystemDefenceLinkedPolicy[0]) { policyname = xxSystemDefenceLinkedPolicy[0]['PolicyName']; }
|
||
x += TableEntry("Default Wired Policy", addLink(policyname, 'changeDefaultPolicy(0)'));
|
||
/*
|
||
policyname = '<i>None</i>';
|
||
if (xxSystemDefenceLinkedPolicy[1]) { policyname = xxSystemDefenceLinkedPolicy[1]['PolicyName']; }
|
||
x += TableEntry("Default Wireless Policy", addLink(policyname, 'changeDefaultPolicy(1)'));
|
||
*/
|
||
|
||
// Filter Statistics
|
||
for (var i = 0; i < 2; i++) {
|
||
var f = xxFilterStatistics[i];
|
||
for (var j in f) {
|
||
var c = f[j];
|
||
if (c == 1) c += ' packet'; else c += ' packets';
|
||
var jn = ((i==0)?'Wired ':'Wireless ') + j;
|
||
x += TableEntry(jn, c);
|
||
}
|
||
}
|
||
x += TableEnd();
|
||
x += "<br>";
|
||
|
||
x += TableStart2();
|
||
x += "<tr><td class=r1 style=padding-left:15px;border:0><br>Manage Intel® AMT system defense policies.<br><br>";
|
||
if (xxSystemDefense['AMT_SystemDefensePolicy'].responses.length == 0) {
|
||
x += "<div style=padding-left:15px><i>No system defense policies found.</i></div><br>";
|
||
} else {
|
||
for (var i in xxSystemDefense['AMT_SystemDefensePolicy'].responses) {
|
||
var policy = xxSystemDefense['AMT_SystemDefensePolicy'].responses[i];
|
||
var desc = '';
|
||
if (policy['FilterCreationHandles']) {
|
||
policy['FilterCreationHandles'] = MakeToArray(policy['FilterCreationHandles']);
|
||
var c = policy['FilterCreationHandles'].length;
|
||
desc = ', ' + c + ' filter' + (c > 1?'s':'');
|
||
}
|
||
x += "<div class=itemBar onclick=showPolicyDetails(" + i + ")><div style=padding-top:3px><b>" + EscapeHtml(policy['PolicyName']) + "</b>" + desc + "</div></div>";
|
||
}
|
||
}
|
||
|
||
x += "<tr><td class=r1 style=padding-left:15px><br>Manage Intel® AMT system defense filters.<br><br>";
|
||
if (xxSystemDefense['AMT_Hdr8021Filter'].responses.length == 0 && xxSystemDefense['AMT_IPHeadersFilter'].responses.length == 0) {
|
||
x += "<div style=padding-left:15px><i>No system defense filters found.</i></div><br>";
|
||
} else {
|
||
for (var i in xxSystemDefense['AMT_Hdr8021Filter'].responses) {
|
||
var filter = xxSystemDefense['AMT_Hdr8021Filter'].responses[i];
|
||
var desc = xxSystemDefenceFilterEthernetTypes[filter['HdrProtocolID8021']];
|
||
if (!desc) desc = 'All Ethernet Protocol ' + filter['HdrProtocolID8021'];
|
||
desc += ', ' + xxSystemDefenceFilterDesc[filter['FilterProfile']];
|
||
if (filter['FilterProfile'] == 2) { desc += " at " + filter['FilterProfileData'] + " packet / sec"; }
|
||
if (filter['ActionEventOnMatch'] == true) desc += ', Event on match';
|
||
x += "<div class=itemBar onclick=showFilterDetails(0," + i + ")><div style=padding-top:3px><b>" + (filter['FilterDirection'] == 0 ? '← ' : '→ ') + EscapeHtml(filter["Name"]) + "</b>, " + desc + "</div></div>";
|
||
}
|
||
for (var i in xxSystemDefense['AMT_IPHeadersFilter'].responses) {
|
||
var filter = xxSystemDefense['AMT_IPHeadersFilter'].responses[i];
|
||
var desc = xxSystemDefenceFilterIPTypes[filter['HdrIPVersion']];
|
||
if (!desc) desc = 'All Ethernet Protocol ' + filter['HdrIPVersion'];
|
||
desc += ', ' + xxSystemDefenceFilterDesc[filter['FilterProfile']];
|
||
if (filter['FilterProfile'] == 2) { desc += " at " + filter['FilterProfileData'] + " packet / sec"; }
|
||
if (filter['ActionEventOnMatch'] == true) desc += ', Event on match';
|
||
var filtercount = 0;
|
||
for (var j in xxSystemDefenceFilters) { if (filter[j]) filtercount++; }
|
||
if (filtercount > 0) desc += ', ' + filtercount + ' filter' + (filtercount>1?'s':'');
|
||
x += "<div class=itemBar onclick=showFilterDetails(1," + i + ")><div style=padding-top:3px><b>" + (filter['FilterDirection'] == 0 ? '← ' : '→ ') + EscapeHtml(filter["Name"]) + "</b>, " + desc + "</div></div>";
|
||
}
|
||
}
|
||
x += "<br><td class=r1>" + TableEnd(AddRefreshButton("PullSystemDefense()") + /*AddButton("Update Stats", "UpdateDefenseStats()") +*/ AddButton("Add Filter...", "AddDefenseFilter()") + AddButton("Add Policy...", "AddDefensePolicy()"));
|
||
|
||
QH('id_TableSystemDefense', x);
|
||
|
||
if (xxFilterStatisticsTimer == null) { UpdateDefenseStats(); xxFilterStatisticsTimerActive = false; xxFilterStatisticsTimer = setInterval(UpdateDefenseStats, 5000); }
|
||
}
|
||
|
||
function StopDefenseStatsTimer() {
|
||
if (xxFilterStatisticsTimer != null) { clearInterval(xxFilterStatisticsTimer); xxFilterStatisticsTimer = null; }
|
||
xxFilterStatisticsTimerActive = false;
|
||
}
|
||
|
||
function UpdateDefenseStats(n) {
|
||
if (!n && xxFilterStatisticsTimerActive == true) { return; }
|
||
xxFilterStatisticsTimerActive = true;
|
||
var network = n?n:0;
|
||
if (xxSystemDefenceLinkedPolicy[network]) {
|
||
var eref = '<a:Address></a:Address><a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_EthernetPort</w:ResourceURI><w:SelectorSet><w:Selector Name="DeviceID">Intel(r) AMT Ethernet Port ' + network + '</w:Selector></w:SelectorSet></a:ReferenceParameters>';
|
||
amtstack.AMT_SystemDefensePolicy_UpdateStatistics(eref, false, UpdateDefenseStats2, network, 0, { 'InstanceID': xxSystemDefenceLinkedPolicy[network]['InstanceID'] });
|
||
} else {
|
||
// No active policy, stop the timer
|
||
xxFilterStatistics[network] = {};
|
||
updateSystemDefense();
|
||
StopDefenseStatsTimer();
|
||
}
|
||
}
|
||
|
||
function UpdateDefenseStats2(stack, name, responses, status, network) {
|
||
if (status == 200) {
|
||
amtstack.Enum('AMT_ActiveFilterStatistics', UpdateDefenseStats3, network);
|
||
} else {
|
||
StopDefenseStatsTimer();
|
||
}
|
||
}
|
||
|
||
function UpdateDefenseStats3(stack, name, responses, status, network) {
|
||
var count = 0;
|
||
if (status == 200) {
|
||
xxFilterStatistics[network] = {};
|
||
for (var i in responses) {
|
||
var readcount = responses[i]['ReadCount'];
|
||
// var matched = responses[i]['FilterMatched'];
|
||
var name = getItem(responses[i]['Dependent']['ReferenceParameters']['SelectorSet']['Selector'][1]['Value']['EndpointReference']['ReferenceParameters']['SelectorSet']['Selector'], '@Name', 'Name')['Value'];
|
||
xxFilterStatistics[network][name] = readcount;
|
||
count++;
|
||
}
|
||
updateSystemDefense();
|
||
}
|
||
xxFilterStatisticsTimerActive = false;
|
||
// If there is no statistics, stop the timer
|
||
if (count == 0) { StopDefenseStatsTimer(); } // TODO: Change this to allow for wireless
|
||
}
|
||
|
||
function changeDefaultPolicy(network) {
|
||
if (xxdialogMode) return;
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><select id=policySelection style=float:right;width:266px><option value=-1>None";
|
||
for (var i in xxSystemDefense['AMT_SystemDefensePolicy'].responses) { x += "<option value=" + i + ((xxSystemDefenceLinkedPolicy[network] && xxSystemDefense['AMT_SystemDefensePolicy'].responses[i]['InstanceID'] == xxSystemDefenceLinkedPolicy[network]['InstanceID']) ? ' selected' : '') + ">" + xxSystemDefense['AMT_SystemDefensePolicy'].responses[i]['PolicyName']; }
|
||
x += "</select><div style=padding-top:4px>Default Policy</div></div>";
|
||
setDialogMode(11, 'Default System Defense Policy', 3, changeDefaultPolicyOk, x, network);
|
||
}
|
||
|
||
function changeDefaultPolicyOk(button, network) {
|
||
var i = Q('policySelection').value;
|
||
var oldpolicy = xxSystemDefenceLinkedPolicy[network];
|
||
if (oldpolicy) { amtstack.Delete('AMT_NetworkPortSystemDefensePolicy', '<w:SelectorSet><w:Selector Name="Antecedent"><a:EndpointReference xmlns:b="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:c="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address><a:ReferenceParameters><w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_EthernetPort</w:ResourceURI><w:SelectorSet><w:Selector Name="CreationClassName">CIM_EthernetPort</w:Selector><w:Selector Name="DeviceID">Intel(r) AMT Ethernet Port ' + network + '</w:Selector></w:SelectorSet></a:ReferenceParameters></a:EndpointReference></w:Selector><w:Selector Name="Dependent"><a:EndpointReference xmlns:b="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:c="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address><a:ReferenceParameters><w:ResourceURI>http://intel.com/wbem/wscim/1/amt-schema/1/AMT_SystemDefensePolicy</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + oldpolicy['InstanceID'] + '</w:Selector></w:SelectorSet></a:ReferenceParameters></a:EndpointReference></w:Selector></w:SelectorSet>', ((i == -1) ? PullSystemDefense : function () { })); }
|
||
|
||
if (i >= 0) {
|
||
var policy = xxSystemDefense['AMT_SystemDefensePolicy'].responses[i];
|
||
var eref = '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_EthernetPort</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="DeviceID">Intel(r) AMT Ethernet Port ' + network + '</Selector></SelectorSet></ReferenceParameters>';
|
||
var pref = '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://intel.com/wbem/wscim/1/amt-schema/1/AMT_SystemDefensePolicy</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">' + policy['InstanceID'] + '</Selector></SelectorSet></ReferenceParameters>';
|
||
amtstack.Create('AMT_NetworkPortSystemDefensePolicy', { 'Antecedent': eref, 'Dependent': pref }, changeDefaultPolicyOk2);
|
||
}
|
||
}
|
||
|
||
function changeDefaultPolicyOk2(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Default System Defense Policy', "Unable to set policy, error " + status);
|
||
} else {
|
||
PullSystemDefense();
|
||
}
|
||
}
|
||
|
||
function AddDefenseFilter() {
|
||
if (xxdialogMode) return;
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><input id=filtername style=float:right;width:260px maxlength=16 onkeyup=AddDefenseFilterUpdate()><div style=padding-top:4px>Name</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=filtertype style=float:right;width:266px onchange=AddDefenseFilterUpdate()><option value=0>Ethernet IP Packet Filter<option value=1>Ethernet ARP Packet Filter<option value=2>IPv4 Packet Filter<option value=3>IPv6 Packet Filter</select><div style=padding-top:4px>Type</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px id=ipfilterdiv><input id=ipfilter style=float:right;width:260px placeholder=\"Optional Rules\" onkeyup=AddDefenseFilterUpdate()><div style=padding-top:4px>Matching Rules</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=filterdir style=float:right;width:266px onchange=AddDefenseFilterUpdate()><option value=0>Outbound / Transmit<option value=1>Inbound / Receive</select><div style=padding-top:4px>Direction</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=filterprofile style=float:right;width:266px onchange=AddDefenseFilterUpdate()><option value=0>Allow, Count<option value=1>Drop, Count<option value=2>Rate Limit<option value=3>Allow<option value=4>Drop</select><div style=padding-top:4px>Action</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px id=filterdatadiv><input id=filterdata style=float:right;width:260px maxlength=8 onkeyup=AddDefenseFilterUpdate()><div style=padding-top:4px>Packets / second</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=filteraction style=float:right;width:266px onchange=AddDefenseFilterUpdate()><option value=false>Do Nothing<option value=1>Event on match</select><div style=padding-top:4px>Event Log</div></div>";
|
||
setDialogMode(11, 'Add System Defense Filter', 3, AddDefenseFilterOk, x);
|
||
AddDefenseFilterUpdate();
|
||
}
|
||
|
||
function AddDefenseFilterOk() {
|
||
if (Q('filtertype').value <= 1) {
|
||
// Add Ethernet IP or ARP filter
|
||
var protocol = (Q('filtertype').value == 0) ? 2048 : 2054; // IP : ARP
|
||
var filter = { 'InstanceID ': 0, 'Name': Q('filtername').value, 'CreationClassName': 0, 'SystemName': 0, 'SystemCreationClassName': 0, 'HdrProtocolID8021': protocol, 'FilterProfile': Q('filterprofile').value, 'FilterDirection': Q('filterdir').value, 'ActionEventOnMatch': Q('filteraction').value };
|
||
if (Q('filterprofile').value == 2) filter['FilterProfileData'] = Q('filterdata').value;
|
||
amtstack.Create('AMT_Hdr8021Filter', filter, AddDefenseFilterOk2);
|
||
} else {
|
||
// Add IPv4 or IPv6 Filter
|
||
var protocol = (Q('filtertype').value == 2) ? 4 : 6; // IPv4 : IPv6
|
||
var filter = { 'InstanceID ': 0, 'Name': Q('filtername').value, 'CreationClassName': 0, 'SystemName': 0, 'SystemCreationClassName': 0, 'HdrIPVersion': protocol, 'FilterProfile': Q('filterprofile').value, 'FilterDirection': Q('filterdir').value, 'ActionEventOnMatch': Q('filteraction').value };
|
||
|
||
// Parse the filter string
|
||
// https://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide/default.htm?turl=HTMLDocuments%2FWS-Management_Class_Reference%2FAMT_Hdr8021Filter.htm
|
||
var filterstrs = Q('ipfilter').value.split(',');
|
||
for (var i in filterstrs) {
|
||
var xx = filterstrs[i].indexOf('=');
|
||
var n = filterstrs[i].substring(0, xx);
|
||
var v = filterstrs[i].substring(xx + 1);
|
||
var t = xxSystemDefenceFilters[n];
|
||
if (!t) { n = "Hdr" + n; t = xxSystemDefenceFilters[n]; }
|
||
if (t) {
|
||
// TODO: Parse v (the value) based on the type t.
|
||
if (t == 2 && protocol == 4) {
|
||
// IPv4 address
|
||
var ipsplit = v.split('.');
|
||
if (ipsplit.length == 4) {
|
||
//filter[n] = btoa(String.fromCharCode(parseInt(ipsplit[0]), parseInt(ipsplit[1]), parseInt(ipsplit[2]), parseInt(ipsplit[3])));
|
||
//filter[n] = [ parseInt(ipsplit[0]), parseInt(ipsplit[1]), parseInt(ipsplit[2]), parseInt(ipsplit[3]) ];
|
||
filter[n] = rstr2hex(String.fromCharCode(parseInt(ipsplit[0]), parseInt(ipsplit[1]), parseInt(ipsplit[2]), parseInt(ipsplit[3])));
|
||
}
|
||
} else {
|
||
// Other values
|
||
filter[n] = v;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (Q('filterprofile').value == 2) filter['FilterProfileData'] = Q('filterdata').value;
|
||
amtstack.Create('AMT_IPHeadersFilter', filter, AddDefenseFilterOk2);
|
||
}
|
||
}
|
||
|
||
function AddDefenseFilterUpdate() {
|
||
var ok = Q('filtername').value.length > 0;
|
||
if (ok && Q('filterprofile').value == 2) { var i = parseInt(Q('filterdata').value); ok = (i > 0 && i < 0xFFFFFFFF); }
|
||
QE('idx_dlgOkButton', ok);
|
||
QV('filterdatadiv', Q('filterprofile').value == 2);
|
||
QV('ipfilterdiv', Q('filtertype').value >= 2);
|
||
}
|
||
|
||
function AddDefenseFilterOk2(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Add System Defense Filter', "Unable to add filter, error #" + status);
|
||
} else {
|
||
PullSystemDefense();
|
||
}
|
||
}
|
||
|
||
function showFilterDetails(t, i) {
|
||
if (xxdialogMode) return;
|
||
var desc, filter, type, tn;
|
||
if (t == 0) {
|
||
tn = 'AMT_Hdr8021Filter';
|
||
type = 'Ethernet Traffic';
|
||
filter = xxSystemDefense[tn].responses[i];
|
||
desc = xxSystemDefenceFilterEthernetTypes[filter['HdrProtocolID8021']];
|
||
if (!desc) desc = 'All Ethernet Protocol ' + filter['HdrProtocolID8021'];
|
||
} else {
|
||
tn = 'AMT_IPHeadersFilter';
|
||
type = 'IP Traffic';
|
||
filter = xxSystemDefense[tn].responses[i];
|
||
desc = xxSystemDefenceFilterIPTypes[filter['HdrIPVersion']];
|
||
if (!desc) desc = 'All IP Protocol ' + filter['HdrIPVersion'];
|
||
}
|
||
var x = '';
|
||
x += addHtmlValue('Name', EscapeHtml(filter["Name"]));
|
||
x += addHtmlValue('Type', type);
|
||
x += addHtmlValue('Matching Traffic', desc);
|
||
x += addHtmlValue('Direction', filter['FilterDirection'] == 0 ? 'Outbound / Transmit' : 'Inbound / Receive');
|
||
if (t == 1) {
|
||
for (var j in xxSystemDefenceFilters) {
|
||
if (filter[j]) {
|
||
var n = j, v = filter[j], t = xxSystemDefenceFilters[j];
|
||
if (t == 2 && v.length == 4) { v = hex2rstr(v); v = v.charCodeAt(0) + '.' + v.charCodeAt(1) + '.' + v.charCodeAt(2) + '.' + v.charCodeAt(3); }
|
||
if (n.startsWith('Hdr')) n = n.substring(3);
|
||
x += addHtmlValue('Filter ' + n, v);
|
||
}
|
||
}
|
||
}
|
||
x += addHtmlValue('Event on match', (filter['ActionEventOnMatch'] == true) ? 'Yes' : 'No');
|
||
setDialogMode(11, 'Ethernet Filter #' + filter['InstanceID'], 5, showFilterDetailsOk, x, [tn, filter]);
|
||
}
|
||
|
||
function showFilterDetailsOk(b, tag) {
|
||
if (b == 2) { amtstack.Delete(tag[0], tag[1], deleteDefenseFilter); }
|
||
}
|
||
|
||
function deleteDefenseFilter(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Remove Filter', "Unable to remove filter, make sure it's not in use.");
|
||
} else {
|
||
PullSystemDefense();
|
||
}
|
||
}
|
||
|
||
var xxAddDefensePolicyFilters;
|
||
function AddDefensePolicy() {
|
||
if (xxdialogMode) return;
|
||
xxAddDefensePolicyFilters = [];
|
||
var x = '';
|
||
x += "<div style=height:26px;margin-top:4px><input id=policyname title='<policy name>:<policy precedence number>' style=float:right;width:260px maxlength=16 onkeyup=AddDefensePolicyUpdate()><div style=padding-top:4px>Name</div></div>";
|
||
x += "<div style=height:26px;margin-top:4px><select id=policytx title='Default action to take for outbound traffic' style=float:right;width:133px><option value=0>Allow<option value=1>Drop<option value=2>Allow,Count<option value=3>Drop,Count<option value=4>Allow,Count,Event<option value=5>Drop,Count,Event</select><select id=policyrx style=float:right;width:133px title='Default action to take for inbound traffic'><option value=0>Allow<option value=1>Drop<option value=2>Allow,Count<option value=3>Drop,Count<option value=4>Allow,Count,Event<option value=5>Drop,Count,Event</select><div style=padding-top:4px>Default TX / RX</div></div>";
|
||
x += "<div id=policyFilters></div>";
|
||
if (xxSystemDefense['AMT_Hdr8021Filter'].responses.length > 0 || xxSystemDefense['AMT_IPHeadersFilter'].responses.length > 0) {
|
||
x += "<div style=height:26px;margin-top:4px><div style=float:right><select id=xfilter style=width:186px>";
|
||
for (var i in xxSystemDefense['AMT_Hdr8021Filter'].responses) {
|
||
var filter = xxSystemDefense['AMT_Hdr8021Filter'].responses[i];
|
||
x += "<option value=" + filter["InstanceID"] + ">" + filter["Name"];
|
||
}
|
||
for (var i in xxSystemDefense['AMT_IPHeadersFilter'].responses) {
|
||
var filter = xxSystemDefense['AMT_IPHeadersFilter'].responses[i];
|
||
x += "<option value=" + filter["InstanceID"] + ">" + filter["Name"];
|
||
}
|
||
x += "</select><input id=addFilterButton type=button value=Add style=width:80px onclick=addFilterButton()></div><div style=padding-top:4px>Add Filter</div></div>";
|
||
}
|
||
setDialogMode(11, 'Add System Defense Policy', 3, AddDefensePolicyOk, x);
|
||
AddDefensePolicyUpdate();
|
||
}
|
||
|
||
function addFilterButton() {
|
||
if (xxAddDefensePolicyFilters.indexOf(Q('xfilter').value) >= 0) return;
|
||
xxAddDefensePolicyFilters.push(Q('xfilter').value);
|
||
AddDefensePolicyUpdate();
|
||
}
|
||
|
||
function removeFilterButton(h) {
|
||
xxAddDefensePolicyFilters.splice(h, 1);
|
||
AddDefensePolicyUpdate();
|
||
}
|
||
|
||
function AddDefensePolicyUpdate() {
|
||
var ok = Q('policyname').value.split(':')[0].length > 0;
|
||
QE('idx_dlgOkButton', ok);
|
||
if (xxAddDefensePolicyFilters.length == 0) {
|
||
QH('policyFilters', '<br><i>This policy contains no filters.</i><br><br>');
|
||
} else {
|
||
var x = '';
|
||
for (var i in xxAddDefensePolicyFilters) {
|
||
x += "<div class=itemBar style=margin-right:0><div style=float:right>" + AddButton2("Remove", "removeFilterButton(" + i + ")") + "</div><div style=padding-top:3px;max-width:260px;overflow:hidden><b>" + GetFilterById(xxAddDefensePolicyFilters[i])['Name'] + "</b></div></div>";
|
||
}
|
||
QH('policyFilters', x);
|
||
}
|
||
}
|
||
|
||
// TODO: Once loaded, we could make a table of "handle -> filter" where both filter types are mixed-in. Could make things simpler/smaller?
|
||
// We would only have one table to loop instead of two at a few places. This function would be removed completely.
|
||
function GetFilterById(h) {
|
||
for (var i in xxSystemDefense['AMT_Hdr8021Filter'].responses) { var filter = xxSystemDefense['AMT_Hdr8021Filter'].responses[i]; if (filter["InstanceID"] == h) return filter; }
|
||
for (var i in xxSystemDefense['AMT_IPHeadersFilter'].responses) { var filter = xxSystemDefense['AMT_IPHeadersFilter'].responses[i]; if (filter["InstanceID"] == h) return filter; }
|
||
}
|
||
|
||
function AddDefensePolicyOk() {
|
||
var tx = Q('policytx').value;
|
||
var rx = Q('policyrx').value;
|
||
var pri = 0;
|
||
var n = Q('policyname').value.split(':');
|
||
if (n.length == 2) pri = parseInt(n[1]);
|
||
var filter = { 'InstanceID ': 0, 'PolicyName': n[0], 'PolicyPrecedence': pri, 'TxDefaultCount': (tx > 1), 'TxDefaultDrop': (tx % 2 == 1), 'TxDefaultMatchEvent': (tx > 3), 'RxDefaultCount': (rx > 1), 'RxDefaultDrop': (rx % 2 == 1), 'RxDefaultMatchEvent': (rx > 3) };
|
||
if (xxAddDefensePolicyFilters.length > 0) filter['FilterCreationHandles'] = xxAddDefensePolicyFilters;
|
||
amtstack.Create('AMT_SystemDefensePolicy', filter, AddDefensePolicyOk2);
|
||
}
|
||
|
||
function AddDefensePolicyOk2(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Add System Defense Policy', "Unable to add policy, error #" + status);
|
||
} else {
|
||
PullSystemDefense();
|
||
}
|
||
}
|
||
|
||
function showPolicyDetails(i) {
|
||
if (xxdialogMode) return;
|
||
var policy = xxSystemDefense['AMT_SystemDefensePolicy'].responses[i];
|
||
var x = '';
|
||
x += addHtmlValue('Name', EscapeHtml(policy["PolicyName"]));
|
||
if (policy["PolicyPrecedence"] != 0) x += addHtmlValue('Precedence', policy["PolicyPrecedence"]);
|
||
|
||
var action = (policy["TxDefaultDrop"] == true) ? 'Drop' : 'Allow';
|
||
if (policy["TxDefaultCount"] == true) action += ', Count';
|
||
if (policy["TxDefaultMatchEvent"] == true) action += ', Event';
|
||
x += addHtmlValue('Default TX Action', action);
|
||
|
||
action = (policy["RxDefaultDrop"] == true) ? 'Drop' : 'Allow';
|
||
if (policy["RxDefaultCount"] == true) action += ', Count';
|
||
if (policy["RxDefaultMatchEvent"] == true) action += ', Event';
|
||
x += addHtmlValue('Default RX Action', action);
|
||
|
||
if (policy['FilterCreationHandles']) { for (var i in policy['FilterCreationHandles']) { x += addHtmlValue('Filter #' + (+i + 1), GetFilterById(policy['FilterCreationHandles'][i])['Name']); } }
|
||
|
||
setDialogMode(11, 'Policy #' + policy['InstanceID'].substring(20), 5, showPolicyDetailsOk, x, policy);
|
||
}
|
||
|
||
function showPolicyDetailsOk(b, tag) {
|
||
if (b == 2) { amtstack.Delete('AMT_SystemDefensePolicy', tag, deleteDefensePolicy); }
|
||
}
|
||
|
||
function deleteDefensePolicy(stack, name, responses, status) {
|
||
if (status != 200) {
|
||
messagebox('Remove Policy', "Unable to remove policy, make sure it's not in use.");
|
||
} else {
|
||
PullSystemDefense();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
//
|
||
// WIRELESS SETTINGS PANEL
|
||
//
|
||
|
||
var xxWireless;
|
||
function PullWireless() {
|
||
amtFirstPull |= 2;
|
||
try { if (amtwirelessif == -1) return; } catch (e) {} // If the wireless adapter has no MAC, don't show this.
|
||
amtstack.BatchEnum("", ["*CIM_WiFiPortCapabilities", "*CIM_WiFiPort", "*CIM_WiFiEndpoint", "CIM_WiFiEndpointSettings"], processWireless);
|
||
}
|
||
|
||
function wifiRefresh() { if (!xxdialogMode) PullWireless(); }
|
||
|
||
var xxWifiState = { 3: 'Disabled', 32768: 'Enabled in S0', 32769: 'Enabled in S0, Sx/AC' };
|
||
var xxRadioState = { 2: 'On, Connected', 3: 'Off', 6: 'On, Disconnected' };
|
||
var xxWifiAuthenticationMethod= { 1: 'Other', 2: 'Open', 3: 'Shared Key', 4: 'WPA PSK', 5: 'WPA IEEE 802.1x', 6: 'WPA2 PSK', 7: 'WPA2 IEEE 802.1x' };
|
||
var xxWifiEncryptionMethod = { 1: 'Other', 2: 'WEP', 3: 'TKIP-RC4', 4: 'CCMP-AES', 5: 'None' }; // For 2 & 5, AuthenticationMethod must be 2 or 3. For 3 & 4 AuthenticationMethod must be 4,5,6 or 7
|
||
|
||
function processWireless(stack, name, responses, status) {
|
||
if (status == 200) { xxWireless = responses; } else { xxWireless = undefined; }
|
||
updateSystemStatus();
|
||
showWirelessInfo();
|
||
}
|
||
|
||
function showWirelessInfo() {
|
||
if (!xxWireless) return;
|
||
var i, j, s = "", sc, x = "<br><h2>Wireless Profiles</h2>";
|
||
if (!(xxWireless['CIM_WiFiPortCapabilities'].response)) return;
|
||
|
||
// Show WIFI profiles sorted by priority
|
||
x += TableStart2();
|
||
x += "<tr><td class=r2 style=padding-left:15px><br>Wireless profiles that Intel® AMT will use for network connectivity.<br><br>";
|
||
s = 0;
|
||
for(i = 0; i < 256; i++) {
|
||
for (j in xxWireless['CIM_WiFiEndpointSettings'].responses) {
|
||
sc = xxWireless['CIM_WiFiEndpointSettings'].responses[j];
|
||
if (sc['AuthenticationMethod'] == 1) continue; // Skip "Endpoint User Settings"
|
||
if (sc['Priority'] == i) {
|
||
x += "<div class=itemBar onclick=showWifiDetails(" + j + ")><div style=float:right>" + EscapeHtml(sc['SSID']) + ", " + xxWifiAuthenticationMethod[sc['AuthenticationMethod']] + ", " + xxWifiEncryptionMethod[sc['EncryptionMethod']] + " ";
|
||
if (xxAccountAdminName) x += AddButton2("Remove", "wifiRemoveButton(\"" + j + "\")");
|
||
x += "</div><div style=padding-top:3px><b>" + EscapeHtml(sc['ElementName']) + "</b></div></div>";
|
||
s++;
|
||
}
|
||
}
|
||
}
|
||
if (s == 0) x += '<i>No Wireless Profiles Present</i><br>';
|
||
|
||
// End of table
|
||
x += "<br><td class=r2>";
|
||
if (xxAccountAdminName) x += TableEnd(AddButton("New Profile", "showWifiNewProfile()")); else x += TableEnd('');
|
||
QH('id_TableWifi2', x + "<br>");
|
||
}
|
||
|
||
function showWifiStateDlg() {
|
||
if (xxdialogMode) return;
|
||
var s = "";
|
||
for (var i in xxWifiState) { s += '<input type=radio name=d11 id=wl' + i + ' value=' + i + ' ' + ((xxWireless['CIM_WiFiPort'].response["EnabledState"] == i)?"checked":"") + '>' + xxWifiState[i] + '<br>'; }
|
||
setDialogMode(11, 'Wireless State', 3, wifiStateDlg, s);
|
||
}
|
||
|
||
function wifiStateDlg() { amtstack.CIM_WiFiPort_RequestStateChange( document.querySelector('input[name=d11]:checked').value, null, function() { amtstack.Get("CIM_WiFiPort", function(stack, name, response, status) { if (status == 200) { xxWireless['CIM_WiFiPort'].response = response.Body; showWirelessInfo(); } } ); } ); }
|
||
|
||
function showWifiDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var i, sc = xxWireless['CIM_WiFiEndpointSettings'].responses[h], x = '<div style=text-align:left>';
|
||
x += addHtmlValue("Profile Name", EscapeHtml(sc['ElementName']));
|
||
x += addHtmlValue("SSID", sc['SSID']);
|
||
x += addHtmlValue("Authentication", xxWifiAuthenticationMethod[sc['AuthenticationMethod']]);
|
||
x += addHtmlValue("Encryption", xxWifiEncryptionMethod[sc['EncryptionMethod']]);
|
||
x += addHtmlValue("Priority", sc['Priority']);
|
||
x += "</div>";
|
||
messagebox("Wireless Profile", x);
|
||
}
|
||
|
||
function wifiRemoveButton(h) {
|
||
if (xxdialogMode) return;
|
||
var sc = xxWireless['CIM_WiFiEndpointSettings'].responses[h];
|
||
QH('id_dialogMessage', 'Remove wireless profile \"' + sc["ElementName"] + '\"?');
|
||
setDialogMode(1, "Wireless Profile", 3, function () { removeWifiButtonEx(h) });
|
||
}
|
||
|
||
function removeWifiButtonEx(h) {
|
||
var sc = xxWireless['CIM_WiFiEndpointSettings'].responses[h];
|
||
amtstack.Delete('CIM_WiFiEndpointSettings', { InstanceID: sc['InstanceID'] }, removeWifiEntryResponse, 0, 1);
|
||
}
|
||
|
||
function removeWifiEntryResponse(stack, name, response, status, tag) {
|
||
if (methodcheck(response)) return;
|
||
amtstack.Enum("CIM_WiFiEndpointSettings", function(stack, name, responses, status) { if (status == 200) { xxWireless['CIM_WiFiEndpointSettings'].responses = responses; showWirelessInfo(); } } );
|
||
}
|
||
|
||
function showWifiNewProfile() {
|
||
if (xxdialogMode) return;
|
||
var x = '';
|
||
for (i = 1; i < 256; i++) {
|
||
var t = 1;
|
||
for (j in xxWireless['CIM_WiFiEndpointSettings'].responses) { if (xxWireless['CIM_WiFiEndpointSettings'].responses[j]['Priority'] == i) t = 0; }
|
||
if (t) { x += '<option value='+ i +'>'+ i; } // Option is a tag that is self closing.
|
||
}
|
||
QH('idx_d12pri', x);
|
||
|
||
idx_d12auth.value = 6;
|
||
idx_d12enc.value = 4;
|
||
|
||
idx_d12name.value = idx_d12ssid.value = idx_d12password1.value = idx_d12password2.value = '';
|
||
|
||
setDialogMode(12, "Add Wireless Profile", 3, function () { addWifiProfile() });
|
||
updateWifiDialog();
|
||
}
|
||
|
||
function addWifiProfile() {
|
||
amtstack.AMT_WiFiPortConfigurationService_AddWiFiSettings(
|
||
{
|
||
'__parameterType': 'reference',
|
||
'__resourceUri': amtstack.CompleteName('CIM_WiFiEndpoint'),
|
||
'Name': 'WiFi Endpoint 0'
|
||
}, {
|
||
'__parameterType': 'instance',
|
||
'__namespace': amtstack.CompleteName('CIM_WiFiEndpointSettings'),
|
||
'ElementName': idx_d12name.value,
|
||
'InstanceID': 'Intel(r) AMT:WiFi Endpoint Settings ' + idx_d12name.value,
|
||
'AuthenticationMethod': idx_d12auth.value,
|
||
'EncryptionMethod': idx_d12enc.value,
|
||
'SSID': idx_d12ssid.value,
|
||
'Priority': idx_d12pri.value,
|
||
'PSKPassPhrase': idx_d12password1.value
|
||
},
|
||
null, null, null, removeWifiEntryResponse
|
||
);
|
||
}
|
||
|
||
function updateWifiDialog() {
|
||
var r = true;
|
||
var a = idx_d12auth.value;
|
||
var e = idx_d12enc.value;
|
||
QV('id_d12e2', a < 4);
|
||
QV('id_d12e3', a > 3);
|
||
QV('id_d12e4', a > 3);
|
||
QV('id_d12e5', a < 4);
|
||
if (a < 4 && (e == 3 || e == 4)) { idx_d12enc.value = 2; }
|
||
if (a > 3 && (e == 2 || e == 5)) { idx_d12enc.value = 3; }
|
||
|
||
// Check if there is already a profile with this name
|
||
for (var j in xxWireless['CIM_WiFiEndpointSettings'].responses) { if (xxWireless['CIM_WiFiEndpointSettings'].responses[j]['ElementName'] == idx_d12name.value) { r = false; } }
|
||
|
||
QE('idx_dlgOkButton', r == true && (idx_d12name.value.length > 0) && (idx_d12ssid.value.length > 0) && (idx_d12password1.value.length > 7) && (idx_d12password1.value == idx_d12password2.value));
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// HARDWARE INFORMATION PANEL
|
||
//
|
||
|
||
function PullHardware() {
|
||
amtstack.BatchEnum("", ["*CIM_ComputerSystemPackage", "CIM_SystemPackaging", "*CIM_Chassis", "CIM_Chip", "*CIM_Card", "*CIM_BIOSElement", "CIM_Processor", "CIM_PhysicalMemory", "CIM_MediaAccessDevice", "CIM_PhysicalPackage"], processHardware);
|
||
amtFirstPull |= 1; // Set the hardware info bit on, indicates we are pulling this information.
|
||
}
|
||
|
||
// http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
|
||
var DMTFCPUStatus = ["Unknown", "Enabled", "Disabled by User", "Disabled By BIOS (POST Error)", "Idle", "Other"];
|
||
var DMTFMemType = ["Unknown", "Other", "DRAM", "Synchronous DRAM", "Cache DRAM", "EDO", "EDRAM", "VRAM", "SRAM", "RAM", "ROM", "Flash", "EEPROM", "FEPROM", "EPROM", "CDRAM", "3DRAM", "SDRAM", "SGRAM", "RDRAM", "DDR", "DDR-2", "BRAM", "FB-DIMM", "DDR3", "FBD2", "DDR4", "LPDDR", "LPDDR2", "LPDDR3", "LPDDR4"];
|
||
var DMTFMemFormFactor = ['', 'Other','Unknown','SIMM','SIP','Chip','DIP','ZIP','Proprietary Card','DIMM','TSOP','Row of chips','RIMM','SODIMM','SRIMM','FB-DIM'];
|
||
var DMTFProcFamilly = { // Page 46 of DMTF document
|
||
191: 'Intel® Core™ 2 Duo Processor',
|
||
192: 'Intel® Core™ 2 Solo processor',
|
||
193: 'Intel® Core™ 2 Extreme processor',
|
||
194: 'Intel® Core™ 2 Quad processor',
|
||
195: 'Intel® Core™ 2 Extreme mobile processor',
|
||
196: 'Intel® Core™ 2 Duo mobile processor',
|
||
197: 'Intel® Core™ 2 Solo mobile processor',
|
||
198: 'Intel® Core™ i7 processor',
|
||
199: 'Dual-Core Intel® Celeron® processor' };
|
||
|
||
var HardwareInventory;
|
||
function processHardware(stack, name, responses, status) {
|
||
if (status != 200) return;
|
||
|
||
var i, x = "<table class=log1 cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px>";
|
||
HardwareInventory = responses;
|
||
QV('go2', true); // Show "Hardware Information" on left panel
|
||
|
||
|
||
x += TableEnd("<div> " + AddRefreshButton("PullHardware(1)") + AddButton("Save...", "SaveHardwareLog()") + ' Hardware information is gathered at system boot time.');
|
||
|
||
var ba = responses['CIM_Chassis'].response;
|
||
var bb = responses['CIM_Card'].response;
|
||
var v = responses['CIM_BIOSElement'].response["SoftwareElementID"];
|
||
|
||
x += "<br><h2>Platform</h2>";
|
||
x += FullTable({"Computer model":ba["Model"], "Manufacturer":ba["Manufacturer"], "Version":ba["Version"], "Serial number":ba["SerialNumber"], "System ID":guidToStr(responses['CIM_SystemPackaging'].responses[0]["PlatformGUID"]).toLowerCase()}, '');
|
||
|
||
x += "<br><h2>Baseboard</h2>";
|
||
x += FullTable({ "Manufacturer":bb["Manufacturer"], "Product name":bb["Model"], "Version":bb["Version"], "Serial number":bb["SerialNumber"], "Asset tag":bb["Tag"], "Replaceable?":(bb["CanBeFRUed"]==true)?'Yes':'No' },'');
|
||
|
||
x += "<br><h2>BIOS</h2>";
|
||
x += FullTable({"Vendor":responses['CIM_BIOSElement'].response["Manufacturer"], "Version":v, "Release date":new Date(responses['CIM_BIOSElement'].response["ReleaseDate"]['Datetime']).toLocaleDateString('en', {timeZone:'UTC'})},'');
|
||
|
||
x += "<br>";
|
||
for (i in responses['CIM_Processor'].responses) {
|
||
var p = responses['CIM_Processor'].responses[i];
|
||
var q = responses['CIM_Chip'].responses[i]; // This is a shortcut, we assume that the first entries in CIM_Chip are the processors, in the same order.
|
||
x += "<h2>Processor " + (parseInt(i) + 1) + "</h2>";
|
||
x += FullTable({"Manufacturer":trademarks(q["Manufacturer"]), "Family":DMTFProcFamilly[p["Family"]], "Version":trademarks(q["Version"]), "Maximum socket speed":p["MaxClockSpeed"] + " MHz", "Status":DMTFCPUStatus[p["CPUStatus"]]},'');
|
||
}
|
||
x += "<br>";
|
||
|
||
for (i in responses['CIM_PhysicalMemory'].responses) {
|
||
var m = responses['CIM_PhysicalMemory'].responses[i];
|
||
x += "<h2>Memory Module " + (+i + 1) + "</h2>";
|
||
x += FullTable({"Bank Label":m["BankLabel"], "Manufacturer":m["Manufacturer"], "Serial Number":m["SerialNumber"], "Size":parseInt(m["Capacity"] / 0x100000) + " MB", "Form factor":DMTFMemFormFactor[m["FormFactor"]], "Type":DMTFMemType[m["MemoryType"]], "Asset tag":m["Tag"], "Part number":m["PartNumber"]},'');
|
||
}
|
||
x += "<br>";
|
||
|
||
for (i in responses['CIM_MediaAccessDevice'].responses) {
|
||
var m = responses['CIM_MediaAccessDevice'].responses[i];
|
||
var n = responses['CIM_PhysicalPackage'].responses[+i + 1]; // This is a shortcut, we assume CIM_PhysicalPackage is the same as CIM_MediaAccessDevice except for the first entry.
|
||
x += "<h2>Storage Media " + (parseInt(i) + 1) + "</h2>";
|
||
x += FullTable({"Model":n['Model'],"Serial number":(n['SerialNumber']==''?'Unknown':n['SerialNumber']), "Size":parseInt(Math.round(m["MaxMediaSize"] * 1000 / 0x100000)) + " MB"},'');
|
||
}
|
||
x += "<br>";
|
||
|
||
QH('id_TableSysInfo', x);
|
||
updateSystemStatus();
|
||
}
|
||
|
||
|
||
function SaveHardwareLog() {
|
||
if (!xxdialogMode && HardwareInventory) SaveJsonFile('IntelAmtHardware', 'hardware', 'Intel AMT Hardware Information', HardwareInventory);
|
||
}
|
||
|
||
|
||
//
|
||
// POWER POLICY
|
||
//
|
||
|
||
var AmtSystemPowerSchemes = null;
|
||
function PullPowerPolicy() { amtstack.Enum("AMT_SystemPowerScheme", powerPolicyResponse); }
|
||
function powerPolicyResponse(stack, name, response, status) { AmtSystemPowerSchemes = response; updateSystemStatus(); }
|
||
|
||
function showPowerPolicyDlg(currentguid) {
|
||
if (xxdialogMode) return;
|
||
var s = "";
|
||
|
||
// Now find the name of the power schema in the AMT_SystemPowerScheme table
|
||
for (var j = 0; j < AmtSystemPowerSchemes.length; j++) {
|
||
var selected = ((AmtSystemPowerSchemes[j]['SchemeGUID'] == currentguid)?' checked':'');
|
||
s += '<input type=radio name=powerpolicy value=\"' + AmtSystemPowerSchemes[j]['InstanceID'] + "\" " + selected + '>' + AmtSystemPowerSchemes[j]['Description'] + '<br>';
|
||
}
|
||
|
||
setDialogMode(11, 'Intel® AMT Power Policy', 3, showPowerPolicyDlgOk, s);
|
||
}
|
||
|
||
function showPowerPolicyDlgOk() {
|
||
var id = null, i = 0, e = document.getElementsByTagName('input');
|
||
for (; i < e.length; i++) { if (e[i].name == 'powerpolicy' && e[i].checked) { id = e[i].value; } }
|
||
amtstack.AMT_SystemPowerScheme_SetPowerScheme(showPowerPolicyDlgOkDone, id);
|
||
}
|
||
|
||
function showPowerPolicyDlgOkDone(stack, name, response, status) { if (status == 200) PullSystemStatus(); }
|
||
|
||
//
|
||
// USER INFORMATION PANEL
|
||
//
|
||
|
||
var xxAccountAdminName, xxAccountRealmInfo, xxAccountEnabledInfo = {}, xxAccountFetch, showHiddenAccounts = false;
|
||
|
||
function PullUserInfo() {
|
||
xxAccountFetch = 1;
|
||
delete xxAccountAdminName;
|
||
xxAccountRealmInfo = {};
|
||
amtstack.AMT_AuthorizationService_GetAdminAclEntry(getAdminAclEntryResponse);
|
||
amtstack.AMT_AuthorizationService_EnumerateUserAclEntries(1, enumerateUserAclEntriesResponse);
|
||
}
|
||
|
||
function getAdminAclEntryResponse(stack, name, response, status) {
|
||
if (status != 200) return;
|
||
xxAccountRealmInfo[-1] = { 'AccessPermission': 999, 'DigestUsername': response.Body["Username"], 'Realms':null };
|
||
xxAccountAdminName = response.Body["Username"];
|
||
updateAccounts();
|
||
}
|
||
|
||
function enumerateUserAclEntriesResponse(stack, name, response, status) {
|
||
if (errcheck(status, stack)) return;
|
||
methodcheck(response);
|
||
QV('go11', true); // Show "User Accounts" on left panel
|
||
xxAccountFetch = response.Body["Handles"].length;
|
||
for (var i in response.Body["Handles"]) {
|
||
var h = response.Body["Handles"][i];
|
||
amtstack.AMT_AuthorizationService_GetAclEnabledState(h, getAclEnabledStateResponse, h);
|
||
amtstack.AMT_AuthorizationService_GetUserAclEntryEx(h, getUserAclEntryExResponse, h);
|
||
}
|
||
updateAccounts();
|
||
}
|
||
|
||
function getUserAclEntryExResponse(stack, name, response, status, tag) {
|
||
xxAccountFetch--;
|
||
//if (errcheck(status, stack)) return;
|
||
//methodcheck(response);
|
||
if (status == 200) { // Sometimes Intel AMT will just freak out on this call, if it's the case, just carry on.
|
||
response.Body["Handle"] = tag;
|
||
if (!response.Body["Realms"]) response.Body["Realms"] = [];
|
||
else if (!Array.isArray(response.Body["Realms"])) response.Body["Realms"] = [response.Body["Realms"]]; // If "Realms" is not an array, it's because it's just one realm. Fix is so it's an array of one.
|
||
xxAccountRealmInfo[tag] = response.Body;
|
||
updateAccounts();
|
||
}
|
||
}
|
||
|
||
function getAclEnabledStateResponse(stack, name, response, status, tag) {
|
||
//if (errcheck(status, stack)) return;
|
||
if (status == 200) { // Sometimes Intel AMT will just freak out on this call, if it's the case, just carry on.
|
||
xxAccountEnabledInfo[tag] = response.Body;
|
||
updateAccounts();
|
||
}
|
||
}
|
||
|
||
function setAclEnabledStateResponse(stack, name, response, status, tag) {
|
||
if (errcheck(status, stack)) return;
|
||
methodcheck(response);
|
||
amtstack.AMT_AuthorizationService_GetAclEnabledState(tag, getAclEnabledStateResponse, tag);
|
||
}
|
||
|
||
function updateAccounts() {
|
||
if (xxAccountFetch > 0) return;
|
||
var x = TableStart2();
|
||
x += "<tr><td class=r1 style=padding-left:15px><br>Manage the Intel® AMT user accounts for this computer.<br><br>";
|
||
for (var i in xxAccountRealmInfo) {
|
||
var r = xxAccountRealmInfo[i], name, hidden = false, state = 0;
|
||
if (r["DigestUsername"]) { name = r["DigestUsername"]; hidden = (name[0] == '$' && name[1] == '$'); } else { name = GetSidString(atob(r["KerberosUserSid"])); }
|
||
if (xxAccountEnabledInfo[i] && name != '$$OsAdmin') { state = (xxAccountEnabledInfo[i]['Enabled'] == true)?1:2; }
|
||
if (showHiddenAccounts || !hidden) {
|
||
var rb = "";
|
||
if (r["AccessPermission"] != 999) {
|
||
if (state == 2) { rb += "Disabled, "; }
|
||
var rc = 0;
|
||
for (i in r["Realms"]) { if (amtstack.RealmNames[r["Realms"][i]] != '') { rc++; } } // Count only realms that have a name, removing reserved realms.
|
||
if (r["Realms"].indexOf('20') >= 0) rb += "Auditor, ";
|
||
if (r["Realms"].indexOf('3') >= 0) { rb += "All realms"; } else { if (rc == 1) { rb += "1 realm"; } else { rb += rc + " realms"; } }
|
||
} else {
|
||
rb += "Administrator";
|
||
r["Handle"] = -1;
|
||
}
|
||
x += "<div class=itemBar onclick=showUserDetails(" + r["Handle"] + ")><div style=float:right>";
|
||
if (state > 0 && xxAccountAdminName) x += " " + AddButton2((state == 1?'Disable':'Enable'), "changeAccountStateButton(event," + r["Handle"] + "," + state + ")");
|
||
if (!hidden && xxAccountAdminName) x += " " + AddButton2("Edit...", "changeAccountButton(event," + r["Handle"] + ")");
|
||
x += "</div><div style=padding-top:3px;width:330px;float:left;overflow-x:hidden title='" + name + "'><b>" + name + "</b></div><div style=padding-top:3px>" + rb + "</div></div>";
|
||
}
|
||
}
|
||
var y = "<div style=float:right;margin-right:8px><a title='Toggle hidden accounts' style=color:gray;cursor:pointer onclick=toggleAccountButton()>" + (showHiddenAccounts?'▲':'▼') + "</a></div><div> " + AddRefreshButton("xxAccountFetch=999;PullUserInfo()");
|
||
if (xxAccountAdminName) y += AddButton("New Account", "newAccountButton()");
|
||
x += "<br><td class=r1>" + TableEnd(y + "</div>");
|
||
QH('id_TableUserAccounts', x);
|
||
}
|
||
|
||
function toggleAccountButton() {
|
||
showHiddenAccounts = !showHiddenAccounts;
|
||
updateAccounts();
|
||
}
|
||
|
||
function removeUserAclEntryResponse(stack, name, response, status, tag) {
|
||
if (methodcheck(response)) return;
|
||
PullUserInfo();
|
||
}
|
||
|
||
function changeAccountStateButton(e, h, s) {
|
||
haltEvent(e);
|
||
if (xxdialogMode) return;
|
||
amtstack.AMT_AuthorizationService_SetAclEnabledState(h, (s==1?false:true), setAclEnabledStateResponse, h);
|
||
}
|
||
|
||
function changeAccountButton(e, h) {
|
||
haltEvent(e);
|
||
if (xxdialogMode) return;
|
||
updateRealms(xxAccountRealmInfo[h]["Realms"]);
|
||
d2username.value = xxAccountRealmInfo[h]["DigestUsername"] ? xxAccountRealmInfo[h]["DigestUsername"] : GetSidString(atob(xxAccountRealmInfo[h]["KerberosUserSid"]));
|
||
d2password1.value = d2password2.value = '';
|
||
d2permission.value = xxAccountRealmInfo[h]["AccessPermission"];
|
||
setDialogMode(2, "Edit Account", ((h==-1)?3:7), function (button) { changeAccountButtonEx(h, button); }); // If handle is -1, only display OK/Cancel buttons, if not, also display delete button.
|
||
updateAccountDialog();
|
||
}
|
||
|
||
function newAccountButton() {
|
||
if (xxdialogMode) return;
|
||
updateRealms([]);
|
||
d2username.value = d2password1.value = d2password2.value = '';
|
||
d2permission.value = 2;
|
||
setDialogMode(2, "New Account", 3, function () { changeAccountButtonEx(null, 1); });
|
||
updateAccountDialog();
|
||
}
|
||
|
||
function changeAccountButtonEx(h, button) {
|
||
if (button == 1) { // OK Button
|
||
var realms = [], username = d2username.value, permission = d2permission.value, password = d2password1.value, sid = GetSidByteArray(Q('d2username').value), d = null;
|
||
if (username.length == 0 || password != d2password2.value) { messagebox("Account Error", "Invalid Parameters"); return; }
|
||
if (sid == null) { d = window.btoa(rstr_md5(username + ":" + amtsysstate['AMT_GeneralSettings'].response["DigestRealm"] + ":" + password)); } else { username = null; sid = btoa(sid); }
|
||
if (h != -1) { for (var y in amtstack.RealmNames) { if (amtstack.RealmNames[y] && Q('rx' + y).checked) { realms.push(y); } } }
|
||
if (h == null) { amtstack.AMT_AuthorizationService_AddUserAclEntryEx(username, d, sid, permission, realms, userAclEntryExResponse); }
|
||
else if (h == -1) { amtstack.AMT_AuthorizationService_SetAdminAclEntryEx(username, d, userAclEntryExResponse); }
|
||
else { amtstack.AMT_AuthorizationService_UpdateUserAclEntryEx(h, username, d, sid, permission, realms, userAclEntryExResponse); }
|
||
}
|
||
if (button == 2) { // Delete Button
|
||
amtstack.AMT_AuthorizationService_RemoveUserAclEntry(h, removeUserAclEntryResponse);
|
||
}
|
||
}
|
||
|
||
function userAclEntryExResponse(stack, name, response, status, tag) { if (methodcheck(response)) return; PullUserInfo(); }
|
||
function updateRealms(r) { QV('id_d2permissions', r != null); if (r != null) { var x = ''; for (var y in amtstack.RealmNames) { var c = ''; if (r.indexOf(parseInt(y)) >= 0) c = ' checked'; if (amtstack.RealmNames[y]) x += '<li><label><input type=checkbox onchange=updateAccountDialog() id=rx' + y + c + '>' + amtstack.RealmNames[y] + '</label></li>'; } QH('id_d2realms', x); } }
|
||
|
||
function updateAccountDialog() {
|
||
var ok = false;
|
||
for (var y in amtstack.RealmNames) { if (amtstack.RealmNames[y] && Q('rx' + y).checked) { ok = true; } }
|
||
ok &= (d2username.value.length > 0 && passwordcheck(d2password1.value) && d2password1.value == d2password2.value);
|
||
QE('idx_dlgOkButton', ok);
|
||
}
|
||
|
||
var xxUserPermissions = ['Local only' , 'Network only', 'All (Local & Network)'];
|
||
function showUserDetails(h) {
|
||
if (xxdialogMode) return;
|
||
var a = xxAccountRealmInfo[h], x = '<div style=text-align:left>';
|
||
var i, n = a["DigestUsername"];
|
||
if (!n) n = GetSidString(atob(a["KerberosUserSid"]));
|
||
x += addHtmlValue("Name", n);
|
||
if (xxAccountEnabledInfo[h]) { x += addHtmlValue("State", ((xxAccountEnabledInfo[h]['Enabled'] == true)?'Enabled':'Disabled')); }
|
||
if (n == xxAccountAdminName) {
|
||
x += addHtmlValue("Permission", "Administrator");
|
||
} else {
|
||
x += addHtmlValue("Permission", xxUserPermissions[a["AccessPermission"]]);
|
||
var y = '';
|
||
for (i in xxAccountRealmInfo[h]["Realms"]) {
|
||
if (amtstack.RealmNames[a["Realms"][i]] != '') { // Remove blank reserved realms
|
||
if (y.length > 0) y += ", ";
|
||
y += amtstack.RealmNames[a["Realms"][i]];
|
||
}
|
||
}
|
||
if (y.length == 0) y = 'None';
|
||
x += addHtmlValue("Realms", "") + "<b>" + y + "</b>";
|
||
}
|
||
x += "</div>";
|
||
messagebox("Account " + n, x);
|
||
}
|
||
|
||
//
|
||
// WSMAN BROWSER PANEL
|
||
//
|
||
|
||
|
||
function wsmanQuery() {
|
||
QH("id_wsresults", "");
|
||
var selections = getSelectedOptions(Q("id_QuerySelect")), selections2 = [];
|
||
for (var x in selections) { if (QS('WSB-' + selections[x]).display == '') selections2.push(selections[x]); }
|
||
if (selections2.length == 0) return;
|
||
QE("id_p12querybutton", false);
|
||
if (selections2 && selections2.length > 0) { amtstack.BatchEnum("Browser", selections2, browserResponse, null, true); } // This is the only BatchEnum where we want to continue and get an answer even if one of the calls fails.
|
||
}
|
||
|
||
function browserResponse(stack, name, responses, status) {
|
||
QE("id_p12querybutton", true);
|
||
var h = "";
|
||
for (var r in responses) {
|
||
var response = responses[r];
|
||
h += "<h2>" + r + "</h2><div style=margin-left:20px>";
|
||
if (response.status == 200) { if (response.responses.length == 0) { h += "<br>(Empty)"; } else { h += ObjectToString(response.responses).replace(/Intel\(r\)/g, 'Intel®'); } } else { h += "<br><div style=color:red>Error #" + response.status + "</div>"; }
|
||
h += "</div><br>";
|
||
}
|
||
QH("id_wsresults", h);
|
||
}
|
||
|
||
function wsmanFilter() {
|
||
var filter = idx_browserFilter.value.toLowerCase();
|
||
for (var w in AllWsman) { QV('WSB-' + AllWsman[w], filter == '' || AllWsman[w].toLowerCase().indexOf(filter) >= 0); }
|
||
}
|
||
|
||
//
|
||
// Remote Terminal
|
||
//
|
||
|
||
|
||
//
|
||
// Remote Desktop
|
||
//
|
||
|
||
|
||
//
|
||
// Remote IDER
|
||
//
|
||
|
||
|
||
|
||
|
||
//
|
||
// Remote Access
|
||
//
|
||
|
||
var xxRemoteAccess = null;
|
||
var xxEnvironementDetection = null;
|
||
var xxCiraServers = null;
|
||
var xxUserInitiatedCira = null;
|
||
var xxUserInitiatedEnabledState = { 32768: 'Disabled', 32769: 'BIOS enabled', 32770: 'OS enable', 32771: 'BIOS & OS enabled' };
|
||
var xxRemoteAccessCredentiaLinks = null;
|
||
var xxMPSUserPass = null;
|
||
var xxPolicies = null;
|
||
|
||
function PullRemoteAccess() {
|
||
// We only deal with remote access starting with Intel AMT 6 and beyond
|
||
|
||
amtstack.BatchEnum(null, ["*AMT_EnvironmentDetectionSettingData", "AMT_ManagementPresenceRemoteSAP", "AMT_RemoteAccessCredentialContext", "AMT_RemoteAccessPolicyAppliesToMPS", "AMT_RemoteAccessPolicyRule", "*AMT_UserInitiatedConnectionService", "AMT_MPSUsernamePassword"], processRemote1);
|
||
}
|
||
|
||
function processRemote1(stack, name, responses, status) {
|
||
if ((errcheck(status, stack)) || (responses["AMT_UserInitiatedConnectionService"] == undefined || responses["AMT_UserInitiatedConnectionService"].response == undefined)) return;
|
||
QV('go17', true); // Remote Access
|
||
xxRemoteAccess = responses;
|
||
xxEnvironementDetection = responses["AMT_EnvironmentDetectionSettingData"].response;
|
||
xxEnvironementDetection['DetectionStrings'] = MakeToArray(xxEnvironementDetection['DetectionStrings']);
|
||
xxCiraServers = responses["AMT_ManagementPresenceRemoteSAP"].responses;
|
||
xxUserInitiatedCira = responses["AMT_UserInitiatedConnectionService"].response;
|
||
xxRemoteAccessCredentiaLinks = responses["AMT_RemoteAccessCredentialContext"].responses;
|
||
xxMPSUserPass = responses["AMT_MPSUsernamePassword"].responses;
|
||
|
||
// Figure out policies attached to servers. Create a policy type to server table.
|
||
xxPolicies = { 'User': [], 'Alert': [], 'Periodic': [] };
|
||
for (var i in responses["AMT_RemoteAccessPolicyAppliesToMPS"].responses) {
|
||
var policy = responses["AMT_RemoteAccessPolicyAppliesToMPS"].responses[i];
|
||
var server = getItem(xxCiraServers, "Name", getItem(policy['ManagedElement']['ReferenceParameters']['SelectorSet']['Selector'], "@Name", "Name")['Value']);
|
||
var ptype = (getItem(policy['PolicySet']['ReferenceParameters']['SelectorSet']['Selector'], "@Name", "PolicyRuleName")['Value']).split(' ')[0];
|
||
xxPolicies[ptype].push(server);
|
||
}
|
||
|
||
updateRemoteAccess();
|
||
}
|
||
|
||
function updateRemoteAccess() {
|
||
if (xxEnvironementDetection == null) return;
|
||
var x = '';
|
||
var e = 'Disabled';
|
||
if (xxEnvironementDetection['DetectionStrings'] && xxEnvironementDetection['DetectionStrings'].length > 0) { e = 'Enabled, ' + xxEnvironementDetection['DetectionStrings'].length + ' domain' + (xxEnvironementDetection['DetectionStrings'].length > 1?'s':''); }
|
||
|
||
// General settings
|
||
x += TableStart();
|
||
x += TableEntry("Environment detection", addLink(e, 'editEnvironmentDetection()'));
|
||
x += TableEntry("User initiation options", addLinkConditional(xxUserInitiatedEnabledState[xxUserInitiatedCira['EnabledState']], 'editUserInitiatedCira()', xxAccountAdminName));
|
||
var y = '<i>None</i>';
|
||
if (xxPolicies['User'].length > 0) { y = ''; for (var i in xxPolicies['User']) { if (y.length > 0) y += ', '; y += xxPolicies['User'][i]['AccessInfo']; } }
|
||
x += TableEntry("User initiated connection", addLinkConditional(y, 'editMpsPolicy("User")', xxAccountAdminName));
|
||
var y = '<i>None</i>';
|
||
if (xxPolicies['Alert'].length > 0) { y = ''; for (var i in xxPolicies['Alert']) { if (y.length > 0) y += ', '; y += xxPolicies['Alert'][i]['AccessInfo']; } }
|
||
x += TableEntry("Alert initiated connection", addLinkConditional(y, 'editMpsPolicy("Alert")', xxAccountAdminName));
|
||
var y = '<i>None</i>';
|
||
if (xxPolicies['Periodic'].length > 0) { y = ''; for (var i in xxPolicies['Periodic']) { if (y.length > 0) y += ', '; y += xxPolicies['Periodic'][i]['AccessInfo']; } }
|
||
var periodicPolicy = getItem(xxRemoteAccess["AMT_RemoteAccessPolicyRule"].responses, "PolicyRuleName", "Periodic");
|
||
if (periodicPolicy) {
|
||
var exdata = atob(periodicPolicy['ExtendedData']);
|
||
if (ReadInt(exdata, 0) == 0) { y += ', each ' + ReadInt(exdata, 4) + ' seconds'; }
|
||
if (ReadInt(exdata, 0) == 1) { var hours = ReadInt(exdata, 4); var minutes = ReadInt(exdata, 8); if (minutes < 10) minutes = '0' + minutes; y += ', at ' + hours + ":" + minutes + ' daily'; }
|
||
}
|
||
x += TableEntry("Periodic connection", addLinkConditional(y, 'editMpsPolicy("Periodic")', xxAccountAdminName));
|
||
x += TableEnd();
|
||
|
||
x += "<br>";
|
||
x += TableStart2();
|
||
x += "<tr><td class=r1 style=padding-left:15px><br>Manage Intel® AMT remote management servers.<br><br>";
|
||
if (xxCiraServers.length == 0) {
|
||
x += "<div style=padding-left:15px><br><i>No remote servers found.</i></div><br>";
|
||
} else {
|
||
for (var i in xxCiraServers) {
|
||
var desc = ':' + xxCiraServers[i]['Port'];
|
||
if (xxCiraServers[i]['CN']) desc += ', ' + xxCiraServers[i]['CN'];
|
||
x += "<div class=itemBar onclick=showServerDetails(" + i + ")><div style=padding-top:3px><b>" + xxCiraServers[i]["AccessInfo"] + "</b>" + EscapeHtml(desc) + "</div></div>";
|
||
}
|
||
}
|
||
var addserverbutton = '';
|
||
if (xxAccountAdminName) addserverbutton = AddButton("Add Server...", "AddRemoteAccessServer()");
|
||
x += "<br><td class=r1>" + TableEnd(AddRefreshButton("PullRemoteAccess()") + addserverbutton);
|
||
|
||
QH('id_TableRemoteAccess', x);
|
||
}
|
||
|
||
var xxEditMpsPolicyType;
|
||
function editMpsPolicy(type) {
|
||
var x = '';
|
||
xxEditMpsPolicyType = type;
|
||
var name = xxEditMpsPolicyType;
|
||
if (name == 'User') name = 'User Initiated';
|
||
var policy = getItem(xxRemoteAccess["AMT_RemoteAccessPolicyRule"].responses, "PolicyRuleName", name);
|
||
x += '<div style=height:26px><select id=d2server1 style=float:right;width:206px onchange=editMpsPolicyUpdate()>';
|
||
x += '<option value=-1>(None)'
|
||
for (var i in xxCiraServers) { x += '<option value=' + i + '' + ((xxPolicies[type][0] && xxPolicies[type][0]['Name'] == xxCiraServers[i]['Name']) ? ' selected' : '') + '>' + xxCiraServers[i]['AccessInfo'] }
|
||
x += '</select><div>Primary server</div></div>';
|
||
if (xxCiraServers.length > 1) {
|
||
x += '<div style=height:26px><select id=d2server2 style=float:right;width:206px onchange=editMpsPolicyUpdate()>';
|
||
x += '<option value=-1>(None)'
|
||
for (var i in xxCiraServers) { x += '<option value=' + i + '' + ((xxPolicies[type][1] && xxPolicies[type][1]['Name'] == xxCiraServers[i]['Name']) ? ' selected' : '') + '>' + xxCiraServers[i]['AccessInfo'] }
|
||
x += '</select><div>Secondary server</div></div>';
|
||
}
|
||
var t, v = 0;
|
||
if (policy) { v = policy['TunnelLifeTime']; }
|
||
x += '<div style=height:26px><input id=d2lifetime style=float:right;width:200px onchange=editMpsPolicyUpdate() value=' + v + '>';
|
||
x += '<div>Tunnel lifetime (Seconds)</div></div>';
|
||
if (type == 'Periodic') {
|
||
t = 0;
|
||
v = 3600;
|
||
if (policy) {
|
||
var xx = atob(policy['ExtendedData']);
|
||
t = ReadInt(xx, 0);
|
||
v = ReadInt(xx, 4);
|
||
if (t == 1) {
|
||
var mm = ReadInt(xx, 8);
|
||
if (mm < 10) mm = '0' + mm;
|
||
v += ':' + mm;
|
||
}
|
||
}
|
||
x += '<div style=height:26px><select id=d2ttype style=float:right;width:206px onchange=editMpsPolicyUpdate()>';
|
||
x += '<option value=0' + (t == 0 ? ' selected' : '') + '>Periodic, time interval<option value=1' + (t == 1 ? ' selected' : '') + '>Time of day, once a day'
|
||
x += '</select><div>Trigger type</div></div><div style=height:26px><input id=d2timer style=float:right;width:200px onkeyup=editMpsPolicyUpdate() value=' + v + '><div id=ttypelabel></div></div>';
|
||
}
|
||
setDialogMode(11, type + " Connection", 3, editMpsPolicyOk, x);
|
||
editMpsPolicyUpdate();
|
||
}
|
||
|
||
function editMpsPolicyUpdate() {
|
||
var ok = (xxCiraServers.length <= 1 || ((Q('d2server1').value == -1) || (Q('d2server1').value != Q('d2server2').value)));
|
||
if (ok == true && xxEditMpsPolicyType == 'Periodic' && Q('d2ttype').value == 1) { var hhmm = Q('d2timer').value.split(':'); if (hhmm.length != 2) ok = false; else { var hh = parseInt(hhmm[0]); var mm = parseInt(hhmm[1]); if (hh < 0 || hh > 23 || mm < 0 || mm > 59) ok = false; } }
|
||
QE('idx_dlgOkButton', ok);
|
||
if (xxCiraServers.length > 1) QE('d2server2', Q('d2server1').value != -1);
|
||
if (xxEditMpsPolicyType == 'Periodic') {
|
||
QE('d2timer', Q('d2server1').value != -1);
|
||
QH('ttypelabel', Q('d2ttype').value == 0 ? 'Trigger interval (Seconds)' : 'Time of day (HH:MM)');
|
||
QE('d2ttype', Q('d2server1').value != -1);
|
||
}
|
||
QE('d2lifetime', Q('d2server1').value != -1);
|
||
}
|
||
|
||
function editMpsPolicyOk() {
|
||
var name = xxEditMpsPolicyType;
|
||
if (name == 'User') name = 'User Initiated';
|
||
|
||
if (getItem(xxRemoteAccess["AMT_RemoteAccessPolicyRule"].responses, "PolicyRuleName", name)) {
|
||
// Remove old policy
|
||
amtstack.Delete('AMT_RemoteAccessPolicyRule', { 'PolicyRuleName': name }, editMpsPolicyOk2);
|
||
} else {
|
||
// No old policy to remove, skip this.
|
||
editMpsPolicyOk2();
|
||
}
|
||
}
|
||
|
||
function editMpsPolicyOk2(stack, name, responses, status) {
|
||
if (Q('d2server1').value == -1) { PullRemoteAccess(); return; }
|
||
|
||
var trigger = 0;
|
||
if (xxEditMpsPolicyType == 'Alert') trigger = 1;
|
||
if (xxEditMpsPolicyType == 'Periodic') trigger = 2;
|
||
|
||
// Setup extended data
|
||
var extendedData = null;
|
||
if (trigger == 2) {
|
||
var timertype = Q('d2ttype').value;
|
||
var exdata = IntToStr(Q('d2timer').value); // Interval trigger
|
||
if (timertype == 1) { var hhmm = Q('d2timer').value.split(':'); exdata = IntToStr(parseInt(hhmm[0])) + IntToStr(parseInt(hhmm[1])); } // Time of day trigger
|
||
extendedData = btoa(IntToStr(timertype) + exdata);
|
||
}
|
||
|
||
// Add new policies
|
||
var servers = [];
|
||
if (Q('d2server1').value >= 0) { servers.push('<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://intel.com/wbem/wscim/1/amt-schema/1/AMT_ManagementPresenceRemoteSAP</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="Name">' + xxCiraServers[Q('d2server1').value]['Name'] + '</Selector></SelectorSet></ReferenceParameters>'); }
|
||
if (Q('d2server1').value >= 0 && xxCiraServers.length > 1 && Q('d2server2').value >= 0) { servers.push('<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://intel.com/wbem/wscim/1/amt-schema/1/AMT_ManagementPresenceRemoteSAP</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="Name">' + xxCiraServers[Q('d2server2').value]['Name'] + '</Selector></SelectorSet></ReferenceParameters>'); }
|
||
amtstack.AMT_RemoteAccessService_AddRemoteAccessPolicyRule(trigger, Q('d2lifetime').value, extendedData, servers, PullRemoteAccess);
|
||
}
|
||
|
||
var editEnvironmentDetectionTmp;
|
||
function editEnvironmentDetection(a) {
|
||
if (a != 1) { editEnvironmentDetectionTmp = (xxEnvironementDetection['DetectionStrings']) ? Clone(xxEnvironementDetection['DetectionStrings']) : []; }
|
||
var x = '';
|
||
if (xxAccountAdminName) x += 'Enter up to 4 intranet domain suffix. If the computer is outside these domains, Intel® AMT local ports will be closed and remote server connections will be active.<br><br>';
|
||
if (editEnvironmentDetectionTmp.length == 0) { x += "<i>No intranet domains, Environemnt detection disabled.</i><br>"; }
|
||
for (var i in editEnvironmentDetectionTmp) { x += "<div class=itemBar style=margin-right:0><div style=float:right>" + AddButton2("Remove", "editEnvironmentDetectionRemove(" + i + ")") + "</div><div style=padding-top:3px;max-width:260px;overflow:hidden title='" + editEnvironmentDetectionTmp[i] + "'><b>" + editEnvironmentDetectionTmp[i] + "</b></div></div>"; }
|
||
if (xxAccountAdminName && editEnvironmentDetectionTmp.length < 4) { x += "<br><input id=edInput placeholder=intranet.org style=width:276px onkeyup=edInputChg() maxlength=63><input type=button id=edAdd value=Add style=width:80px;margin-left:5px onclick=editEnvironmentDetectionAdd()>"; }
|
||
if (a == 1) QH('id_dialogOptions', x); else setDialogMode(11, 'Environment Detection', xxAccountAdminName?3:1, editEnvironmentDetectionDlg, x);
|
||
edInputChg();
|
||
}
|
||
|
||
function editEnvironmentDetectionDlg() {
|
||
if (xxAccountAdminName) {
|
||
var t = Clone(xxEnvironementDetection);
|
||
t['DetectionStrings'] = editEnvironmentDetectionTmp;
|
||
//console.log('AMT_EnvironmentDetectionSettingData', t);
|
||
amtstack.Put('AMT_EnvironmentDetectionSettingData', t, editEnvironmentDetectionDlg2, 0, 1);
|
||
}
|
||
}
|
||
|
||
function editEnvironmentDetectionDlg2(stack, name, response, status) {
|
||
if (status != 200) { messagebox('Environment Detection', 'Failed to add server, status ' + status); }
|
||
else if ((response.Body["ReturnValue"]) && (response.Body["ReturnValue"] != 0)) { messagebox('Environment Detection', response.Body.ReturnValueStr.replace(/_/g, ' ')); }
|
||
else { PullRemoteAccess(); }
|
||
}
|
||
|
||
function edInputChg() {
|
||
if (editEnvironmentDetectionTmp.length < 4) QE('edAdd', Q('edInput').value.length > 0);
|
||
}
|
||
|
||
function editEnvironmentDetectionAdd() {
|
||
editEnvironmentDetectionTmp.push(Q('edInput').value);
|
||
editEnvironmentDetection(1);
|
||
}
|
||
|
||
function editEnvironmentDetectionRemove(h) {
|
||
editEnvironmentDetectionTmp.splice(h, 1);
|
||
editEnvironmentDetection(1);
|
||
}
|
||
|
||
function editUserInitiatedCira() {
|
||
if (xxdialogMode) return;
|
||
var s = "";
|
||
for (var i in xxUserInitiatedEnabledState) { s += '<input type=radio name=d11 id=wl' + i + ' value=' + i + ' ' + ((xxUserInitiatedCira['EnabledState'] == i) ? "checked" : "") + '>' + xxUserInitiatedEnabledState[i] + '<br>'; }
|
||
setDialogMode(11, 'User Initiated Tunnel', 3, editUserInitiatedCiraDlg, s);
|
||
}
|
||
|
||
function editUserInitiatedCiraDlg() { amtstack.AMT_UserInitiatedConnectionService_RequestStateChange(document.querySelector('input[name=d11]:checked').value, null, editUserInitiatedCiraDlg2); }
|
||
function editUserInitiatedCiraDlg2(stack, name, response, status) { if (status == 200) { amtstack.Get('AMT_UserInitiatedConnectionService', editUserInitiatedCiraDlg3, 0, 1); } }
|
||
function editUserInitiatedCiraDlg3(stack, name, response, status) { if (status == 200) { xxUserInitiatedCira = response.Body; updateRemoteAccess(); } }
|
||
|
||
var xxShowServerDetailsHandle;
|
||
function showServerDetails(h) {
|
||
xxShowServerDetailsHandle = h;
|
||
var server = xxCiraServers[h];
|
||
var x = '';
|
||
x += addHtmlValue("Access Name", server["AccessInfo"]);
|
||
if (server["Port"]) x += addHtmlValue("Port", server["Port"]);
|
||
if (server["CN"]) x += addHtmlValue("Common Name", server["CN"]);
|
||
|
||
var credentiallink = getElementWithContextSelectorValue(xxRemoteAccessCredentiaLinks, server['Name']);
|
||
if (credentiallink) {
|
||
var credentialselectorvalue = credentiallink['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
|
||
if (credentialselectorvalue.indexOf('Username') > 0) {
|
||
x += addHtmlValue('Authentication Type', 'User & Pass / Server-Auth TLS');
|
||
x += addHtmlValue('Remote ID', getInstance(xxMPSUserPass, credentialselectorvalue)['RemoteID']);
|
||
} else {
|
||
x += addHtmlValue('Authentication Type', 'Certificate / Mutual-Auth TLS');
|
||
|
||
var c = getInstance(xxCertificates, credentialselectorvalue);
|
||
x += addHtmlValue('Certificate Name', parseCertName(c['Subject'])['CN']);
|
||
}
|
||
}
|
||
|
||
var buttons = 1;
|
||
if (xxAccountAdminName) buttons = 5;
|
||
setDialogMode(11, "Remote Server #" + (h + 1), buttons, showServerDetailsOk, x);
|
||
}
|
||
|
||
function getElementWithContextSelectorValue(elements, value) {
|
||
for (var i in elements) {
|
||
elements[i]['ElementProvidingContext']['ReferenceParameters']['SelectorSet']['Selector'] = MakeToArray(elements[i]['ElementProvidingContext']['ReferenceParameters']['SelectorSet']['Selector']);
|
||
for (var j in elements[i]['ElementProvidingContext']['ReferenceParameters']['SelectorSet']['Selector']) { if (elements[i]['ElementProvidingContext']['ReferenceParameters']['SelectorSet']['Selector'][j]['Value'] == value) return elements[i]; }
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function showServerDetailsOk(r) {
|
||
// Delete the selected CIRA server
|
||
if (r == 2) { amtstack.Delete('AMT_ManagementPresenceRemoteSAP', { 'Name': xxCiraServers[xxShowServerDetailsHandle]['Name'] }, showServerDetailsOk2); }
|
||
}
|
||
|
||
function showServerDetailsOk2(stack, name, responses, status) {
|
||
if (status == 408) { messagebox('Remote Server Removal', 'Unable to remove server, access denied.'); } else { PullRemoteAccess(); }
|
||
}
|
||
|
||
function parseCertName(x) {
|
||
var j, r = {}, xx = x.split(',');
|
||
for (var i in xx) { j = xx[i].indexOf('='); r[xx[i].substring(0, j)] = xx[i].substring(j + 1); }
|
||
return r;
|
||
}
|
||
|
||
function AddRemoteAccessServer() {
|
||
|
||
var certs = [];
|
||
for (var i in xxCertificates) { if (xxCertificates[i].XPrivateKey) { certs.push(xxCertificates[i]); } }
|
||
var x = '';
|
||
x += '<div style=height:26px><select id=d2type style=float:right;width:206px onchange=AddRemoteAccessServerUpdate()><option value=201>Hostname FQDN<option value=3>IPv4 address</select><div>Connection Type</div></div>';
|
||
x += '<div style=height:26px><input id=d2name style=float:right;width:200px onkeyup=AddRemoteAccessServerUpdate()><div id=d2lname></div></div>';
|
||
x += '<div style=height:26px><input id=d2port onkeypress="return (event.charCode == 0 || (event.charCode >= 48 && event.charCode <= 57))" style=float:right;width:200px value=4433 onkeyup=AddRemoteAccessServerUpdate()><div>Server port</div></div>';
|
||
x += '<div style=height:26px id=d2ucn><input id=d2cn style=float:right;width:200px onkeyup=AddRemoteAccessServerUpdate()><div>Server Common Name</div></div>';
|
||
x += '<div style=height:26px><select id=d2auth style=float:right;width:206px onchange=AddRemoteAccessServerUpdate()>';
|
||
if (certs.length > 0) { x += '<option value=1>Certificate'; }
|
||
x += '<option value=2>Username/Password</select><div>Authentication Type</div></div>';
|
||
x += '<span id=d2utype>';
|
||
x += '<div style=height:26px><input id=d2user style=float:right;width:200px onkeyup=AddRemoteAccessServerUpdate()><div>Username</div></div>';
|
||
x += '<div style=height:26px><input id=d2pass style=float:right;width:200px onkeyup=AddRemoteAccessServerUpdate()><div>Strong Password</div></div>';
|
||
x += '</span>';
|
||
x += '<span id=d2ctype>';
|
||
x += '<div style=height:26px><select id=d2cert style=float:right;width:206px onchange=AddRemoteAccessServerUpdate()>';
|
||
for (var i in certs) { x += '<option value=' + certs[i]['InstanceID'].substring(34) + '>' + parseCertName(certs[i]['Subject'])['CN']; }
|
||
x += '</select><div>Certificate</div></div></span>';
|
||
setDialogMode(11, "Add Remote Server", 3, AddRemoteAccessServerOk, x);
|
||
AddRemoteAccessServerUpdate();
|
||
}
|
||
|
||
function AddRemoteAccessServerOk() {
|
||
var cref, user, pass, cn;
|
||
if (Q('d2auth').value == 1) {
|
||
cref = '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://intel.com/wbem/wscim/1/amt-schema/1/AMT_PublicKeyCertificate</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT Certificate: Handle: ' + Q('d2cert').value + '</Selector></SelectorSet></ReferenceParameters>';
|
||
} else {
|
||
user = Q('d2user').value;
|
||
pass = Q('d2pass').value;
|
||
}
|
||
if (Q('d2cn').value.length > 0) cn = Q('d2cn').value;
|
||
amtstack.AMT_RemoteAccessService_AddMpServer(Q('d2name').value, Q('d2type').value, Q('d2port').value, Q('d2auth').value, cref, user, pass, cn, AddRemoteAccessServerOk2);
|
||
}
|
||
|
||
function AddRemoteAccessServerOk2(stack, name, response, status) {
|
||
if (status != 200) {
|
||
messagebox('Add Internet Server', 'Failed to add server, status ' + status);
|
||
} else if (response.Body["ReturnValue"] != 0) {
|
||
messagebox('Add Internet Server', response.Body.ReturnValueStr.replace(/_/g, ' '));
|
||
} else {
|
||
PullRemoteAccess();
|
||
}
|
||
}
|
||
|
||
function AddRemoteAccessServerUpdate() {
|
||
var ok = (Q('d2name').value.length != 0);
|
||
if (Q('d2type').value == 3 && ok == true) { ok = (Q('d2cn').value.length != 0); }
|
||
if (Q('d2auth').value == 2 && ok == true) { ok = ((Q('d2user').value.length != 0) && (passwordcheck(Q('d2pass').value))); }
|
||
QH('d2lname', (Q('d2type').value == 201) ? 'Hostname' : 'IPv4 Address');
|
||
QV('d2utype', Q('d2auth').value == 2);
|
||
QV('d2ucn', Q('d2type').value == 3);
|
||
QV('d2ctype', Q('d2auth').value == 1);
|
||
QE('idx_dlgOkButton', ok);
|
||
}
|
||
|
||
|
||
//
|
||
// Edit Computer Name & DNS feature
|
||
//
|
||
|
||
function showEditNameDlg(x) {
|
||
if (xxdialogMode) return;
|
||
var t = amtsysstate['AMT_GeneralSettings'].response["HostName"], y = amtsysstate['AMT_GeneralSettings'].response["DomainName"];
|
||
if (y != null && y.length > 0) t += "." + y;
|
||
var r = '<br><div style=height:26px><input id=d11name value="' + t + '" style=float:right;width:200px><div>Name & Domain</div></div>';
|
||
if (x == 1) {
|
||
var s = (amtsysstate['AMT_GeneralSettings'].response["SharedFQDN"] == true);
|
||
r += '<div style=height:26px><select id=d11fqdn style=float:right;width:200px><option value=true ' + (s?'selected':'') + '>Shared, same as OS<option value="false" ' + (s?'':'selected') + '>Dedicated, different from OS</select><div>Name Sharing</div></div>';
|
||
}
|
||
setDialogMode(11, 'Computer Name', 3, editNameDlgOk, r);
|
||
}
|
||
|
||
function editNameDlgOk() {
|
||
var i = Q('d11name').value, j = i.indexOf('.'), k = '';
|
||
if (j >= 0) { k = i.substring(j + 1); i = i.substring(0, j); }
|
||
var clone = Clone(amtsysstate['AMT_GeneralSettings'].response);
|
||
clone["HostName"] = i;
|
||
clone["DomainName"] = k;
|
||
if (Q('d11fqdn')) { clone["SharedFQDN"] = d11fqdn.value; }
|
||
amtstack.Put('AMT_GeneralSettings', clone, function() { amtstack.Get('AMT_GeneralSettings', computerNameGet, 0, 1); }, 0, 1);
|
||
}
|
||
|
||
function computerNameGet(stack, name, response, status) {
|
||
if (status == 200) { amtsysstate['AMT_GeneralSettings'].response = response.Body; updateSystemStatus(); }
|
||
}
|
||
|
||
function showEditDnsDlg() {
|
||
if (xxdialogMode) return;
|
||
var g = amtsysstate['AMT_GeneralSettings'].response;
|
||
var v = 0;
|
||
if (g["DDNSUpdateByDHCPServerEnabled"] == true) v = 1;
|
||
if (g["DDNSUpdateEnabled"] == true) v = 2;
|
||
idx_d23ddns.value = v;
|
||
idx_d23interval.value = g['DDNSPeriodicUpdateInterval'];
|
||
idx_d23ttl.value = g['DDNSTTL'];
|
||
showEditDnsDlgChange();
|
||
setDialogMode(23, 'Dynamic DNS client', 3, showEditDnsDlgOk);
|
||
}
|
||
|
||
function showEditDnsDlgOk() {
|
||
var clone = Clone(amtsysstate['AMT_GeneralSettings'].response);
|
||
clone["DDNSUpdateEnabled"] = ((idx_d23ddns.value == 2)?true:false);
|
||
clone["DDNSUpdateByDHCPServerEnabled"] = ((idx_d23ddns.value == 1)?true:false);
|
||
if (idx_d23ddns.value == 2) {
|
||
clone["DDNSPeriodicUpdateInterval"] = idx_d23interval.value;
|
||
clone["DDNSTTL"] = idx_d23ttl.value;
|
||
}
|
||
amtstack.Put('AMT_GeneralSettings', clone, function() { amtstack.Get('AMT_GeneralSettings', computerNameGet, 0, 1); }, 0, 1);
|
||
}
|
||
|
||
function showEditDnsDlgChange() {
|
||
QE('idx_d23interval', idx_d23ddns.value == 2);
|
||
QE('idx_d23ttl', idx_d23ddns.value == 2);
|
||
}
|
||
|
||
//
|
||
// Intel AMT Features
|
||
//
|
||
|
||
function showFeaturesDlg() {
|
||
if (xxdialogMode || !xxAccountAdminName) return;
|
||
idx_d9redir.checked = amtfeatures[0], idx_d9kvm.checked = amtfeatures[3], idx_d9ider.checked = amtfeatures[2], idx_d9sol.checked = amtfeatures[1];
|
||
QV('idx_d9kvm_div', amtfeatures[3] != undefined);
|
||
setDialogMode(9, 'Intel® AMT Features', 3, featuresDlgOk)
|
||
}
|
||
|
||
function featuresDlgOk() {
|
||
var r = amtsysstate['AMT_RedirectionService'].response;
|
||
r["ListenerEnabled"] = idx_d9redir.checked;
|
||
r["EnabledState"] = 32768 + ((idx_d9ider.checked?1:0) + (idx_d9sol.checked?2:0));
|
||
amtstack.AMT_RedirectionService_RequestStateChange(r["EnabledState"],
|
||
function(stack, name, response, status) {
|
||
if (status != 200) { messagebox("Error", "RedirectionService, RequestStateChange Error " + status); return; }
|
||
amtstack.CIM_KVMRedirectionSAP_RequestStateChange((idx_d9kvm.checked)?2:3, 0,
|
||
function(stack, name, response, status) {
|
||
if (status != 200) { messagebox("Error", "KVMRedirectionSAP, RequestStateChange Error " + status); return; }
|
||
amtstack.Put("AMT_RedirectionService", r, function(stack, name, response, status) {
|
||
if (status != 200) { messagebox("Error", "RedirectionService PUT Error " + status); return; }
|
||
amtstack.Get("AMT_RedirectionService", featuresDlgGet1, 0, 1);
|
||
amtstack.Get("CIM_KVMRedirectionSAP", featuresDlgGet2, 0, 1) }, 0, 1)
|
||
}
|
||
);}
|
||
);
|
||
}
|
||
|
||
function featuresDlgGet1(stack, name, response, status) {
|
||
if (status == 200) { amtsysstate['AMT_RedirectionService'].response = response.Body; updateSystemStatus(); }
|
||
}
|
||
|
||
function featuresDlgGet2(stack, name, response, status) {
|
||
if (status == 200) { amtsysstate['CIM_KVMRedirectionSAP'].response = response.Body; updateSystemStatus(); }
|
||
}
|
||
|
||
//
|
||
// Intel AMT User Consent
|
||
//
|
||
|
||
function showConsentDlg() {
|
||
if (xxdialogMode) return;
|
||
var optinrequired = amtsysstate['IPS_OptInService'].response["OptInRequired"];
|
||
idx_d10none.checked = (optinrequired == 0);
|
||
idx_d10kvm.checked = (optinrequired == 1);
|
||
idx_d10all.checked = (optinrequired == 0xFFFFFFFF);
|
||
setDialogMode(10, 'User Consent', 3, consentDlgOk)
|
||
}
|
||
|
||
function consentDlgOk() {
|
||
amtsysstate['IPS_OptInService'].response["OptInRequired"] = document.querySelector('input[name=d10]:checked').value;
|
||
amtstack.Put("IPS_OptInService", amtsysstate['IPS_OptInService'].response, function() { amtstack.Get("IPS_OptInService", consentGet, 0, 1); }, 0, 1);
|
||
}
|
||
|
||
function consentGet(stack, name, response, status) {
|
||
if (status == 200) { PullSystemStatus(); }
|
||
}
|
||
|
||
//
|
||
// NETWORK SETTINGS
|
||
//
|
||
|
||
|
||
|
||
/*
|
||
IPv6 %AddrType%:
|
||
0 - Address generated from: fe80::/64 + Interface ID,
|
||
1 - Address generated from: [Router advertised prefix] + [Interface ID],
|
||
2 - Global IPv6 address obtained from DHCPv6 Server,
|
||
3 - IPv6 unicast address configured by the user,
|
||
4 - Returned by FW for IPv6 addresses that are not owned by FW (such as IPv6 router address, configuration server address etc.)
|
||
|
||
IPv6 %AddrState%:
|
||
0 - DAD is still in process for this address,
|
||
1 - Address is valid and may be used for new communication,
|
||
2 - Address is deprecated and should not be used for new communication,
|
||
3 - Covers both the preferred and deprecated states,
|
||
4 - The valid lifetime of the address has expired,
|
||
5 - An interface ID collision has been detected for this address when performing DAD,
|
||
6 - Returned by FW for IPv6 addresses that are not owned by FW (such as IPv6 router address, configuration server address etc.
|
||
*/
|
||
// ipv6addrtype are element 0 to 4, ipv6addrstate are 5 and after. Merged both arrays to cut down size
|
||
var ipv6addrtype = ["Link local address", "Network local address", "Global address", "User configured", "Not allowed", "DAD in progress", "valid", "deprecated", "preferred/deprecated", "expired", "collision", "not allowed"];
|
||
function showIPv6AddrDlg(netifid, addrstr) {
|
||
if (xxdialogMode) return;
|
||
var x = TableStart();
|
||
t = addrstr.split(',');
|
||
for (var i = 0; i < t.length; i += 3) {
|
||
x += TableEntry("<b>" + t[i] + "</b><br><span style=font-size:10px>" + ipv6addrtype[t[i + 1]] + ", " + ipv6addrtype[+t[i + 2] + 5] + "</span>", "");
|
||
}
|
||
setDialogMode(11, 'IPv6 addresses for ' + (netifid==0?'wired':'wireless') + ' interface', 1, null, x + TableEnd());
|
||
}
|
||
|
||
function showIPv6StateDlg(netifid, state) {
|
||
if (xxdialogMode || !amtsysstate) return;
|
||
var zz = amtsysstate['IPS_IPv6PortSettings'].responses[netifid];
|
||
ipv6manual = (netifid == 0 && (isIpAddress(zz["IPv6Address"]) || isIpAddress(zz["DefaultRouter"]) || isIpAddress(zz["PrimaryDNS"]) || isIpAddress(zz["SecondaryDNS"])));
|
||
|
||
QV('id_d21manualdiv', netifid == 0);
|
||
QV('id_d21subnetdiv', false);
|
||
QV('d21o0', true);
|
||
QV('d21l0', true);
|
||
QH('d21l0', 'IPv6 disabled');
|
||
QH('d21l1', 'IPv6 enabled, automatic');
|
||
QH('d21l2', 'IPv6 enabled, automatic + manual addresse');
|
||
d21o0.checked = !state;
|
||
d21o1.checked = state && !ipv6manual;
|
||
d21o2.checked = state && ipv6manual;
|
||
idx_d21address.value = isIpAddress(zz["IPv6Address"],'');
|
||
idx_d21gateway.value = isIpAddress(zz["DefaultRouter"],'');
|
||
idx_d21dns1.value = isIpAddress(zz["PrimaryDNS"],'');
|
||
idx_d21dns2.value = isIpAddress(zz["SecondaryDNS"],'');
|
||
updateIPSetupDlg();
|
||
setDialogMode(21, 'IPv6 support for ' + (netifid==0?'wired':'wireless') + ' interface', 3, function() { showIPv6StateDlgOk(netifid) })
|
||
}
|
||
|
||
function showIPv6StateDlgOk(netifid) {
|
||
var zz = amtsysstate['IPS_IPv6PortSettings'].responses[netifid];
|
||
if (netifid == 0) { // We can only set IPv6 manual address for wired interface
|
||
if (d21o1.checked) { // Fully automatic, clear all addresses
|
||
zz["IPv6Address"] = zz["DefaultRouter"] = zz["PrimaryDNS"] = zz["SecondaryDNS"] = "::";
|
||
amtstack.Put("IPS_IPv6PortSettings", zz, showIPv6StateDlgDone);
|
||
}
|
||
if (d21o2.checked) { // Manual, set addresses
|
||
zz["IPv6Address"] = idx_d21address.value;
|
||
zz["DefaultRouter"] = idx_d21gateway.value;
|
||
zz["PrimaryDNS"] = idx_d21dns1.value;
|
||
zz["SecondaryDNS"] = idx_d21dns2.value;
|
||
amtstack.Put("IPS_IPv6PortSettings", zz, showIPv6StateDlgDone);
|
||
}
|
||
}
|
||
// This code enables/disables IPv6 for a given interface.
|
||
var elementSettings = amtsysstate['CIM_ElementSettingData'].responses;
|
||
for (var i = 0; i < elementSettings.length; i++) {
|
||
if (elementSettings[i]['SettingData'] && elementSettings[i]['SettingData']['ReferenceParameters']['SelectorSet']['Selector']['Value'] == ('Intel(r) IPS IPv6 Settings ' + netifid)) {
|
||
var clone = Clone(elementSettings[i]);
|
||
clone['IsCurrent'] = ((d21o0.checked)?2:1);
|
||
amtstack.Put("CIM_ElementSettingData", clone, showIPv6StateDlgDone);
|
||
}
|
||
}
|
||
}
|
||
|
||
function showIPv6StateDlgDone(stack, name, response, status) {
|
||
if (status == 200) {
|
||
amtsysstate = undefined;
|
||
PullSystemStatus();
|
||
} else {
|
||
messagebox('IPv6 support', 'Unable to set IPv6 state, error ' + status);
|
||
}
|
||
}
|
||
|
||
function showPingActionDlg() {
|
||
if (xxdialogMode) return;
|
||
var g = amtsysstate['AMT_GeneralSettings'].response;
|
||
var v = (g["PingResponseEnabled"] == true) + ((g["RmcpPingResponseEnabled"] == true) << 1);
|
||
d20a.checked = (v == 0);
|
||
d20b.checked = (v == 1);
|
||
d20c.checked = (v == 2);
|
||
d20d.checked = (v == 3);
|
||
setDialogMode(20, 'Intel® AMT Ping Response', 3, showPingActionDlgOk);
|
||
}
|
||
|
||
function showPingActionDlgOk() {
|
||
var clone = Clone(amtsysstate['AMT_GeneralSettings'].response);
|
||
var v = document.querySelector('input[name=d20]:checked').value;
|
||
clone["PingResponseEnabled"] = ((v & 1) != 0);
|
||
clone["RmcpPingResponseEnabled"] = ((v & 2) != 0);
|
||
amtstack.Put("AMT_GeneralSettings", clone, PullSystemStatus, 0, 1);
|
||
}
|
||
|
||
function showIPSetupDlg() {
|
||
if (xxdialogMode) return;
|
||
var x = amtsysstate['AMT_EthernetPortSettings'].responses[0];
|
||
QV('id_d21manualdiv', true);
|
||
QV('id_d21subnetdiv', true);
|
||
QV('d21o0', false);
|
||
QV('d21l0', false);
|
||
QH('d21l1', 'Automatic configuration using DHCP server');
|
||
QH('d21l2', 'Static configuration using IPv4 settings below');
|
||
d21o1.checked = (x['DHCPEnabled'] == true);
|
||
d21o2.checked = !d21o1.checked;
|
||
idx_d21address.value = isIpAddress(x["IPAddress"],'');
|
||
idx_d21subnet.value = isIpAddress(x["SubnetMask"],'');
|
||
idx_d21gateway.value = isIpAddress(x["DefaultGateway"],'');
|
||
idx_d21dns1.value = isIpAddress(x["PrimaryDNS"],'');
|
||
idx_d21dns2.value = isIpAddress(x["SecondaryDNS"],'');
|
||
updateIPSetupDlg();
|
||
setDialogMode(21, 'IPv4 Settings', 3, showIPSetupDlgOk)
|
||
}
|
||
|
||
function updateIPSetupDlg() {
|
||
idx_d21address.disabled = idx_d21subnet.disabled = idx_d21gateway.disabled = idx_d21dns1.disabled = idx_d21dns2.disabled = !d21o2.checked;
|
||
}
|
||
|
||
function showIPSetupDlgOk() {
|
||
var x = Clone(amtsysstate['AMT_EthernetPortSettings'].responses[0]);
|
||
x['DHCPEnabled'] = d21o1.checked;
|
||
delete x["IPAddress"];
|
||
delete x["SubnetMask"];
|
||
delete x["DefaultGateway"];
|
||
delete x["PrimaryDNS"];
|
||
delete x["SecondaryDNS"];
|
||
if (d21o1.checked == false) {
|
||
x["IPAddress"] = idx_d21address.value;
|
||
x["SubnetMask"] = idx_d21subnet.value;
|
||
x["DefaultGateway"] = idx_d21gateway.value;
|
||
if (idx_d21dns1.value != '') x["PrimaryDNS"] = idx_d21dns1.value;
|
||
if (idx_d21dns2.value != '') x["SecondaryDNS"] = idx_d21dns2.value;
|
||
}
|
||
amtstack.Put("AMT_EthernetPortSettings", x, showIPSetupDlgDone, 0, 1);
|
||
}
|
||
|
||
function showIPSetupDlgDone(stack, name, response, status) {
|
||
if (status == 200) {
|
||
amtsysstate = undefined;
|
||
PullSystemStatus();
|
||
} else {
|
||
messagebox('IPv4 Settings', 'Unable to set network parameters, error ' + status);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// POWER ACTIONS
|
||
//
|
||
|
||
amtPowerBootCapabilities = null;
|
||
function showPowerActionDlg() {
|
||
if (xxdialogMode) return;
|
||
statusbox("Power Actions", "Checking capabilities...");
|
||
amtstack.Get("AMT_BootCapabilities", powerActionResponse00, 0, 1);
|
||
}
|
||
|
||
function powerActionResponse00(stack, name, response, status) {
|
||
amtPowerBootCapabilities = response.Body;
|
||
QH('d5actionSelect', '');
|
||
addOption('d5actionSelect', 'Power up', 2);
|
||
addOption('d5actionSelect', 'Power cycle', 5);
|
||
addOption('d5actionSelect', 'Power down', 8);
|
||
addOption('d5actionSelect', 'Reset', 10);
|
||
if (amtPowerBootCapabilities["ForceDiagnosticBoot"] == true) {
|
||
addOption('d5actionSelect', 'Power on to diagnostic', 300);
|
||
addOption('d5actionSelect', 'Reset to diagnostic', 301);
|
||
}
|
||
if (amtversion > 9) {
|
||
addOption('d5actionSelect', 'Soft-off', 12);
|
||
addOption('d5actionSelect', 'Soft-reset', 14);
|
||
addOption('d5actionSelect', 'Sleep', 4);
|
||
addOption('d5actionSelect', 'Hibernate', 7);
|
||
}
|
||
if (amtPowerBootCapabilities["BIOSSetup"] == true) {
|
||
addOption('d5actionSelect', 'Power up to BIOS', 100);
|
||
addOption('d5actionSelect', 'Reset to BIOS', 101);
|
||
}
|
||
if (amtPowerBootCapabilities["SecureErase"] == true) {
|
||
addOption('d5actionSelect', "Reset to Secure Erase", 104);
|
||
}
|
||
addOption('d5actionSelect', 'Reset to PXE', 400);
|
||
addOption('d5actionSelect', 'Power on to PXE', 401);
|
||
|
||
addOption('d5actionSelect', 'Custom action...', 999);
|
||
if (amtversion > 5) { addOption('d5actionSelect', 'User consent...', 998); } // On AMT 5 and higher, offer the option of doing user consent alone.
|
||
setDialogMode(5, 'Power Actions', 3, powerActionDlgCheck);
|
||
}
|
||
|
||
function powerActionDlgCheck() {
|
||
var action = d5actionSelect.value;
|
||
if (action == 104) {
|
||
setDialogMode(11, 'Power Actions', 3, powerActionDlg, 'Confirm execution of Intel® Remote Secure Erase?<br><div style=color:red>This will wipe data on the remote system!</div>');
|
||
} else {
|
||
powerActionDlg();
|
||
}
|
||
}
|
||
|
||
function powerActionDlg() {
|
||
//if (amtversion == 0) return;
|
||
//if (amtversion > 6) { amtstack.Get("IPS_OptInService", powerActionResponse0); } else { amtstack.Get("AMT_BootSettingData", powerActionResponse1); }
|
||
var action = d5actionSelect.value;
|
||
|
||
if (action == 999) { showAdvPowerDlg(); return; }
|
||
if (action == 998) { amtstack.Get("IPS_OptInService", powerActionResponse0, 0, 1); return; }
|
||
|
||
// Some actions will not work if KVM/SOL/IDER are connected. If we perform these, disconnect now.
|
||
if ((action < 10) && (action > 2)) {
|
||
}
|
||
|
||
statusbox("Power Action", "Checking state...");
|
||
|
||
var uc = true; // Check if we need to ask for user consent
|
||
if (amtversion < 6) uc = false; // Below AMT 6, user consent does not exist.
|
||
if ((currentView == 13) && (action == 8)) uc = false; // if in terminal, don't ask for it if power down.
|
||
if ((currentView != 13) && (action <= 10)) uc = false; // if not in terminal, don't ask for basic operations.
|
||
|
||
if (uc) {
|
||
// Attempt user consent
|
||
amtstack.Get("IPS_OptInService", powerActionResponse0, 0, 1);
|
||
} else {
|
||
// Skip user consent
|
||
amtstack.Get("AMT_BootSettingData", powerActionResponse1, 0, 1);
|
||
}
|
||
}
|
||
|
||
|
||
var AvdPowerDlg;
|
||
function showAdvPowerDlg() {
|
||
// Show boot capabilities
|
||
QV('d24dBiosPause', amtPowerBootCapabilities["BIOSPause"] == true);
|
||
QV('d24dReflashBios', amtPowerBootCapabilities["BIOSReflash"] == true);
|
||
QV('d24dBiosSetup', amtPowerBootCapabilities["BIOSSetup"] == true);
|
||
QV('d24dConfigurationDataReset', amtPowerBootCapabilities["ConfigurationDataReset"] == true);
|
||
// QV('', amtPowerBootCapabilities["ForceCDorDVDBoot"] == true);
|
||
// QV('', amtPowerBootCapabilities["ForceDiagnosticBoot"] == true);
|
||
// QV('', amtPowerBootCapabilities["ForceHardDriveBoot"] == true);
|
||
// QV('', amtPowerBootCapabilities["ForceHardDriveSafeModeBoot"] == true);
|
||
// QV('', amtPowerBootCapabilities["ForcePXEBoot"] == true);
|
||
QV('d24dForceProgressEvents', amtPowerBootCapabilities["ForcedProgressEvents"] == true);
|
||
QV('d24dUseIDER', amtPowerBootCapabilities["IDER"] == true);
|
||
QV('d24dLockKeyboard', amtPowerBootCapabilities["KeyboardLock"] == true);
|
||
QV('d24dLockPowerButton', amtPowerBootCapabilities["PowerButtonLock"] == true);
|
||
QV('d24dLockResetButton', amtPowerBootCapabilities["ResetButtonLock"] == true);
|
||
QV('d24dSerialOverLan', amtPowerBootCapabilities["SOL"] == true);
|
||
QV('d24dSecureErase', amtPowerBootCapabilities["SecureErase"] == true);
|
||
QV('d24dLockSleepButton', amtPowerBootCapabilities["SleepButtonLock"] == true);
|
||
QV('d24dUserPasswordBypass', amtPowerBootCapabilities["UserPasswordBypass"] == true);
|
||
QV('idx_d24Verbocity1', amtPowerBootCapabilities["VerbosityQuiet"] == true);
|
||
QV('idx_d24Verbocity2', amtPowerBootCapabilities["VerbosityVerbose"] == true);
|
||
QV('idx_d24Verbocity3', amtPowerBootCapabilities["VerbosityScreenBlank"] == true);
|
||
|
||
setDialogMode(24, 'Custom Power Action', 3, showAdvPowerDlgOk);
|
||
showAdvPowerDlgChange();
|
||
}
|
||
|
||
function showAdvPowerDlgChange() { QV('idd_d24IDERBootDevice', Q('d24UseIDER').checked); }
|
||
|
||
function showAdvPowerDlgOk() {
|
||
// Fetch all of the user data
|
||
AvdPowerDlg = {};
|
||
AvdPowerDlg.Action = Q('idx_d24Command').value;
|
||
AvdPowerDlg.BIOSPause = Q('d24BiosPause').checked;
|
||
AvdPowerDlg.BIOSSetup = Q('d24BiosSetup').checked;
|
||
AvdPowerDlg.BootMediaIndex = Q('idx_d24BootMediaIndex').value;
|
||
AvdPowerDlg.ConfigurationDataReset = Q('d24ConfigurationDataReset').checked;
|
||
AvdPowerDlg.FirmwareVerbosity = Q('idx_d24Verbocity').value;
|
||
AvdPowerDlg.ForcedProgressEvents = Q('d24ForceProgressEvents').checked;
|
||
AvdPowerDlg.IDERBootDevice = Q('idx_d24IDERBootDevice').value; // 0 = Boot on Floppy, 1 = Boot on IDER
|
||
AvdPowerDlg.LockKeyboard = Q('d24LockKeyboard').checked;
|
||
AvdPowerDlg.LockPowerButton = Q('d24LockPowerButton').checked;
|
||
AvdPowerDlg.LockResetButton = Q('d24LockResetButton').checked;
|
||
AvdPowerDlg.LockSleepButton = Q('d24LockSleepButton').checked;
|
||
AvdPowerDlg.ReflashBIOS = Q('d24ReflashBios').checked;
|
||
AvdPowerDlg.UseIDER = Q('d24UseIDER').checked;
|
||
AvdPowerDlg.UseSOL = Q('d24SerialOverLan').checked;
|
||
AvdPowerDlg.UseSafeMode = Q('d24SafeMode').checked;
|
||
AvdPowerDlg.UserPasswordBypass = Q('d24UserPasswordBypass').checked;
|
||
AvdPowerDlg.SecureErase = Q('d24SecureErase').checked;
|
||
|
||
// Attempt user consent
|
||
statusbox("Power Action", "Checking state...");
|
||
amtstack.Get("IPS_OptInService", powerActionResponse0, 0, 1);
|
||
}
|
||
|
||
function powerActionResponse0(stack, name, response, status) {
|
||
//if (errcheck(status, stack)) return; // TODO: If AMT 5 or below, this will error and we need to skip user consent.
|
||
if (status != 200) { messagebox("Power Action", "Error #" + status); return; }
|
||
//console.log("powerActionResponse0(" + name + "," + ObjectToString2(response) + "," + status + ")");
|
||
//console.log("OptInRequired:", response.Body["OptInRequired"]);
|
||
//console.log("OptInState:", response.Body["OptInState"]);
|
||
//console.log(response);
|
||
if (response.Body["OptInRequired"] == 0xFFFFFFFF && response.Body["OptInState"] != 3 && response.Body["OptInState"] != 4) {
|
||
if (response.Body["OptInState"] == 2) {
|
||
// amtstack.IPS_OptInService_CancelOptIn(null);
|
||
d6ConsentText.value = ''; // Reset user consent code to empty
|
||
setDialogMode(6, "User Consent", 11, powerActionSendConsent);
|
||
checkConsentDisplay();
|
||
consentChanged();
|
||
return;
|
||
}
|
||
//console.log("Opt-in Required.", response.Body["OptInRequired"]);
|
||
statusbox("Power Action", "Starting opt-in...");
|
||
amtstack.IPS_OptInService_StartOptIn(powerActionResponseC1, 0, 1);
|
||
} else {
|
||
if (d5actionSelect.value == 998) { messagebox("User Consent", "User consent not needed."); return; }
|
||
statusbox("Power Action", "Getting Boot Settings...");
|
||
amtstack.Get("AMT_BootSettingData", powerActionResponse1, 0, 1);
|
||
}
|
||
}
|
||
|
||
function powerActionResponseC1(stack, name, response, status) {
|
||
//if (errcheck(status, stack)) return;
|
||
if (status != 200) { messagebox("Power Action", "Error #" + status); return; }
|
||
//console.log("powerActionResponseC1(" + name + "," + ObjectToString2(response) + "," + status + ")");
|
||
if (response.Body["ReturnValue"] != 0) {
|
||
messagebox("User Consent Error", response.Body.ReturnValueStr.replace(/_/g, ' '));
|
||
} else {
|
||
d6ConsentText.value = ''; // Reset user consent code to empty
|
||
setDialogMode(6, "User Consent", 11, powerActionSendConsent);
|
||
checkConsentDisplay();
|
||
consentChanged();
|
||
|
||
// Check if the machine is powered down, if it is, power it up for user consent.
|
||
amtstack.Enum('CIM_ServiceAvailableToElement', function(stack, name, responses, status) {
|
||
if (errcheck(status, stack)) return;
|
||
amtsysstate['CIM_ServiceAvailableToElement'].responses = responses;
|
||
updateSystemStatus();
|
||
if (amtsysstate['CIM_ServiceAvailableToElement'].responses[0]["PowerState"] != 2) {
|
||
// System is not on, power it on now.
|
||
amtstack.RequestPowerStateChange(2, function(stack, name, response, status) {});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
//function powerActionCancelConsent() { amtstack.IPS_OptInService_CancelOptIn(null); }
|
||
|
||
function powerActionSendConsent(buttons) {
|
||
if (buttons == 0) {
|
||
// Cancel button
|
||
amtstack.IPS_OptInService_CancelOptIn(function() {});
|
||
} else {
|
||
// OK Button
|
||
statusbox("Power Action", "Sending user consent...");
|
||
amtstack.IPS_OptInService_SendOptInCode(d6ConsentText.value, powerActionResponseC2, 0, 1);
|
||
}
|
||
}
|
||
|
||
function powerActionResponseC2(stack, name, response, status) {
|
||
//if (errcheck(status, stack)) return;
|
||
if (status != 200) { messagebox("Power Action", "Error #" + status); return; }
|
||
//console.log("powerActionResponseC2(" + name + "," + ObjectToString2(response) + "," + status + ")");
|
||
if (response.Body["ReturnValue"] != 0) {
|
||
amtstack.Get("IPS_OptInService", powerActionResponse0, 0, 1);
|
||
} else {
|
||
if (d5actionSelect.value == 998) { messagebox("User Consent", "User consent succesful."); return; } // This was just a user consent request, no need to go further.
|
||
statusbox("Power Action", "Checking state...");
|
||
amtstack.Get("AMT_BootSettingData", powerActionResponse1, 0, 1);
|
||
}
|
||
}
|
||
|
||
function powerActionResponse1(stack, name, response, status) {
|
||
//if (errcheck(status, stack)) return;
|
||
if (status != 200) { messagebox("Power Action", "Error #" + status); return; }
|
||
//console.log("powerActionResponse1(" + name + "," + ObjectToString2(response) + "," + status + ")");
|
||
var action = d5actionSelect.value;
|
||
var r = response.Body;
|
||
|
||
if (action == 999) {
|
||
r["BIOSPause"] = AvdPowerDlg.BIOSPause;
|
||
r["BIOSSetup"] = AvdPowerDlg.BIOSSetup;
|
||
r["BootMediaIndex"] = AvdPowerDlg.BootMediaIndex;
|
||
r["ConfigurationDataReset"] = AvdPowerDlg.ConfigurationDataReset;
|
||
r["FirmwareVerbosity"] = AvdPowerDlg.FirmwareVerbosity;
|
||
r["ForcedProgressEvents"] = AvdPowerDlg.ForcedProgressEvents;
|
||
r["IDERBootDevice"] = AvdPowerDlg.IDERBootDevice; // 0 = Boot on Floppy, 1 = Boot on IDER
|
||
r["LockKeyboard"] = AvdPowerDlg.LockKeyboard;
|
||
r["LockPowerButton"] = AvdPowerDlg.LockPowerButton;
|
||
r["LockResetButton"] = AvdPowerDlg.LockResetButton;
|
||
r["LockSleepButton"] = AvdPowerDlg.LockSleepButton;
|
||
r["ReflashBIOS"] = AvdPowerDlg.ReflashBIOS;
|
||
r["UseIDER"] = AvdPowerDlg.UseIDER;
|
||
r["UseSOL"] = AvdPowerDlg.UseSOL;
|
||
r["UseSafeMode"] = AvdPowerDlg.UseSafeMode;
|
||
r["UserPasswordBypass"] = AvdPowerDlg.UserPasswordBypass;
|
||
if (r["SecureErase"]) { r["SecureErase"] = ((AvdPowerDlg.SecureErase) && (amtPowerBootCapabilities["SecureErase"] == true)); }
|
||
} else {
|
||
r["BIOSPause"] = false;
|
||
r["BIOSSetup"] = (action > 99 && action < 104);
|
||
r["BootMediaIndex"] = 0;
|
||
r["ConfigurationDataReset"] = false;
|
||
r["FirmwareVerbosity"] = 0;
|
||
r["ForcedProgressEvents"] = false;
|
||
r["IDERBootDevice"] = ((action == 202) || (action == 203))?1:0; // 0 = Boot on Floppy, 1 = Boot on IDER
|
||
r["LockKeyboard"] = false;
|
||
r["LockPowerButton"] = false;
|
||
r["LockResetButton"] = false;
|
||
r["LockSleepButton"] = false;
|
||
r["ReflashBIOS"] = false;
|
||
r["UseIDER"] = ((action > 199) && (action < 300));
|
||
//r["UseSOL"] = ((currentView == 13) && (action != 8) && (action != 300) && (action != 301)); // If we are looking at the terminal, turn on SOL. SOL can't be used with diagnostic mode (300/301)
|
||
r["UseSOL"] = ((currentView == 13) && (action != 8)); // If we are looking at the terminal, turn on SOL.
|
||
r["UseSafeMode"] = false;
|
||
r["UserPasswordBypass"] = false;
|
||
if (r["SecureErase"]) { r["SecureErase"] = ((action == 104) && (amtPowerBootCapabilities["SecureErase"] == true)); }
|
||
|
||
}
|
||
//if (action == 104 && !r["SecureErase"]) { /*console.log("This Intel® AMT does not support Secure Erase");*/ cleanup(); return; }
|
||
//console.log("Setting Boot Settings: " + ObjectToString2(r), action);
|
||
statusbox("Power Action", "Setting boot settings...");
|
||
amtstack.Put("AMT_BootSettingData", r, powerActionResponse2, r, 1);
|
||
}
|
||
|
||
function powerActionResponse2(stack, name, response, status, tag) {
|
||
//console.log("powerActionResponse2(" + name + "," + response + "," + status + ")");
|
||
if (status != 200) { messagebox("Power Action", "PUT AMT_BootSettingData, Error #" + status); console.log(tag); return; }
|
||
//if (status == 408) { messagebox("Power Action", "Access denied."); return; }
|
||
//if (errcheck(status, stack)) return;
|
||
//console.log("Setup next boot...");
|
||
statusbox("Power Action", "Setting next boot...");
|
||
amtstack.SetBootConfigRole(1, powerActionResponse3x, 0, 1);
|
||
}
|
||
|
||
function powerActionResponse3x(stack, name, response, status) {
|
||
//console.log("powerActionResponse3x(" + name + "," + response + "," + status + ")");
|
||
var action = d5actionSelect.value, bootSource = null;
|
||
|
||
if (action == 999) {
|
||
if (idx_d24ForceBootDevice.value > 0) { bootSource = ['Force CD/DVD Boot', 'Force PXE Boot', 'Force Hard-drive Boot', 'Force Diagnostic Boot'][idx_d24ForceBootDevice.value - 1]; }
|
||
} else {
|
||
if (action == 300 || action == 301) { bootSource = 'Force Diagnostic Boot'; }
|
||
if (action == 400 || action == 401) { bootSource = 'Force PXE Boot'; }
|
||
|
||
}
|
||
if (bootSource != null) { bootSource = '<Address xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2004/08/addressing</Address><ReferenceParameters xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing"><ResourceURI xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_BootSourceSetting</ResourceURI><SelectorSet xmlns="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"><Selector Name="InstanceID">Intel(r) AMT: ' + bootSource + '</Selector></SelectorSet></ReferenceParameters>'; }
|
||
amtstack.CIM_BootConfigSetting_ChangeBootOrder(bootSource, powerActionResponse3);
|
||
}
|
||
|
||
var targetPowerAction = 0;
|
||
function powerActionResponse3(stack, name, response, status) {
|
||
//console.log("powerActionResponse3(" + name + "," + response + "," + status + ")");
|
||
if (errcheck(status, stack)) return;
|
||
//console.log("Performing Power State Change...");
|
||
statusbox("Power Action", "Performing power action...");
|
||
var action = d5actionSelect.value;
|
||
if (action == 100 || action == 201 || action == 203 || action == 300 || action == 401) action = 2; // Power up
|
||
if (action == 101 || action == 200 || action == 202 || action == 301 || action == 400) action = 10; // Reset
|
||
if (action == 104) action = 10; // Reset with Remote Secure Erase
|
||
|
||
if (action == 999) action = AvdPowerDlg.Action;
|
||
targetPowerAction = action;
|
||
//console.log('RequestPowerStateChange:' + action);
|
||
if (action == 11) { action = 10; }
|
||
if (action < 999) { amtstack.RequestPowerStateChange(action, powerActionResponse4); } else { messagebox("Power Action", "Next boot action set."); }
|
||
}
|
||
|
||
function powerActionResponse4(stack, name, response, status) {
|
||
//console.log("powerActionResponse4(" + name + "," + response + "," + status + ")");
|
||
if (status == 200) {
|
||
// Done!
|
||
//console.log("Action Completed.");
|
||
QH('id_dialogMessage', "Power action completed.");
|
||
setDialogMode(1, "Power Action", 0);
|
||
setTimeout(function() { setDialogMode(0); }, 1300);
|
||
}
|
||
// console.log("Power State Error: " + status);
|
||
// Sometimes Intel AMT with not respond and this will timeout. Try to pull the power state
|
||
amtstack.Get("CIM_AssociatedPowerManagementService", powerActionResponse5, 0, 1);
|
||
}
|
||
|
||
function powerActionResponse5(stack, name, response, status) {
|
||
//console.log("powerActionResponse5(" + name + "," + response + "," + status + ")");
|
||
/*
|
||
if (errcheck(status, stack)) return;
|
||
var powerstate = parseInt(response.Body["PowerState"]);
|
||
console.log("powerstate: " + powerstate);
|
||
if (targetPowerAction == powerstate) {
|
||
console.log("Action Completed. ");
|
||
} else {
|
||
console.log("Power State Error: " + status + ", CurrentState=" + powerstate + ", TargetState=" + targetPowerAction);
|
||
}
|
||
*/
|
||
}
|
||
|
||
function consentChanged() { QE('idx_dlgOkButton', d6ConsentText.value.length == 6); }
|
||
function changeConsentDisplay() { xxchangeConsentDisplay = true; checkConsentDisplay(); }
|
||
function checkConsentDisplay() { /*console.log("Getting display information...");*/ amtstack.Get("IPS_SecIOService", checkConsentDisplayResponse1); }
|
||
|
||
var xxchangeConsentDisplay = false;
|
||
function checkConsentDisplayResponse1(stack, name, response, status) {
|
||
// console.log("checkConsentDisplayResponse1(" + name + "," + response + "," + status + ")");
|
||
if (status == 200) {
|
||
if (response.Body["DefaultScreen"]) response.Body["DefaultScreen"] = parseInt(response.Body["DefaultScreen"]);
|
||
if (response.Body["NumberOfScreens"]) response.Body["NumberOfScreens"] = parseInt(response.Body["NumberOfScreens"]);
|
||
if (xxchangeConsentDisplay == true) {
|
||
xxchangeConsentDisplay = false;
|
||
// Change consent screen settings
|
||
response.Body["DefaultScreen"] = d6Display.value;
|
||
amtstack.Put("IPS_SecIOService", response.Body, checkConsentDisplayResponse1);
|
||
} else {
|
||
d6Display.value = response.Body["DefaultScreen"];
|
||
QV("d6ThirdDisplay", response.Body["NumberOfScreens"] > 2);
|
||
}
|
||
} else {
|
||
//console.log("Display Error: " + status);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Storage
|
||
//
|
||
|
||
var xxStorage = null;
|
||
var xxStorageVendors = [];
|
||
var xxStorageApplications = [];
|
||
|
||
function PullStorage() {
|
||
amtFirstPull |= 8;
|
||
wsstack.comm.PerformAjax("", PullStorageResponse, null, 0, "/amt-storage/", "GET");
|
||
}
|
||
|
||
function PullStorageResponse(data, status, tag) {
|
||
if (amtstack.PendingBatchOperations == 0) refreshButtons(true); // If nothing is being done, re-enable refresh buttons
|
||
|
||
if (status == 200) {
|
||
QV('go21', true); // Show Storage Panel
|
||
|
||
var len; // Remove all chars that are below 32, this will allow parsing even the the firmware gives us garbage.
|
||
for (var i = 0; i < 32; i++) { do { len = data.length; data = data.replace(String.fromCharCode(i),''); } while (len > data.length); }
|
||
try { xxStorage = JSON.parse(data); } catch (e) { return; }
|
||
xxStorageVendors = [];
|
||
xxStorageApplications = [];
|
||
var content = xxStorage['content'];
|
||
|
||
// Move index.htm and logon.htm
|
||
if (content['index.htm'] || content['logon.htm']) { content[''] = {'':{}}; }
|
||
if (content['index.htm']) { content['']['']['index.htm'] = content['index.htm']; delete content['index.htm']; }
|
||
if (content['logon.htm']) { content['']['']['logon.htm'] = content['logon.htm']; delete content['logon.htm']; }
|
||
|
||
// Display all storage files
|
||
var count = 0, x = TableStart2() + "<tr><td class=r1 style=padding-left:15px><br>Manage Intel® AMT storage for this computer.<br><br>";
|
||
var ii, jj, xx = "", links = "", linkid = 30;
|
||
for (var i in content) {
|
||
var jcount = 0;
|
||
for (var j in content[i]) {
|
||
jcount++;
|
||
var kcount = 0;
|
||
for (var k in content[i][j]) {
|
||
kcount++;
|
||
if (i != ii || j != jj) {
|
||
if (xx != "") { x += xx; xx = "<br>"; }
|
||
ii = i; jj = j;
|
||
if (i != '') { xx += EscapeHtml(i + " / " + j); } else { xx += "Root" }
|
||
}
|
||
//var handle = "\"" + i + "\",\"" + j + "\",\"" + k + "\"";
|
||
var handle = "\"" + i + ((i != '') ? '/' : '') + j + ((j != '') ? '/' : '') + k + "\"";
|
||
xx += '<div class=itemBar onclick=showStorageDetails("' + i + '","' + j + '","' + k + '",' + handle + ')><div style=float:right>';
|
||
|
||
xx += " " + AddButton2("Download", "DownloadFromStorage(" + handle + ",\"" + k + "\",event)");
|
||
xx += "</div><div style=padding-top:3px><b>" + EscapeHtml(k) + "</b>, <i>" + content[i][j][k]['size'] + " bytes</i></div></div>";
|
||
if (content[i][j][k]['link'] && i != '') { links += '<p id=go' + linkid + ' class=nav1 onclick=goiFrame(event,' + linkid + ',"/amt-storage/' + i + '/' + j + '/' + k + '") title="' + i + ' / ' + j + '"><a>' + content[i][j][k]['link'] + '</a></p>'; linkid++; }
|
||
count++;
|
||
if (xxStorageVendors.indexOf(i) == -1) xxStorageVendors.push(i);
|
||
if (xxStorageApplications.indexOf(j) == -1) xxStorageApplications.push(j);
|
||
}
|
||
if (kcount == 0) { wsstack.comm.PerformAjax("", function () { }, null, 0, "/amt-storage/" + i + "/" + j, "DELETE"); } // Clear empty applications
|
||
}
|
||
if (jcount == 0) { wsstack.comm.PerformAjax("", function () { }, null, 0, "/amt-storage/" + i, "DELETE"); } // Clear empty vendors
|
||
}
|
||
if (xx != "") x += xx;
|
||
if (count == 0) { x += "<div style=padding-left:15px><br><i>No files found.</i></div><br>"; }
|
||
x += "<br><td class=r1>" + TableEnd(AddRefreshButton("PullStorage()") + AddButton("Upload...", "UploadToStorage()")
|
||
);
|
||
QH('storagelinks', links);
|
||
QH('id_TableSystemStorage', x);
|
||
} else {
|
||
QH('id_TableSystemStorage', 'Unable to load storage data...<br/>' + AddButton("Refresh", "PullStorage()"));
|
||
}
|
||
}
|
||
|
||
function showStorageDetails(i, j, k, h) {
|
||
if (xxdialogMode) return;
|
||
var x = '', item = xxStorage['content'][i][j][k];
|
||
if (i != '') x += addHtmlValue("Vendor", i);
|
||
if (j != '') x += addHtmlValue("Application", j);
|
||
x += addHtmlValue("Name", k);
|
||
x += addHtmlValue("Size", item['size'] + ' bytes');
|
||
if (item['link']) { x += addHtmlValue("Link", item['link']); }
|
||
setDialogMode(11, 'Storage Item', 5, showStorageDetailsEx, x, h);
|
||
}
|
||
|
||
function showStorageDetailsEx(buttons, handle) {
|
||
if (buttons == 2) { wsstack.comm.PerformAjax("", storageDeleteResponse, null, 0, '/amt-storage/' + handle, "DELETE"); }
|
||
}
|
||
|
||
function storageDeleteResponse(data, status) {
|
||
if (status != 200) { messagebox("Storage", "Unable to delete file (ERR" + status + "), check that the computer is powered on."); } else { PullStorage(); }
|
||
}
|
||
|
||
|
||
function DownloadFromStorage(handle, filename, e) {
|
||
if (xxdialogMode) return;
|
||
haltEvent(e);
|
||
wsstack.comm.PerformAjax("", DownloadFromStorageEx, filename, 0, '/amt-storage/' + handle, "GET");
|
||
}
|
||
|
||
function DownloadFromStorageEx(data, status, tag) {
|
||
if ((status != 200) || (data == null)) { console.log(status, 'Data = null'); return; }
|
||
saveAs(data2blob(data), tag);
|
||
}
|
||
|
||
function OpenFromStorage(handle, e) {
|
||
if (xxdialogMode) return;
|
||
haltEvent(e);
|
||
|
||
window.open('http://' + wsstack.comm.host + ':' + wsstack.comm.port + '/amt-storage/' + handle, '_blank').focus();
|
||
}
|
||
|
||
function PushToStorage(vendorAppnameFilename, data, append) {
|
||
var tag = null;
|
||
if (data.length > 7000) { tag = [vendorAppnameFilename, data.substring(7000)]; data = data.substring(0, 7000); }
|
||
wsstack.comm.PerformAjax(data, PushToStorageResponse, tag, 0, "/amt-storage/" + vendorAppnameFilename + (append == true ? "?append=" : ""), "PUT");
|
||
}
|
||
|
||
function PushToStorageResponse(data, status, tag) {
|
||
if (status != 200) { messagebox("Storage", "Unable to push file (ERR" + status + "), check that the computer is powered on."); } else {
|
||
if (tag != null) { PushToStorage(tag[0], tag[1], true); } else { PullStorage(); }
|
||
}
|
||
}
|
||
|
||
function UploadToStorage(file, filename) {
|
||
if (xxdialogMode) return;
|
||
if (!filename) { filename = ''; }
|
||
var x = '';
|
||
x += '<br>Select a small file to upload to storage and enter a vendor, application and filename.<br>';
|
||
if (!file) {
|
||
x += '<br><div style=height:20px><input type=file id=mstoragefile style=float:right;width:240px onchange=SetStorageName()><div>Upload file</div></div>';
|
||
} else {
|
||
x += '<br><div style=height:20px><input id=mstoragefile style=float:right;width:240px readonly disabled=disabled value="' + filename + '" ><div>Upload file</div></div>';
|
||
}
|
||
x += '<br><div style=height:16px><input id=mstoragevendor placeholder=Vendor list=mstoragevendorlist maxlength=11 style=float:right;width:240px><div>Vendor name</div></div>';
|
||
x += '<br><div style=height:16px><input id=mstorageapplication placeholder=App list=mstorageapplicationlist maxlength=11 style=float:right;width:240px><div>Application name</div></div>';
|
||
x += '<br><div style=height:16px><input id=mstoragefilename placeholder=Filename maxlength=11 style=float:right;width:240px><div>Filename</div></div>';
|
||
x += '<br><div style=height:16px><input id=mstoragetype placeholder=application/octet-stream list=mstoragetypelist style=float:right;width:240px><div>MIME Type</div></div>';
|
||
x += '<br><div style=height:16px><input id=mstoragelink style=float:right;width:240px><div title="If set, creates a link to this content from the main web page">Link</div></div><br>';
|
||
x += '<datalist id=mstoragevendorlist>';
|
||
for (var i in xxStorageVendors) { x += '<option value="' + xxStorageVendors[i] + '">'; }
|
||
x += '</datalist>';
|
||
x += '<datalist id=mstorageapplicationlist>';
|
||
for (var i in xxStorageApplications) { x += '<option value="' + xxStorageApplications[i] + '">'; }
|
||
x += '</datalist><datalist id=mstoragetypelist><option value="application/octet-stream"><option value="image/jpeg"><option value="text/html"><option value="text/plain"></datalist>';
|
||
setDialogMode(11, 'Storage Upload', 3, UploadToStorageEx, x, file);
|
||
if (file) { SetStorageName(filename); }
|
||
}
|
||
|
||
|
||
function UploadToStorageEx(button, tag) {
|
||
if (!tag) {
|
||
var x = Q('mstoragefile');
|
||
if (x.files.length != 1) return;
|
||
var reader = new FileReader();
|
||
reader.onload = UploadToStorageEx2;
|
||
reader.filename = x.files[0].name;
|
||
reader.readAsBinaryString(x.files[0]);
|
||
} else {
|
||
var reader = new FileReader();
|
||
reader.onload = UploadToStorageEx2;
|
||
reader.filename = Q('mstoragefile').value;
|
||
reader.readAsBinaryString(tag);
|
||
}
|
||
}
|
||
|
||
function SetStorageName(filename) {
|
||
if (!filename) {
|
||
var x = Q('mstoragefile');
|
||
if (x.files.length == 1) { filename = x.files[0].name; } else { filename = ''; }
|
||
}
|
||
filename = filename.split(' ').join('');
|
||
var filenameSplit = filename.split('-');
|
||
if (filenameSplit.length == 3 && filenameSplit[0].length < 12 && filenameSplit[1].length < 12) {
|
||
Q('mstoragevendor').value = filenameSplit[0];
|
||
Q('mstorageapplication').value = filenameSplit[1];
|
||
filename = filenameSplit[2];
|
||
}
|
||
filename = filename.split('-').join('');
|
||
if (filename.endsWith('.gz')) filename = filename.substring(0, filename.length - 3);
|
||
if (filename.endsWith('.htm') || filename.endsWith('.html')) Q('mstoragetype').value = "text/html";
|
||
else if (filename.endsWith('.txt')) Q('mstoragetype').value = "text/plain";
|
||
if (filename.length > 11) filename = filename.substring(0, 11);
|
||
Q('mstoragefilename').value = filename;
|
||
}
|
||
|
||
function UploadToStorageEx2(file) {
|
||
var vaf, vendor = Q('mstoragevendor').value;
|
||
var app = Q('mstorageapplication').value;
|
||
var filename = Q('mstoragefilename').value;
|
||
if (filename == '') filename = 'Filename';
|
||
var mimetype = Q('mstoragetype').value;
|
||
if (mimetype == '') mimetype = 'application/octet-stream';
|
||
var link = Q('mstoragelink').value;
|
||
if (vendor == '' && app == '' && (filename.toLowerCase() == 'logon.htm' || filename.toLowerCase() == 'index.htm')) {
|
||
vaf = filename.toLowerCase();
|
||
} else {
|
||
if (vendor == '') vendor = 'Vendor';
|
||
if (app == '') app = 'App';
|
||
vaf = vendor + "/" + app + "/" + filename;
|
||
}
|
||
|
||
var data = "<metadata><headers>";
|
||
var filename = file.target.filename;
|
||
if (!filename) { filename = Q('mstoragefile').files[0].name; }
|
||
if (filename.endsWith('.gz')) { data += "<h>Content-Encoding: gzip</h>"; }
|
||
data += "<h>Content-Type: " + mimetype + "</h></headers>"; // <h>X-UA-Compatible: IE=Edge</h>
|
||
if (link != '') data += "<link>" + link + "</link>";
|
||
data += "</metadata>" + file.target.result;
|
||
|
||
PushToStorage(vaf, data);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Alarm Panel
|
||
//
|
||
|
||
function _fmtdatetime(str) {
|
||
return str.replace("T"," ").replace("Z","");
|
||
}
|
||
|
||
function _fmtinterval(str) {
|
||
var cl = str.replace("T", "").substring(str.indexOf('P') + 1);
|
||
cl = ' ' + cl.replace("D", " days ").replace("H", " hours ").replace("M"," minutes ");
|
||
cl = cl.replace(" 1 days ", " 1 day ").replace(" 1 hours ", " 1 hour ").replace(" 1 minutes "," 1 minute ");
|
||
return cl.substring(0, cl.length - 1);
|
||
}
|
||
|
||
function _fmttimepad(str) {
|
||
str = '' + str;
|
||
while (str.length < 2) { str = '0' + str; }
|
||
return str;
|
||
}
|
||
|
||
var xxAlarms = null;
|
||
|
||
function PullAlarms() {
|
||
var x = TableStart2() + "<tr><td class=r1 style=padding-left:15px><br>Manage wake alarms.<br><br>"
|
||
amtstack.Enum("IPS_AlarmClockOccurrence", function(stack, name, response, status) {
|
||
if (status == 200) {
|
||
QV("go23", true);
|
||
if (response.length > 0) {
|
||
xxAlarms = response;
|
||
for (var i = 0; i < response.length; i++) {
|
||
var waketime = new Date(response[i]["StartTime"]["Datetime"]);
|
||
var details = '<b>' + response[i]["ElementName"] + '</b>, wake on ' + waketime.toLocaleString().replace(', ', ' at ');
|
||
if (response[i]["Interval"] != undefined) { details += ' and each' + _fmtinterval(response[i]["Interval"]["Interval"]); }
|
||
if (response[i]["DeleteOnCompletion"] == true) { details += ', delete when done'; }
|
||
x += "<div class=itemBar onclick=showAlertDetails(" + i + ")><div style=float:right>";
|
||
if (xxAccountAdminName) x += " " + AddButton2("Edit...", "showAddAlarm(" + i + ")");
|
||
x += "</div><div style=padding-top:3px;width:auto;float:left;overflow-x:hidden>" + details + "</div></div>";
|
||
}
|
||
} else {
|
||
xxAlarms = null;
|
||
x += "<div style=padding-left:15px><br><i>No wake alarms registered.</i></div><br>";
|
||
}
|
||
var y = "<div> " + AddRefreshButton("PullAlarms()");
|
||
if (xxAccountAdminName) { y += AddButton("Remove all alarms", "RemoveAllAlarms()") + AddButton("Add", "showAddAlarm()"); }
|
||
x += "<br><td class=r1>" + TableEnd(y + "</div>");
|
||
QH('id_TableAlarm', x);
|
||
}
|
||
}, null, true);
|
||
}
|
||
|
||
function prepareAlarmOccurenceTemplate(id, nm, start, interval, del){
|
||
return "<d:AlarmTemplate xmlns:d=\"http://intel.com/wbem/wscim/1/amt-schema/1/AMT_AlarmClockService\" xmlns:s=\"http://intel.com/wbem/wscim/1/ips-schema/1/IPS_AlarmClockOccurrence\"><s:InstanceID>" + id + "</s:InstanceID><s:ElementName>" + nm + "</s:ElementName><s:StartTime><p:Datetime xmlns:p=\"http://schemas.dmtf.org/wbem/wscim/1/common\">" + start + "</p:Datetime></s:StartTime><s:Interval><p:Interval xmlns:p=\"http://schemas.dmtf.org/wbem/wscim/1/common\">" + interval + "</p:Interval></s:Interval><s:DeleteOnCompletion>" + del + "</s:DeleteOnCompletion></d:AlarmTemplate>";
|
||
}
|
||
|
||
function RemoveAllAlarms(){
|
||
setDialogMode(1, "Remove all wake alarms", 3, RemoveAllAlarmsEx, "Confirm removal of all wake alarms?");
|
||
}
|
||
|
||
function RemoveAllAlarmsEx() {
|
||
var deleteCount = xxAlarms.length;
|
||
for (var a in xxAlarms) { amtstack.Delete("IPS_AlarmClockOccurrence", xxAlarms[a], function(stack, name, response, status) { if (--deleteCount == 0) { PullAlarms(); }}); }
|
||
}
|
||
|
||
function showAddAlarm(alrm){
|
||
if (xxdialogMode) return;
|
||
QE('d25alarm_name', !alrm);
|
||
if (alrm != undefined) {
|
||
var alarm = xxAlarms[alrm], waketime = new Date(alarm["StartTime"]["Datetime"]);
|
||
Q('d25alarm_name').value = alarm["ElementName"];
|
||
Q('d25alarm_sdate').value = waketime.getFullYear() + '-' + _fmttimepad(waketime.getMonth() + 1) + '-' + _fmttimepad(waketime.getDate());
|
||
Q('d25alarm_stime').value = waketime.getHours() + ':' + _fmttimepad(waketime.getMinutes()) + ':' + _fmttimepad(waketime.getSeconds());
|
||
if (alarm["Interval"]) {
|
||
var j = alarm["Interval"]["Interval"].replace('P','').replace('T','').replace('D','D,').replace('H','H,').replace('M','M,').split(','), k = [0, 0, 0];
|
||
for (var i in j) {
|
||
var jl = j[i].length - 1;
|
||
if (j[i][jl] == 'D') { k[0] = parseInt(j[i].substring(0, jl)); }
|
||
if (j[i][jl] == 'H') { k[1] = parseInt(j[i].substring(0, jl)); }
|
||
if (j[i][jl] == 'M') { k[2] = parseInt(j[i].substring(0, jl)); }
|
||
}
|
||
Q('d25alarm_interval').value = k.join('-');
|
||
} else {
|
||
Q('d25alarm_interval').value = '';
|
||
}
|
||
Q('d25alarm_doc').value = (alarm["DeleteOnCompletion"] == true)?1:0;
|
||
} else {
|
||
var currentTime = new Date();
|
||
currentTime.setDate(new Date().getDate() + 1);
|
||
Q('d25alarm_name').value = '';
|
||
Q('d25alarm_sdate').value = currentTime.getFullYear() + '-' + _fmttimepad(currentTime.getMonth() + 1) + '-' + _fmttimepad(currentTime.getDate());
|
||
Q('d25alarm_stime').value = currentTime.getHours() + ':' + _fmttimepad(currentTime.getMinutes()) + ':00';
|
||
Q('d25alarm_interval').value = '';
|
||
Q('d25alarm_doc').value = 0;
|
||
}
|
||
setDialogMode(25, "Add new alarm", (alrm != undefined)?7:3, showAddAlarmOk, '', alrm);
|
||
alertDialogUpdate();
|
||
}
|
||
|
||
function alertDialogUpdate() {
|
||
var il = Q('d25alarm_interval').value.split('-').length;
|
||
var ok = (Q('d25alarm_name').value.length > 0) && (Q('d25alarm_sdate').value.split('-').length == 3) && (Q('d25alarm_stime').value.split(':').length == 3) && ((il == 1) || (il == 3));
|
||
QE('idx_dlgOkButton', ok);
|
||
}
|
||
|
||
function showAddAlarmOk(button, alrm) {
|
||
if (button == 2) { showAlertDetailsDelete(button, alrm); return; }
|
||
var alarm_name = Q('d25alarm_name').value;
|
||
var x1 = Q('d25alarm_sdate').value.split('-');
|
||
var x2 = Q('d25alarm_stime').value.split(':');
|
||
var t = new Date(x1[0], x1[1], x1[2], x2[0], x2[1], x2[2], 0);
|
||
var alarm_starttime = _fmttimepad(t.getUTCFullYear()) + '-' + _fmttimepad(t.getUTCMonth()) + '-' + _fmttimepad(t.getUTCDate()) + 'T' + _fmttimepad(t.getUTCHours()) + ':' + _fmttimepad(t.getUTCMinutes()) + ':' + _fmttimepad(t.getUTCSeconds()) + 'Z';
|
||
var x = Q('d25alarm_interval').value.split('-');
|
||
if (x.length != 3) { x = [0, 0, 0]; }
|
||
var alarm_interval = 'P' + x[0] + 'DT' + x[1] + 'H' + x[2] + 'M';
|
||
var alarm_doc = (Q('d25alarm_doc').value == 1);
|
||
var tpl = prepareAlarmOccurenceTemplate(alarm_name, alarm_name, alarm_starttime, alarm_interval, alarm_doc);
|
||
if (alrm == undefined) {
|
||
wsstack.ExecMethodXml(amtstack.CompleteName("AMT_AlarmClockService"), "AddAlarm", tpl,
|
||
function (ws, resuri, response, status) {
|
||
if (status != 200) { messagebox('Add alarm', 'Failed to add alarm. Status: ' + status); return; }
|
||
if (response.Body['ReturnValue'] != 0) { messagebox('Add alarm', 'Failed to add alarm, ' + response.Body['ReturnValueStr']); return; }
|
||
PullAlarms();
|
||
}
|
||
);
|
||
} else {
|
||
var alarmx = Clone(xxAlarms[alrm]);
|
||
alarmx["StartTime"] = "<p:Datetime xmlns:p=\"http://schemas.dmtf.org/wbem/wscim/1/common\">" + alarm_starttime + "</p:Datetime>";
|
||
alarmx["Interval"] = "<p:Interval xmlns:p=\"http://schemas.dmtf.org/wbem/wscim/1/common\">" + alarm_interval + "</p:Interval>";
|
||
alarmx["DeleteOnCompletion"] = alarm_doc;
|
||
amtstack.Put("IPS_AlarmClockOccurrence", alarmx, function (ws, resuri, response, status) {
|
||
if (status != 200) { messagebox('Edit alarm', 'Failed to change alarm. Status: ' + status); return; }
|
||
PullAlarms();
|
||
}, null, null, { "InstanceID" : alarmx["InstanceID"]});
|
||
}
|
||
}
|
||
|
||
function showAlertDetails(i) {
|
||
if (xxdialogMode) return;
|
||
var alarm = xxAlarms[i], waketime = new Date(alarm["StartTime"]["Datetime"]);
|
||
var x = '<div style=text-align:left>' + addHtmlValue("Name", alarm["ElementName"]) + addHtmlValue("Wake time", waketime.toLocaleString().replace(', ', ' at '));
|
||
if (alarm["Interval"] != undefined) { x += addHtmlValue("Internal", _fmtinterval(alarm["Interval"]["Interval"])); }
|
||
x += addHtmlValue("After wake", (alarm["DeleteOnCompletion"] == true)?"Delete Alarm":"Keep Alarm") + "</div>";
|
||
messagebox("Alarm " + alarm["ElementName"], x);
|
||
setDialogMode(11, "Alarm " + alarm["ElementName"], 5, showAlertDetailsDelete, x, i);
|
||
}
|
||
|
||
function showAlertDetailsDelete(button, tag) {
|
||
if (button == 2) { amtstack.Delete("IPS_AlarmClockOccurrence", xxAlarms[tag], function(stack, name, response, status) { PullAlarms(); }); }
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Scripting
|
||
//
|
||
|
||
// Called by the "Run Script..." button. Display a dialog box to ask for the script file.
|
||
function script_runScriptDlg() {
|
||
if (xxdialogMode || scriptstate) return;
|
||
setDialogMode(11, "Run Script", 3, script_runScriptDlgOk, "<br><input id=scriptopen type=file style=width:100% accept=.mescript>");
|
||
}
|
||
|
||
// Once the script file is selected, decode the script here.
|
||
function script_runScriptDlgOk(r) {
|
||
if (r != 1) return;
|
||
var x = Q('scriptopen');
|
||
if (x.files.length != 1) return;
|
||
var reader = new FileReader();
|
||
reader.onload = script_onScriptRead;
|
||
reader.readAsBinaryString(x.files[0]);
|
||
}
|
||
|
||
// Called once the script is decoded, get the script setup and start running it.
|
||
function script_onScriptRead(file) {
|
||
var x;
|
||
try { x = JSON.parse(file.target.result); } catch (e) {}
|
||
|
||
if (currentView == 20) {
|
||
// Load script into editor
|
||
if (x['scriptText']) Q('scriptarea').value = x['scriptText'];
|
||
if (x['mescript']) Q('compiledarea').value = rstr2hex(atob(x['mescript']));
|
||
if (x['blocks']) { script_setBuildBlocks(x['blocks']); scriptViewButton(1); } else { script_setBuildBlocks(); scriptViewButton(0); }
|
||
if (x['scriptBlocks']) { script_BlockScript = x['scriptBlocks']; } else { if (!script_BuildingBlocks) { script_BlockScript = []; } }
|
||
|
||
// Update the scriptBlocks to the latest blocks. This is does because we may update an existing script to new blocks.
|
||
for (var i in script_BlockScript) {
|
||
var block = script_BlockScript[i];
|
||
var xblock = script_BuildingBlocks[block['xname']];
|
||
if (xblock) {
|
||
var b = Clone(xblock); // Clone a new block, then set all the variables.
|
||
b['id'] = block['id'];
|
||
b['xname'] = block['xname'];
|
||
for (var j in b.vars) { if (block.vars[j]) { b.vars[j]['value'] = block.vars[j]['value']; } }
|
||
script_BlockScript[i] = b;
|
||
}
|
||
}
|
||
|
||
// Reset the script state
|
||
fupdatescript();
|
||
delete scriptstate;
|
||
resetScriptButton();
|
||
return;
|
||
}
|
||
var startvars = { '_interactive':1 };
|
||
|
||
startvars['_certificates'] = 1;
|
||
|
||
startvars['_mode'] = 'Firmware';
|
||
|
||
startvars['_mode'] = 'MeshCentral';
|
||
if (x && x['mescript']) scriptstate = script_setup(atob(x['mescript']), startvars);
|
||
if (scriptstate) {
|
||
scriptstate.wsstack = wsstack;
|
||
scriptstate.amtstack = amtstack;
|
||
scriptstate.onStep = script_updateScriptState;
|
||
scriptstate.onConsole = script_console;
|
||
scriptstate.start(100);
|
||
} else {
|
||
messagebox("Run Script", "Invalid script file.");
|
||
}
|
||
}
|
||
|
||
// Called when the script changes state, displays the green status bar at the top of the web page.
|
||
function script_updateScriptState() {
|
||
if (scriptstate) {
|
||
QV('id_scriptstatus', scriptstate.state > 0); center();
|
||
if (scriptstate.state == 0) {
|
||
scriptstate = undefined;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Called by a running script to update the console. The last message in the console is displayed in the top green bar.
|
||
function script_console(msg) {
|
||
if (msg.indexOf("INFO: ") == 0) { msg = msg.substring(6); }
|
||
if (msg.indexOf("SUCCESS: ") == 0) { msg = msg.substring(9); }
|
||
if (msg.indexOf("ERROR: ") == 0) { msg = msg.substring(7); }
|
||
QH('id_scriptstatusstr', ', ' + msg);
|
||
}
|
||
|
||
// Called by the "Stop" button on the script status bar. Causes the script to stop.
|
||
function script_Stop() {
|
||
if (scriptstate) {
|
||
if (scriptstate.dialog == true) { setDialogMode(0); }
|
||
scriptstate.stop();
|
||
scriptstate.state = 0;
|
||
script_updateScriptState();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
var script_BuildingBlocks; // List of block that can be used to build a script. This is shown on the list side.
|
||
var script_StartingBuildingBlocks; // List of block that can be used to build a script when you get started.
|
||
var script_BlockScript = []; // List of blocks that are part of the current script.
|
||
var script_BlockScriptSelectedId = null; // The identifier of the currently selected block.
|
||
var script_BuilderView = 0; // 0 = Editor View, 1 = Builder View
|
||
var editscriptstate; // This is the instance of the script runner used by the editor. This is different from the runner used by the web page.
|
||
|
||
|
||
function scriptLoadStartingBlocks() {
|
||
|
||
var request = new XMLHttpRequest();
|
||
request.onload = function() {
|
||
if (request.status >= 200 && request.status < 400) {
|
||
// Success
|
||
var x;
|
||
try { x = JSON.parse(request.responseText); } catch (e) {}
|
||
if ((x) && (x['blocks'])) { script_StartingBuildingBlocks = x['blocks']; script_setBuildBlocks(script_StartingBuildingBlocks); }
|
||
}
|
||
};
|
||
request.onerror = function() { console.log('Failed to get script blocks'); };
|
||
request.open('GET', 'scriptblocks.txt', true);
|
||
request.send();
|
||
}
|
||
|
||
// Called by the "View Editor" or "View Builder" button to toggle between the view modes.
|
||
// If there is no construction blocks, display the script editor only.
|
||
function scriptViewButton(x) {
|
||
script_BuilderView = x;
|
||
QV('scripteditor', x == 0);
|
||
QV('scriptbuilder', x == 1);
|
||
QV('viewEditorButton', script_BuildingBlocks && (x == 1));
|
||
QV('viewBuilderButton', script_BuildingBlocks && (x == 0));
|
||
}
|
||
|
||
// Display the list of construction script block on the left side.
|
||
function script_setBuildBlocks(blocks) {
|
||
script_BuildingBlocks = blocks;
|
||
var x = '';
|
||
if (blocks) {
|
||
for (var i in blocks) {
|
||
if (i.charCodeAt(0) != 95) {
|
||
x += '<div id=sblock_' + i + ' style=cursor:pointer;background-color:#ccc;width:auto;padding:5px;margin:2px ondblclick=script_faddblock("' + i + '") draggable=true ondragstart=script_fondragstart(event,this) ondragend=script_fondragend(event,this) title="' + blocks[i]['desc'] + '"';
|
||
x += '>' + blocks[i]['name'] + '</div>';
|
||
}
|
||
}
|
||
}
|
||
QH('blocks', x);
|
||
script_fonfilterchanged();
|
||
scriptViewButton(script_BuildingBlocks?1:0);
|
||
}
|
||
|
||
// Add a new block to the end of the block script. This is called when you right click on a block and hit "Add" in the context menu.
|
||
function script_faddblock(h) {
|
||
var b = Clone(script_BuildingBlocks[h]); // To add a new block to the script, clone the block, assign it a random id and set the xname to the name of the block.
|
||
b['id'] = Math.random();
|
||
b['xname'] = h;
|
||
script_BlockScript.push(b); // Now that a block is created, add it at the end of the script and set it as the selected block.
|
||
script_BlockScriptSelectedId = script_BlockScript.length - 1;
|
||
fupdatescript(); // Re-display the list of script blocks. The selected block will also be highlighted.
|
||
}
|
||
|
||
// Edit a script block
|
||
function script_feditblock(h) {
|
||
if (xxdialogMode) return;
|
||
setDialogMode(11, 'Edit ' + script_BuildingBlocks[h]['name'], 3, script_feditblockEx, 'Edit this block? This operation will reset the block editor and load the block code into the code editor.', h);
|
||
}
|
||
|
||
// Called when the script block edit dialog is closed.
|
||
function script_feditblockEx(button, h) {
|
||
script_newScriptDlgOk();
|
||
scriptViewButton(0);
|
||
var x = '', b = script_BuildingBlocks[h];
|
||
x += '##!BLOCK!##\r\n#id=' + h + '\r\n#name=' + b['name'] + '\r\n#desc=' + b['desc'] + '\r\n##!BLOCK!##\r\n';
|
||
for (var i in b['vars']) {
|
||
var v = b['vars'][i];
|
||
x += '##!VAR!##\r\n#id=' + i + '\r\n#name=' + v['name'] + '\r\n#desc=' + v['desc'] + '\r\n#type=' + v['type'] + '\r\n';
|
||
if (v['maxlength']) { x += '#maxlength=' + v['maxlength'] + '\r\n'; }
|
||
if (v['values']) { for (var j in v['values']) { x += '#values-' + j +'=' + v['values'][j] + '\r\n'; } }
|
||
x += '#value=' + v['value'] + '\r\n##SWAP %%%' + i + '%%% ' + v['value'] + '\r\n';
|
||
}
|
||
x += '##!VAR!##\r\n##SWAP %%%~%%% 0\r\n\r\n##!BLOCK!##\r\n' + b['code'] + '\r\n##!BLOCK!##\r\n';
|
||
Q('scriptarea').value = x;
|
||
}
|
||
|
||
function script_fConvertScriptToJsonBlock(script) {
|
||
var result = {}, scriptparts = script.split('##!BLOCK!##\n'), scriptlines = scriptparts[1].split('\n');
|
||
|
||
// Parse information part
|
||
for (var i in scriptlines) {
|
||
var s = scriptlines[i].split('=');
|
||
if (s.length == 2) { result[s[0].substring(1)] = s[1]; }
|
||
}
|
||
|
||
// Parse variables part
|
||
result['vars'] = {};
|
||
scriptvariables = scriptparts[2].split('##!VAR!##\n');
|
||
for (var i in scriptvariables) {
|
||
scriptlines = scriptvariables[i].split('\n');
|
||
var v = {}, values = {}, valueslen = 0;
|
||
for (var j in scriptlines) {
|
||
var s = scriptlines[j].split('=');
|
||
if ((s.length == 2) && (s[1])) {
|
||
if ((s[0]) && (s[0].length > 0)) {
|
||
if (s[0].substring(0, 8) == '#values-') {
|
||
values[s[0].substring(8)] = s[1];
|
||
valueslen++;
|
||
} else {
|
||
v[s[0].substring(1)] = s[1];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (v.id) {
|
||
if (valueslen > 0) { v['values'] = values; }
|
||
var id = v.id;
|
||
delete v.id;
|
||
result['vars'][id] = v;
|
||
}
|
||
}
|
||
|
||
// Parse code part
|
||
result['code'] = scriptparts[3];
|
||
|
||
// Package the result
|
||
var id = result.id;
|
||
delete result.id;
|
||
var r = {};
|
||
r[id] = result;
|
||
return JSON.stringify(r, null, ' ');
|
||
}
|
||
|
||
/*
|
||
// If the delete key is pressed, delete the block
|
||
function script_fonkeypress(e) {
|
||
if (xxdialogMode) return;
|
||
if (e.key == 'Delete' && script_BlockScriptSelectedId != null) {
|
||
//delete script_BlockScript[script_BlockScriptSelectedId];
|
||
script_BlockScript.splice(script_BlockScriptSelectedId, 1);
|
||
script_BlockScriptSelectedId = null;
|
||
fupdatescript();
|
||
}
|
||
}
|
||
*/
|
||
|
||
// Filer the list of blocks on the left side list.
|
||
function script_fonfilterchanged() {
|
||
var filter = Q('blockfilter').value.toLowerCase();
|
||
for (var i in script_BuildingBlocks) {
|
||
// Script blocks that start with "_" are hidden blocks and should never be shown.
|
||
if (i.charCodeAt(0) != 95) { QV('sblock_' + i, (script_BuildingBlocks[i]['name'].toLowerCase().indexOf(filter) >= 0 || script_BuildingBlocks[i]['desc'].toLowerCase().indexOf(filter) >= 0)); }
|
||
}
|
||
}
|
||
|
||
// Called when you click on a block. If you double click, on a block, do like pressing on the "Edit..." button.
|
||
var script_fonclickDblClickDetectIndex = null;
|
||
var script_fonclickDblClickDetectTime = null;
|
||
function script_fonclick(e, t) {
|
||
if (xxdialogMode) return;
|
||
script_BlockScriptSelectedId = null;
|
||
if (t) { t = fgetParentWithId(t); if (t.id.startsWith('xblock_')) { script_BlockScriptSelectedId = t.id.substring(7); } } // Figure out what block was clicked on
|
||
fupdatescript(); // This will show the currently selected block in a different color.
|
||
haltEvent(e);
|
||
|
||
// Figure out if this is a double click. We need two clicks on the same object within 250ms.
|
||
if (script_fonclickDblClickDetectIndex == script_BlockScriptSelectedId && (new Date().getTime() - script_fonclickDblClickDetectTime) < 250) { return script_foneditclick(script_BlockScriptSelectedId); }
|
||
script_fonclickDblClickDetectIndex = script_BlockScriptSelectedId;
|
||
script_fonclickDblClickDetectTime = new Date().getTime();
|
||
}
|
||
|
||
// Script block drag & drop functions, allow the user to create and move script blocks.
|
||
function script_fondragstart(e, t) { if (xxdialogMode) return; t = fgetParentWithId(t); t.style.opacity = '0.4'; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('scriptbuilder/block', t.id); }
|
||
function script_fondragend(e, t) { if (xxdialogMode) return; t = fgetParentWithId(t); t.style.opacity = '1.0'; }
|
||
function script_fondragenter(e, t) { if (xxdialogMode) return; fgetParentWithId(t).style['border-top'] = 'solid 2px black'; }
|
||
function script_fondragleave(e, t) { if (xxdialogMode) return; e = e.originalEvent || e; var currentElement = document.elementFromPoint(e.pageX, e.pageY); if (!t.contains(currentElement)) { fgetParentWithId(t).style['border-top'] = 'none'; } }
|
||
|
||
// Called when you drop a block on the block script area. This code will add or move a block into it's new place.
|
||
function script_fondrop(e, t) {
|
||
if (xxdialogMode) return;
|
||
t = fgetParentWithId(t);
|
||
var b, x = e.dataTransfer.getData('scriptbuilder/block'), dp = parseInt(t.id.substring(7));
|
||
if (x == '') { documentFileSelectHandler(e); return; }
|
||
if (x.startsWith("sblock_")) {
|
||
// Create a new script block
|
||
b = Clone(script_BuildingBlocks[x.substring(7)]);
|
||
b['id'] = Math.random();
|
||
b['xname'] = x.substring(7);
|
||
} else {
|
||
// Pull an existing script block from the list
|
||
var i = parseInt(x.substring(7));
|
||
b = script_BlockScript[i];
|
||
script_BlockScript.splice(i, 1);
|
||
if (dp > i) dp--;
|
||
}
|
||
if (t.id == 'scriptblocks') {
|
||
// Put the block at the end of the list
|
||
if (b) script_BlockScript.push(b);
|
||
script_BlockScriptSelectedId = script_BlockScript.length - 1;
|
||
} else {
|
||
// Insert the block within the list, above the drop point
|
||
script_BlockScript.splice(dp, 0, b);
|
||
script_BlockScriptSelectedId = dp;
|
||
}
|
||
fupdatescript();
|
||
haltEvent(e);
|
||
}
|
||
|
||
// Called when you hit "Edit..." on a block, displays a dialog box with all block attributes.
|
||
function script_foneditclick(h) {
|
||
if (xxdialogMode) return;
|
||
var block = script_BlockScript[h];
|
||
script_BlockScriptSelectedId = h;
|
||
fupdatescript();
|
||
|
||
if (block != null) {
|
||
var b = block['vars'] ? 7 : 5;
|
||
var c = block['desc'] + "<br><br>";
|
||
if (block['vars']) {
|
||
// For each variable in a block, add the proper input control for this variable and set the control to the current value.
|
||
for (var i in block['vars']) {
|
||
var d = block['vars'][i]['value'];
|
||
var attributes = '';
|
||
// Block values can be on type 1:Text,2:Number,3:DropDownList,4:password,5:MultiSelect
|
||
if (block['vars'][i]['maxlength']) attributes += (" maxlength=" + block['vars'][i]['maxlength']);
|
||
if (block['vars'][i]['type'] == 2) { attributes += " onkeypress='return numbersOnly(event)'" }
|
||
if (block['vars'][i]['type'] == 1 || block['vars'][i]['type'] == 2) { d = "<input title='" + block['vars'][i]['desc'] + "' id=scriptXvalue_" + i + " value='" + block['vars'][i]['value'] + "' " + attributes + " style=width:100%></input>"; }
|
||
if (block['vars'][i]['type'] == 3) {
|
||
d = "<select title='" + block['vars'][i]['desc'] + "' id=scriptXvalue_" + i + " style=width:100%;padding:0;margin:0>";
|
||
for (var j in block['vars'][i]['values']) { d += "<option value=" + j + (j == block['vars'][i]['value'] ? " selected" : "") + ">" + block['vars'][i]['values'][j] + "</option>"; }
|
||
d += "</select>";
|
||
}
|
||
if (block['vars'][i]['type'] == 4) { d = "<input type=password autocomplete=off title='" + block['vars'][i]['desc'] + "' id=scriptXvalue_" + i + " value='" + block['vars'][i]['value'] + "' " + attributes + " style=width:100%></input>"; }
|
||
if (block['vars'][i]['type'] == 5) { d = ''; }
|
||
if (block['vars'][i]['type'] == 6) { d = "<input type=file title='" + block['vars'][i]['desc'] + "' id=scriptXvalue_" + i + " " + attributes + " style=width:100%></input>"; }
|
||
c += '<table style=width:100% title="' + block['vars'][i]['desc'] + '"><td style=width:120px>' + block['vars'][i]['name'] + '<td><b>' + d + '</b></table>';
|
||
if (block['vars'][i]['type'] == 5) {
|
||
var x = '';
|
||
c += '<ul id=scriptXvalue_' + i + ' style="list-style-type:none;height:100px;overflow:auto;width:100%;border:1px solid #000;background-color:white;overflow-x:hidden;margin:0;padding:0">';
|
||
for (var y in block['vars'][i]['values']) {
|
||
var xc = '';
|
||
if (block['vars'][i]['value'].indexOf(y) >= 0) xc = ' checked';
|
||
c += '<li><label><input type=checkbox id=scriptXvaluex_' + i + '-' + y + '' + xc + '>' + block['vars'][i]['values'][y] + '</label></li>';
|
||
}
|
||
c += '</ul>';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 11 only)
|
||
setDialogMode(11, block.name, b, script_foneditclickEx, c, h);
|
||
}
|
||
|
||
// Called when the script block edit dialog is closed.
|
||
function script_foneditclickEx(button, tag) {
|
||
if (xxdialogMode) return;
|
||
if (button == 2) {
|
||
// Delete this block
|
||
script_BlockScript.splice(tag, 1);
|
||
if (script_BlockScriptSelectedId == tag) { script_BlockScriptSelectedId = null; }
|
||
} else {
|
||
// Change this block with new arguments
|
||
var block = script_BlockScript[tag];
|
||
if (block['vars']) {
|
||
for (var i in block['vars']) {
|
||
if (block['vars'][i]['type'] == 5) {
|
||
block['vars'][i]['value'] = [];
|
||
for (var y in block['vars'][i]['values']) { if (Q('scriptXvaluex_' + i + '-' + y).checked) block['vars'][i]['value'].push(y); }
|
||
} else if (block['vars'][i]['type'] == 6) {
|
||
var x = Q('scriptXvalue_' + i);
|
||
if (x.files.length == 1) {
|
||
var reader = new FileReader();
|
||
reader.onload = function(file) { block['vars'][i]['value'] = btoa(file.target.result); fupdatescript(); };
|
||
reader.readAsBinaryString(x.files[0]);
|
||
}
|
||
} else {
|
||
block['vars'][i]['value'] = Q('scriptXvalue_' + i).value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
fupdatescript();
|
||
}
|
||
|
||
// Starting with a child element, go up one parent at a time until we find an element with an ID.
|
||
function fgetParentWithId(x) { while (!x.id) { x = x.parentElement; } return x; }
|
||
|
||
// Displays the list of script blocks to the HTML page. Each block is displayed with it's name, attributes and an edit button.
|
||
function fupdatescript() {
|
||
var x = '';
|
||
for (var i in script_BlockScript) {
|
||
x += '<div id=xblock_' + i + ' style=cursor:pointer;min-height:24px;background-color:#' + ((script_BlockScriptSelectedId == i) ? 'aaa' : 'ccc') + ';width:auto;padding:5px;margin:2px draggable=true onclick=script_fonclick(event,this) ondragenter=script_fondragenter(event,this) ondragleave=script_fondragleave(event,this) ondragstart=script_fondragstart(event,this) ondragend=script_fondragend(event,this) ondrop=script_fondrop(event,this) title="' + script_BlockScript[i]['desc'] + '"';
|
||
x += '><input style=float:right type=button value=Edit... onclick=script_foneditclick(' + i + ')><div style=font-size:16px><b>' + script_BlockScript[i].name + '</b>';
|
||
if (script_BlockScript[i]['vars']) {
|
||
var r = 0;
|
||
x += "<table class='scriptBlockVar us' cellpadding=0 cellspacing=0 style=width:100%;border-radius:5px;margin-top:8px>";
|
||
for (var j in script_BlockScript[i]['vars']) {
|
||
var v = script_BlockScript[i]['vars'][j]['value'];
|
||
if (script_BlockScript[i]['vars'][j]['type'] == 4 && script_BlockScript[i]['vars'][j]['value'].length > 0) v = "*****";
|
||
if (script_BlockScript[i]['vars'][j]['type'] == 3) v = script_BlockScript[i]['vars'][j]['values'][script_BlockScript[i]['vars'][j]['value']];
|
||
if (script_BlockScript[i]['vars'][j]['type'] == 6) {
|
||
if (script_BlockScript[i]['vars'][j]['value']) { v = "Binary file, " + script_BlockScript[i]['vars'][j]['value'].length + " bytes"; } else { v = "Not set"; }
|
||
}
|
||
x += "<tr title='" + script_BlockScript[i]['vars'][j]['desc'] + "'><td width=200px style='" + (r > 0 ? "border-top:1px solid #a810a8" : "") + "'><p>" + script_BlockScript[i]['vars'][j]['name'] + "<td style='" + (r > 0 ? "border-top:1px solid #a810a8" : "") + "'>" + v;
|
||
r++;
|
||
}
|
||
x += "<tr><td style=height:3px></table>";
|
||
}
|
||
x += '</div></div>';
|
||
}
|
||
if (x == '') { x = '<div style="padding:15px;color:gray">Start your script by drag & dropping blocks from the left side into this scripting box.<div>'; }
|
||
QH('scriptblocks', x + "<div style=height:80px></div>");
|
||
for (var i in script_BlockScript) { QS('xblock_' + i).borderLeft = (script_CurrentHighlightBlock == i) ? "3px solid black" : ""; }
|
||
script_blocksToScript();
|
||
}
|
||
|
||
// Convert the list of blocks into a script that can be compiled
|
||
function script_blocksToScript() {
|
||
var script = '';
|
||
if (script_BuildingBlocks) {
|
||
if (script_BuildingBlocks['_start']) { script += '##### Starting Block #####\r\n' + script_BuildingBlocks['_start']['code'] + '\r\n\r\n'; }
|
||
for (var i in script_BlockScript) {
|
||
var code = script_BlockScript[i]['code'];
|
||
code = code.split("%%%~%%%").join(i);
|
||
for (var j in script_BlockScript[i]['vars']) { code = code.split("%%%" + j + "%%%").join(script_BlockScript[i]['vars'][j]['value']); }
|
||
script += '##### Block: ' + script_BlockScript[i]['name'] + ' #####\r\nHighlightBlock __t ' + i + '\r\n' + code + '\r\n\r\n';
|
||
}
|
||
if (script_BuildingBlocks['_end']) { script += '##### Ending Block #####\r\n' + script_BuildingBlocks['_end']['code'] + '\r\nHighlightBlock\r\n'; }
|
||
}
|
||
Q('scriptarea').value = script;
|
||
}
|
||
|
||
// Highlight a script block. This is useful to know what block the script is currently running
|
||
var script_CurrentHighlightBlock;
|
||
function script_HighlightBlock(runner, x) {
|
||
if (runner == editscriptstate) { // Only highlight blocks if the scrpt editor is running the script. If running a script on the page, don't highlight blocks in the editor.
|
||
script_CurrentHighlightBlock = x;
|
||
for (var i in script_BlockScript) { QS('xblock_' + i).borderLeft = (script_CurrentHighlightBlock == i) ? "3px solid black" : ""; }
|
||
}
|
||
}
|
||
|
||
// Called by the "New..." button to reset the script editor to the start
|
||
function script_newScriptDlg() {
|
||
if (xxdialogMode) return;
|
||
setDialogMode(11, "New Script", 3, script_newScriptDlgOk, "<br>Reset & clear the script editor?");
|
||
}
|
||
|
||
// Clear the script editor
|
||
function script_newScriptDlgOk() {
|
||
script_setBuildBlocks(script_StartingBuildingBlocks);
|
||
script_BlockScript = [];
|
||
script_BlockScriptSelectedId = null;
|
||
delete editscriptstate;
|
||
scriptViewButton(script_BuildingBlocks?1:0);
|
||
QH('variables', '');
|
||
Q('scriptarea').value = '';
|
||
resetScriptButton();
|
||
}
|
||
|
||
// Compile and reset a script to the starting position
|
||
function resetScriptButton() {
|
||
// Compile the script
|
||
breakScriptButton();
|
||
Q('compiledarea').value = rstr2hex(script_compile(Q('scriptarea').value, function(m) { messagebox("Script Compile Error", m); }));
|
||
delete editscriptstate;
|
||
|
||
// Reset the script environment
|
||
Q('console').value = '';
|
||
QH('variables', '');
|
||
QH('EditScriptStatus', 'Stopped');
|
||
if (script_BuilderView == 1) { fupdatescript(); }
|
||
if (Q('compiledarea').value.length == 0) return;
|
||
|
||
// Create a new script runtime object
|
||
var startvars = { '_interactive':1 };
|
||
|
||
startvars['_certificates'] = 1;
|
||
|
||
startvars['_mode'] = 'Firmware';
|
||
|
||
startvars['_mode'] = 'MeshCentral';
|
||
editscriptstate = script_setup(hex2rstr(Q('compiledarea').value), startvars);
|
||
editscriptstate.wsstack = wsstack;
|
||
editscriptstate.amtstack = amtstack;
|
||
editscriptstate.onStep = editscript_updateScriptState;
|
||
editscriptstate.onConsole = editscript_console;
|
||
editscript_updateScriptState(editscriptstate);
|
||
}
|
||
|
||
// Start running the script
|
||
function runScriptButton() {
|
||
if (editscriptstate == null) resetScriptButton();
|
||
if (editscriptstate == null) return;
|
||
editscriptstate.start(100);
|
||
}
|
||
|
||
// Stop/Pause the script
|
||
function breakScriptButton() {
|
||
if (editscriptstate == null) return;
|
||
editscriptstate.stop();
|
||
}
|
||
|
||
// Run one step of the script
|
||
function stepScriptButton() {
|
||
if (editscriptstate == null) resetScriptButton();
|
||
if (editscriptstate == null) return;
|
||
breakScriptButton();
|
||
editscriptstate.step();
|
||
}
|
||
|
||
// Display a new line into the script console
|
||
function editscript_console(msg) {
|
||
Q('console').value += (msg + '\n');
|
||
}
|
||
|
||
// Update the script state information and displayed variables
|
||
var script_states = ['Stopped', 'Running', 'Paused'];
|
||
function editscript_updateScriptState(runstate) {
|
||
// Update variables
|
||
var r = '';
|
||
if (runstate && runstate != null) {
|
||
var vs = [];
|
||
for (var i in runstate.variables) { if (!i.startsWith('__')) vs.push(i); }
|
||
vs.sort();
|
||
//for (var i in vs) { r += vs[i] + " = " + script_toString(runstate.variables[vs[i]]) + "\n"; }
|
||
for (var i in vs) {
|
||
if (typeof runstate.variables[vs[i]] == 'object') {
|
||
r += "<b>" + vs[i] + "</b> = " + ObjectToStringEx(runstate.variables[vs[i]], 2) + "<br>";
|
||
} else {
|
||
r += "<b>" + vs[i] + "</b> = " + EscapeHtml(script_toString(runstate.variables[vs[i]])) + "<br>";
|
||
}
|
||
}
|
||
}
|
||
QH('variables', r);
|
||
|
||
// Update state
|
||
r = 'Stopped, No Script';
|
||
if (runstate && runstate != null) { r = script_states[runstate.state]; if (runstate.state > 0) r += ", " + runstate.ip + " : " + script_decompile(runstate.script, runstate.ip); }
|
||
if (r.length > 50) { r = r.substring(0, 50) + "..."; }
|
||
QH('EditScriptStatus', r);
|
||
}
|
||
|
||
// Return a string from any input, if the input is an object, stringify it.
|
||
function script_toString(x) { if (typeof x == 'object') return JSON.stringify(x); return x; }
|
||
|
||
|
||
// Save a script to disk, start by showing a dialog box to prompt to the filename.
|
||
function script_saveScript(e) {
|
||
if (xxdialogMode || scriptstate) return;
|
||
if (e && (e.shiftKey == true)) {
|
||
// Save as script block
|
||
setDialogMode(11, "Script Block", 1, null, "<br><textarea id=scriptSaveScriptJsonBlock style=width:100%;height:200px;resize:vertical />");
|
||
QH('scriptSaveScriptJsonBlock', script_fConvertScriptToJsonBlock(Q('scriptarea').value));
|
||
} else {
|
||
// Save as .mescript file
|
||
|
||
setDialogMode(11, "Save Script", 3, script_saveScriptOk, "<br><input id=scriptsavename style=width:100% value=test.mescript >");
|
||
}
|
||
}
|
||
|
||
// Once the filename is selected, save the file.
|
||
function script_saveScriptOk() {
|
||
if (xxdialogMode) return;
|
||
var x = JSON.stringify({
|
||
"scriptText": Q('scriptarea').value,
|
||
"mescript": btoa(script_compile(Q('scriptarea').value)),
|
||
"blocks": script_StartingBuildingBlocks,
|
||
"scriptBlocks": script_BlockScript
|
||
}, null, ' ');
|
||
saveAs(data2blob(x), Q('scriptsavename').value);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// POPUP DIALOG
|
||
//
|
||
|
||
// undefined = Hidden, 1 = Generic Message
|
||
var xxdialogMode;
|
||
var xxdialogFunc;
|
||
var xxdialogButtons;
|
||
var xxdialogTag;
|
||
|
||
// Display a dialog box
|
||
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 11 only)
|
||
function setDialogMode(x, y, b, f, c, tag) {
|
||
xxdialogMode = x;
|
||
xxdialogFunc = f;
|
||
xxdialogButtons = b;
|
||
xxdialogTag = tag;
|
||
QE('idx_dlgOkButton', true);
|
||
QV('idx_dlgOkButton', b & 1);
|
||
QV('idx_dlgCancelButton', b & 2);
|
||
QV('id_dialogclose', b & 2);
|
||
QV('idx_dlgDeleteButton', b & 4);
|
||
if (y) QH('id_dialogtitle', y);
|
||
for (var i = 1; i < 26; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
|
||
QV('dialog', x);
|
||
if (c) { if (x == 11) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
|
||
}
|
||
|
||
function dialogclose(x) {
|
||
var f = xxdialogFunc, b = xxdialogButtons, t = xxdialogTag;
|
||
setDialogMode();
|
||
if (((b & 8) || x) && f) f(x, t);
|
||
}
|
||
|
||
function center() {
|
||
QS('dialog').left = ((((getDocWidth() - 400) / 2)) + "px");
|
||
|
||
//QS('id_mainarea_frame').height = Q('id_mainarea').offsetHeight;
|
||
}
|
||
function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
|
||
function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
|
||
|
||
//
|
||
// GENERIC METHODS
|
||
//
|
||
|
||
|
||
function SaveJsonFile(name, name2, desc, data) {
|
||
var n = '', r = {}, d = new Date();
|
||
if (amtsysstate) {
|
||
n = "-" + amtsysstate['AMT_GeneralSettings'].response['HostName'];
|
||
r = {'webappversion':version,'description':desc,'hostname':amtsysstate['AMT_GeneralSettings'].response['HostName'],'localtime':Date(),'utctime':new Date().toUTCString(),'isotime':new Date().toISOString()};
|
||
if (HardwareInventory) r['systemid'] = guidToStr(HardwareInventory['CIM_ComputerSystemPackage'].response["PlatformGUID"].toLowerCase());
|
||
}
|
||
n += '-' + d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2) + "-" + ("0" + d.getHours()).slice(-2) + "-" + ("0" + d.getMinutes()).slice(-2);;
|
||
r[name2] = data;
|
||
|
||
saveAs(data2blob(JSON.stringify(r, null, ' ').replace(/\n/g, '\r\n')), name + n + '.json');
|
||
}
|
||
|
||
var httpErrorTable = {
|
||
200: 'OK',
|
||
401: 'Authentication Error',
|
||
408: 'Timeout Error',
|
||
601: 'WSMAN Parsing Error',
|
||
602: 'Unable to parse HTTP response header',
|
||
603: 'Unexpected HTTP enum response',
|
||
604: 'Unexpected HTTP pull response',
|
||
}
|
||
function errcheck(s, stack) {
|
||
if (wsstack == null || amtstack != stack) return true;
|
||
if (s != 200 && s != 9) {
|
||
setDialogMode();
|
||
wsstack.comm.FailAllError = 999; // Cause all new responses to be silent
|
||
amtstack.CancelAllQueries(999);
|
||
QH('id_messageviewstr', ((httpErrorTable[s])?(httpErrorTable[s]):('Error #' + s)));
|
||
|
||
if (s == 401) { QH('id_messageviewstr', 'Authentication Error<br /><br /><input type=button value="Set new credentials" onclick=meshcentral2credCallback(true)></input>'); }
|
||
go(100);
|
||
QS('id_progressbar').width = 0;
|
||
|
||
}
|
||
return (s != 200);
|
||
}
|
||
|
||
|
||
function goiFrame(e,x,y) {
|
||
if (xxdialogMode) return;
|
||
go(x);
|
||
if ((e.shiftKey == true) || (Q('id_StorageIFrame').src.endsWith(y) == false)) Q('id_StorageIFrame').src = y;
|
||
QV('id_mainarea_pad', false);
|
||
QV('id_mainarea_frame', true);
|
||
}
|
||
|
||
function go(x, force) {
|
||
if (xxdialogMode && force != 1) return;
|
||
|
||
QV('id_mainarea_frame', false);
|
||
QV('id_mainarea_pad', true);
|
||
QV('id_messageview', x == 100);
|
||
QV('id_mainview', x < 100);
|
||
for (var i = 0; i < 80; i++) { // Edit this line when adding a new screen
|
||
QV('p' + i, i == x);
|
||
var q = QS('go' + i);
|
||
// ###BEGIN###{Look-Intel}
|
||
if (q) { q['background-color'] = ((i == x)?"#abcae1":""); } // #e8eefe
|
||
// ###END###{Look-Intel}
|
||
|
||
if (q) { q['background-color'] = ((i == x)?"gray":""); }
|
||
}
|
||
currentView = x;
|
||
}
|
||
|
||
function addLink(x, f) { return "<a style=cursor:pointer;color:blue onclick='" + f + "'>♦ " + x + "</a>"; }
|
||
function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
|
||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||
function addOption(q, t, i) { var option = document.createElement("option"); option.text = t; option.value = i; Q(q).add(option); }
|
||
function addDisabledOption(q, t, i) { var option = document.createElement("option"); option.text = t; option.value = i; option.disabled = 1; Q(q).add(option); }
|
||
function passwordcheck(p) { if (p.length < 8) return false; var upper = 0, lower = 0, number = 0, nonalpha = 0; for (var i in p) { var c = p.charCodeAt(i); if ((c > 64) && (c < 91)) { upper = 1; } else if ((c > 96) && (c < 123)) { lower = 1; } else if ((c > 47) && (c < 58)) { number = 1; } else { nonalpha = 1; } } return ((upper + lower + number + nonalpha) == 4); }
|
||
function methodcheck(r) { if (r && r != null && r.Body && r.Body['ReturnValue'] != 0) { messagebox("Call Error", r.Header['Method'] + ": " + (r.Body.ReturnValueStr + '').replace("_", " ")); return true; } return false; }
|
||
function TableStart() { return "<table class='log1 us' cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td width=200px><p><td>"; }
|
||
function TableStart2() { return "<table class='log1 us' cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td><p><td>"; }
|
||
function TableEntry(n, v) { return "<tr><td class=r1><p>" + n + "<td class=r1>" + v; }
|
||
function FullTable(x, e) { var r = TableStart(); for (i in x) { if (i && x[i]) r += TableEntry(i, x[i]); } return r + TableEnd(e); }
|
||
function TableEnd(n) { return "<tr><td colspan=2><p>" + (n?n:'') + "</table>"; }
|
||
function AddButton(v, f) { return "<input type=button value='" + v + "' onclick='" + f + "' style=margin:4px>"; }
|
||
function AddButton2(v, f) { return "<input type=button value='" + v + "' onclick='" + f + "'>"; }
|
||
function AddRefreshButton(f) { return "<input type=button name=refreshbtn value=Refresh onclick='refreshButtons(false);" + f + "' style=margin:4px " + (refreshButtonsState==false?"disabled":"") + ">"; }
|
||
function MoreStart() { return "<a style=cursor:pointer;color:blue id=morexxx1 onclick=QV(\"morexxx1\",false);QV(\"morexxx2\",true)>▼ More</a><div id=morexxx2 style=display:none><br><hr>"; };
|
||
function MoreEnd() { return "<a style=cursor:pointer;color:blue onclick=QV(\"morexxx2\",false);QV(\"morexxx1\",true)>▲ Less</a></div>"; };
|
||
function getSelectedOptions(sel) { var opts = [], opt; for (var i = 0, len = sel.options.length; i < len; i++) { opt = sel.options[i]; if (opt.selected) { opts.push(opt.value); } } return opts; }
|
||
function getInstance(x, y) { for (var i in x) { if (x[i]["InstanceID"] == y) return x[i]; } return null; }
|
||
function getItem(x, y, z) { for (var i in x) { if (x[i][y] == z) return x[i]; } return null; }
|
||
function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); }
|
||
function getUrlVars() { var j, hash, vars = [], hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); for (var i = 0; i < hashes.length; i++) { j = hashes[i].indexOf('='); if (j > 0) { vars[hashes[i].substring(0, j)] = hashes[i].substring(j + 1, hashes[i].length); } } return vars; }
|
||
function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
|
||
function getDocHeight() { if (window.innerHeight) return window.innerHeight; if (document.documentElement && document.documentElement.clientHeight && document.documentElement.clientHeight != 0) return document.documentElement.clientHeight; return document.getElementsByTagName('body')[0].clientHeight; }
|
||
function addHtmlValue(t, v) { return '<div style=height:20px><div style=float:right;width:220px;overflow:hidden><b title="' + v + '">' + v + '</b></div><div>' + t + '</div></div>'; }
|
||
function addHtmlValueNoTitle(t, v) { return '<div style=height:20px><div style=float:right;width:220px;overflow:hidden>' + v + '</b></div><div>' + t + '</div></div>'; }
|
||
function numbersOnly(e, x) { return (event.charCode == 0) || (event.charCode == x) || (event.charCode >= 48 && event.charCode <= 57); }
|
||
|
||
|
||
|
||
// Get things started
|
||
startup();
|
||
</script> |