Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 139 additions & 27 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ <h2 class="fw-bold text-dark mb-1">Evidence Validation Suite</h2>
<i class="bi bi-filetype-csv"></i> CSV Report
</button>
</div>
<div class="col-6 col-md-2">
<button class="btn-tile bg-csv" onclick="downloadSelectedExcel()">
<i class="bi bi-file-earmark-spreadsheet"></i> Excel Report
</button>
</div>
<div class="col-6 col-md-2">
<button class="btn-tile bg-pdf" onclick="generateHashPDF()">
<i class="bi bi-file-earmark-pdf-fill"></i> Hash PDF
Expand Down Expand Up @@ -287,16 +292,26 @@ <h5 class="fw-bold">Drag & Drop Evidence Files Here</h5>
<tr class="table-header">
<th width="5%" class="ps-4"><input type="checkbox" id="selectAll" onclick="toggleAll(this)"></th>
<th width="5%">#</th>
<th width="30%">Filename</th>
<th width="20%">MD5 Hash</th>
<th width="40%">SHA-256 (Section 63 BSA Compliant)</th>
<th width="25%">Filename</th>
<th width="18%">MD5 Hash</th>
<th width="24%">SHA-256 (Section 63 BSA Compliant)</th>
<th width="23%">SHA-3 / Keccak-256</th>
</tr>
</thead>
<tbody id="hashTableBody" style="background: white;"></tbody>
</table>
</div>
</div>

<div class="mt-3 shadow-sm border rounded bg-dark text-white">
<div class="px-3 py-2 border-bottom border-secondary fw-bold">
<i class="bi bi-terminal"></i> System Console
</div>
<div id="systemConsole" class="p-3 small" style="min-height: 70px; max-height: 140px; overflow-y: auto; font-family: 'Courier New', monospace;">
Ready for client-side evidence hashing.
</div>
</div>

</div>
</div>

Expand Down Expand Up @@ -486,17 +501,19 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
const rowId = `row-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
const isEml = file.name.toLowerCase().endsWith('.eml');
const rowCount = tableBody.rows.length + 1;
const safeFileName = escapeHtml(file.name);

tableBody.insertAdjacentHTML('beforeend', `
<tr id="${rowId}">
<td class="ps-4"><input type="checkbox" name="fileSelect" checked></td>
<td>${rowCount}</td>
<td class="fw-bold text-dark">
${file.name}
${safeFileName}
${isEml ? '<span class="badge bg-danger text-white ms-2">EMAIL</span>' : ''}
</td>
<td class="hash-font md5">Calculating...</td>
<td class="hash-font sha256">Calculating...</td>
<td class="hash-font sha3">Calculating...</td>
</tr>
`);
fileQueue.push({ file, rowId });
Expand All @@ -517,8 +534,11 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
const wordArray = CryptoJS.lib.WordArray.create(e.target.result);
setTimeout(() => {
if (row) {
row.querySelector('.md5').innerText = CryptoJS.MD5(wordArray).toString();
row.querySelector('.sha256').innerText = CryptoJS.SHA256(wordArray).toString();
const sha3 = CryptoJS.SHA3(wordArray, { outputLength: 256 }).toString();
row.querySelector('.md5').innerText = CryptoJS.MD5(wordArray).toString();
row.querySelector('.sha256').innerText = CryptoJS.SHA256(wordArray).toString();
row.querySelector('.sha3').innerText = sha3;
logSystemMessage(`SHA-3 / Keccak-256 for ${file.name}: ${sha3}`);
}
processQueue();
}, 50);
Expand Down Expand Up @@ -612,7 +632,7 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>

const text = await readFile(files[i]);
const emailData = parseEml(text);
const sha256 = await calculateSHA256(files[i]);
const hashes = await calculateHashValues(files[i]);

// HEADER WITH WRAPPED FILENAME
doc.setFillColor(0, 51, 102);
Expand Down Expand Up @@ -646,7 +666,8 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
['Message-ID', emailData.messageId],
['Originating IP', emailData.serverIP],
['DKIM Status', emailData.dkim],
['SHA-256 Hash', sha256]
['SHA-256 Hash', hashes.sha256],
['SHA-3 / Keccak-256 Hash', hashes.sha3]
],
theme: 'grid',
headStyles: {
Expand Down Expand Up @@ -715,7 +736,9 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
rows.push([
cells[1].innerText,
cells[2].innerText,
cells[4].innerText
cells[3].innerText,
cells[4].innerText,
cells[5].innerText
]);
}
});
Expand All @@ -727,7 +750,7 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>

doc.autoTable({
startY: 70,
head: [['#', 'Filename', 'SHA-256']],
head: [['#', 'Filename', 'MD5', 'SHA-256', 'SHA-3 / Keccak-256']],
body: rows,
theme: 'grid',
headStyles: {
Expand All @@ -736,13 +759,15 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
fontStyle: 'bold'
},
bodyStyles: {
fontSize: 9,
fontSize: 7,
font: 'courier'
},
columnStyles: {
0: { cellWidth: 40 },
1: { cellWidth: 300 },
2: { cellWidth: 450, font: 'courier' }
0: { cellWidth: 35 },
1: { cellWidth: 170 },
2: { cellWidth: 130, font: 'courier' },
3: { cellWidth: 210, font: 'courier' },
4: { cellWidth: 210, font: 'courier' }
}
});

Expand Down Expand Up @@ -782,6 +807,13 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
alignment: AlignmentType.CENTER
})],
shading: { fill: "003366" }
}),
new TableCell({
children: [new Paragraph({
children: [new TextRun({ text: "SHA-3 / Keccak-256", bold: true, color: "FFFFFF" })],
alignment: AlignmentType.CENTER
})],
shading: { fill: "003366" }
})
]
}));
Expand All @@ -795,6 +827,9 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
new TableCell({ children: [new Paragraph(cells[2].innerText)] }),
new TableCell({ children: [new Paragraph({
children: [new TextRun({ text: cells[4].innerText, font: "Courier New", size: 18 })]
})] }),
new TableCell({ children: [new Paragraph({
children: [new TextRun({ text: cells[5].innerText, font: "Courier New", size: 18 })]
})] })
]
}));
Expand Down Expand Up @@ -837,33 +872,86 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>

// ===== DOWNLOAD CSV =====
function downloadSelectedCSV() {
let csv = "#,Subject,From,To,Date,Originating IP,DKIM,SHA-256\n";

let counter = 1;
let csv = "#,Filename,MD5,SHA-256,SHA-3 / Keccak-256,Subject,From,To,Date,Originating IP,DKIM\n";
const promises = [];

document.querySelectorAll("#hashTableBody tr").forEach(tr => {
if (tr.querySelector('input').checked && tr.fileData && tr.fileData.name.toLowerCase().endsWith('.eml')) {
if (tr.querySelector('input').checked && tr.fileData) {
const cells = tr.querySelectorAll('td');
promises.push(
readFile(tr.fileData).then(text => {
const eml = parseEml(text);
return `${counter++},"${eml.subject}","${eml.from}","${eml.to}","${eml.date}","${eml.serverIP}","${eml.dkim}","${cells[4].innerText}"\n`;
})
);
const base = [
cells[1].innerText,
tr.fileData.name,
cells[3].innerText,
cells[4].innerText,
cells[5].innerText
];

if (tr.fileData.name.toLowerCase().endsWith('.eml')) {
promises.push(
readFile(tr.fileData).then(text => {
const eml = parseEml(text);
return toCsvRow([...base, eml.subject, eml.from, eml.to, eml.date, eml.serverIP, eml.dkim]);
})
);
} else {
promises.push(Promise.resolve(toCsvRow([...base, "", "", "", "", "", ""])));
}
}
});

if (promises.length === 0) {
alert("Please select files to include in report.");
return;
}

Promise.all(promises).then(rows => {
csv += rows.join('');
const blob = new Blob([csv], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = "Email_Report.csv";
a.download = "Hash_Report.csv";
a.click();
});
}

// ===== DOWNLOAD EXCEL =====
function downloadSelectedExcel() {
const rows = [["#", "Filename", "MD5", "SHA-256", "SHA-3 / Keccak-256"]];
document.querySelectorAll("#hashTableBody tr").forEach(tr => {
if (tr.querySelector('input').checked && tr.fileData) {
const cells = tr.querySelectorAll('td');
rows.push([
cells[1].innerText,
tr.fileData.name,
cells[3].innerText,
cells[4].innerText,
cells[5].innerText
]);
}
});

if (rows.length === 1) {
alert("Please select files to include in report.");
return;
}

const htmlRows = rows.map((row, rowIndex) => {
const tag = rowIndex === 0 ? "th" : "td";
return `<tr>${row.map(cell => `<${tag}>${escapeHtml(String(cell))}</${tag}>`).join("")}</tr>`;
}).join("");
const worksheet = `
<html>
<head><meta charset="UTF-8"></head>
<body><table border="1">${htmlRows}</table></body>
</html>
`;
const blob = new Blob([worksheet], { type: "application/vnd.ms-excel" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "Hash_Report.xls";
a.click();
}

// ===== UTILITY FUNCTIONS =====
function getSelectedEmlFiles() {
const files = [];
Expand All @@ -883,17 +971,41 @@ <h6 class="fw-bold mt-3">Yahoo/Outlook:</h6>
});
}

async function calculateSHA256(file) {
async function calculateHashValues(file) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => {
const wordArray = CryptoJS.lib.WordArray.create(e.target.result);
resolve(CryptoJS.SHA256(wordArray).toString());
resolve({
sha256: CryptoJS.SHA256(wordArray).toString(),
sha3: CryptoJS.SHA3(wordArray, { outputLength: 256 }).toString()
});
};
reader.readAsArrayBuffer(file);
});
}

function toCsvRow(values) {
return values.map(value => `"${String(value).replace(/"/g, '""')}"`).join(",") + "\n";
}

function escapeHtml(value) {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

function logSystemMessage(message) {
const systemConsole = document.getElementById("systemConsole");
const timestamp = new Date().toLocaleTimeString();
systemConsole.insertAdjacentHTML("beforeend", `<div>[${timestamp}] ${escapeHtml(message)}</div>`);
systemConsole.scrollTop = systemConsole.scrollHeight;
console.info(message);
}

function toggleAll(checkbox) {
document.querySelectorAll('input[name="fileSelect"]').forEach(c => c.checked = checkbox.checked);
}
Expand Down