Verify Email Authenticity

Verify the ARC (Authenticated Received Chain) signature of an email to ensure its authenticity and integrity, especially for messages that have been forwarded or handled by multiple mail servers.

Raw Email
Upload File

Drag & drop your .eml file here, or click to select

tab.classList.remove('active'); } }); // Update tab contents tabContents.forEach(content => { if (content.id === `${tabName}-tab`) { content.classList.add('active'); } else { content.classList.remove('active'); } }); // Update verify button state updateVerifyButtonState(); } // Add click handlers to tabs tabs.forEach(tab => { tab.addEventListener('click', () => { const tabName = tab.getAttribute('data-tab'); switchTab(tabName); }); }); // Clear file function window.clearFile = function() { currentFile = null; fileInput.value = ''; fileInfo.classList.add('hidden'); updateVerifyButtonState(); }; // File upload handling fileInput.addEventListener('change', (e) => { if (e.target.files && e.target.files.length > 0) { handleFile(e.target.files[0]); } }); // Handle drag and drop ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, preventDefaults, false); document.body.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, highlight, false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, unhighlight, false); }); function highlight() { dropZone.style.borderColor = '#4a6fa5'; dropZone.style.backgroundColor = 'rgba(74, 111, 165, 0.05)'; } function unhighlight() { dropZone.style.borderColor = ''; dropZone.style.backgroundColor = ''; } dropZone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const file = dt.files[0]; if (file) { handleFile(file); } }); async function handleFile(file) { // Check file type const validTypes = ['message/rfc822', 'application/mbox', 'text/plain']; const fileExt = file.name.split('.').pop().toLowerCase(); if (!validTypes.includes(file.type) && !['eml', 'mbox', 'txt'].includes(fileExt)) { showError('Invalid file type. Please upload an .eml or .mbox file.'); return; } currentFile = file; // Update UI fileName.textContent = file.name; fileSize.textContent = formatFileSize(file.size); fileInfo.classList.remove('hidden'); // Read file content const content = await file.text(); emailContent.value = content; updateVerifyButtonState(); } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Update verify button state based on input function updateVerifyButtonState() { const pasteTab = document.querySelector('.tab[data-tab="paste"]'); const isPasteTab = pasteTab && pasteTab.classList.contains('active'); const hasContent = isPasteTab ? (emailContent && emailContent.value.trim() !== '') : (currentFile !== null); if (verifyBtn) { verifyBtn.disabled = !hasContent; } } emailContent.addEventListener('input', updateVerifyButtonState); // Verify button click handler verifyBtn.addEventListener('click', async () => { let content = emailContent.value.trim(); if (!content && currentFile) { content = await currentFile.text(); } if (!content) { showError('Please provide email content to verify.'); return; } verifyArcSignature(content); }); // Verify ARC signature async function verifyArcSignature(emailContent) { // Show loading state loading.style.display = 'block'; resultDiv.className = 'result'; resultDiv.style.display = 'none'; try { // Basic validation if (!emailContent || typeof emailContent !== 'string' || emailContent.trim() === '') { throw new Error('No email content provided'); } // Check for minimum required headers if (!emailContent.includes('ARC-Seal:') || !emailContent.includes('ARC-Message-Signature:')) { throw new Error('Email does not contain required ARC headers. Make sure to include the full email headers.'); } // Use the deployed worker const workerUrl = 'https://arc-verifier.denis3015.workers.dev'; try { const response = await fetch(workerUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email: emailContent }) }); const result = await response.json(); if (!response.ok || !result.success) { const errorMsg = result.error || 'Failed to verify ARC signature'; console.error('Verification error:', errorMsg, result); throw new Error(errorMsg); } displayResult(result); } catch (error) { console.error('Request failed:', error); throw new Error(`Network error: ${error.message}`); } } catch (error) { // If we have a detailed error message, use it let errorMessage = error.message || 'An error occurred while verifying the ARC signature.'; // Add more context to common errors if (errorMessage.includes('No ARC headers found')) { errorMessage += '\n\nThis usually means the email was not properly forwarded with full headers.'; } else if (errorMessage.includes('DKIM verification failed')) { errorMessage += '\n\nThis could be due to missing or invalid DKIM signatures.'; } else if (errorMessage.includes('ARC-Seal')) { errorMessage += '\n\nThis indicates the ARC chain might be broken or incomplete.'; } showError(errorMessage); } finally { loading.style.display = 'none'; } } function createSection(title, className = '') { const section = document.createElement('div'); section.className = `result-section ${className}`; const titleEl = document.createElement('h3'); titleEl.textContent = title; section.appendChild(titleEl); return { section, content: section }; } function createStatusBadge(text, type = 'info') { const badge = document.createElement('span'); badge.className = `status-badge ${type}`; badge.textContent = text; return badge; } function createKeyValuePair(key, value, isCode = false) { const row = document.createElement('div'); row.className = 'key-value-pair'; const keyEl = document.createElement('span'); keyEl.className = 'key'; keyEl.textContent = key; const valueEl = document.createElement('span'); valueEl.className = `value ${isCode ? 'code' : ''}`; valueEl.textContent = value; row.appendChild(keyEl); row.appendChild(valueEl); return row; } function createStatusHeader(result) { const statusDiv = document.createElement('div'); const isSuccess = result.verified; statusDiv.className = `verification-status ${isSuccess ? 'success' : 'warning'}`; const statusIcon = document.createElement('i'); statusIcon.className = `fas ${isSuccess ? 'fa-check-circle' : 'fa-exclamation-triangle'}`; const statusText = document.createElement('span'); statusText.className = 'status-text'; statusText.textContent = isSuccess ? 'ARC Verification Successful' : 'ARC Verification Issues Found'; statusDiv.appendChild(statusIcon); statusDiv.appendChild(statusText); if (result.chainStatus) { const chainStatus = document.createElement('div'); chainStatus.className = 'chain-status'; chainStatus.textContent = `Chain Status: ${result.chainStatus.toUpperCase()}`; statusDiv.appendChild(chainStatus); } return statusDiv; } function createSummarySection(result) { const { section: summarySection, content: summaryContent } = createSection('Summary'); if (result.details.verificationSteps && result.details.verificationSteps.length > 0) { const stepsList = document.createElement('div'); stepsList.className = 'steps-list'; result.details.verificationSteps.forEach(step => { const stepItem = document.createElement('div'); stepItem.className = 'step-item'; const stepIcon = document.createElement('i'); stepIcon.className = 'fas fa-check-circle success'; const stepText = document.createElement('span'); stepText.textContent = step; stepItem.appendChild(stepIcon); stepItem.appendChild(stepText); stepsList.appendChild(stepItem); }); summaryContent.appendChild(stepsList); } return summarySection; } function createChainAnalysisSection(result) { if (!result.chainAnalysis) return null; const { section: chainSection, content: chainContent } = createSection('Chain Analysis'); if (result.chainStatus) { chainContent.appendChild(createKeyValuePair('Chain Status', result.chainStatus.toUpperCase())); } if (result.chainAnalysis.hopCount) { chainContent.appendChild(createKeyValuePair('Number of Hops', result.chainAnalysis.hopCount)); } if (result.chainAnalysis.cvStatus) { const cvStatus = document.createElement('div'); cvStatus.className = 'detail-item'; cvStatus.innerHTML = ` Chain Validation Status: ${result.chainAnalysis.cvStatus.toUpperCase()} `; chainContent.appendChild(cvStatus); } return chainSection; } function createAuthResultsSection(result) { if (!result.authResults) return null; const { section: authSection, content: authContent } = createSection('Authentication Results'); // Helper function to create auth result row const createAuthResult = (type, authData) => { if (!authData) return null; const authDiv = document.createElement('div'); authDiv.className = 'auth-result'; authDiv.innerHTML = ` ${type} ${authData.result ? authData.result.toUpperCase() : 'N/A'} ${authData.details || ''} `; return authDiv; }; // Add SPF, DKIM, and DMARC results if (result.authResults.spf) { const spfDiv = createAuthResult('SPF', result.authResults.spf); if (spfDiv) authContent.appendChild(spfDiv); } if (result.authResults.dkim) { const dkimDiv = createAuthResult('DKIM', result.authResults.dkim); if (dkimDiv) authContent.appendChild(dkimDiv); } if (result.authResults.dmarc) { const dmarcDiv = createAuthResult('DMARC', result.authResults.dmarc); if (dmarcDiv) authContent.appendChild(dmarcDiv); } return authSection; } function createWarningsSection(result) { if (!result.warnings || result.warnings.length === 0) return null; const { section: warningsSection, content: warningsContent } = createSection('Warnings', 'warnings'); const warningsList = document.createElement('div'); warningsList.className = 'warnings-list'; result.warnings.forEach(warning => { const warningItem = document.createElement('div'); warningItem.className = 'warning-item'; warningItem.innerHTML = ` ${warning} `; warningsList.appendChild(warningItem); }); warningsContent.appendChild(warningsList); return warningsSection; } function createRawDetailsSection(result) { const { section: detailsSection } = createSection('Raw Details', 'raw-details'); detailsSection.classList.add('collapsible'); const detailsToggle = document.createElement('div'); detailsToggle.className = 'details-toggle'; detailsToggle.innerHTML = ` Show Raw Details `; const detailsContentWrapper = document.createElement('div'); detailsContentWrapper.className = 'details-content'; detailsContentWrapper.style.display = 'none'; const pre = document.createElement('pre'); pre.textContent = JSON.stringify(result, null, 2); detailsContentWrapper.appendChild(pre); detailsToggle.addEventListener('click', () => { const isHidden = detailsContentWrapper.style.display === 'none'; detailsContentWrapper.style.display = isHidden ? 'block' : 'none'; const icon = detailsToggle.querySelector('i'); const span = detailsToggle.querySelector('span'); icon.className = isHidden ? 'fas fa-chevron-up' : 'fas fa-chevron-down'; span.textContent = isHidden ? 'Hide Raw Details' : 'Show Raw Details'; }); detailsSection.appendChild(detailsToggle); detailsSection.appendChild(detailsContentWrapper); return detailsSection; } function createHelpSection(result) { if (result.verified) return null; const helpSection = document.createElement('div'); helpSection.className = 'help-section'; helpSection.innerHTML = `

Need Help?

This email failed ARC verification. Here are some common issues:

  • The email might have been modified in transit
  • One or more mail servers in the delivery path might not properly handle ARC
  • The email might be missing required ARC headers

Check the raw details above for more specific information about the verification failure.

`; return helpSection; } function displayResult(result) { // Clear previous results resultDiv.className = 'result'; resultTitle.textContent = 'ARC Verification Results'; resultContent.innerHTML = ''; // Create main result card const card = document.createElement('div'); card.className = 'result-card'; // Add status header card.appendChild(createStatusHeader(result)); // Add summary section card.appendChild(createSummarySection(result)); // Add chain analysis section if available const chainSection = createChainAnalysisSection(result); if (chainSection) card.appendChild(chainSection); // Add auth results section if available const authSection = createAuthResultsSection(result); if (authSection) card.appendChild(authSection); // Add warnings section if any warnings const warningsSection = createWarningsSection(result); if (warningsSection) card.appendChild(warningsSection); // Always add raw details section (collapsible) card.appendChild(createRawDetailsSection(result)); // Add help section if verification failed const helpSection = createHelpSection(result); if (helpSection) card.appendChild(helpSection); // Add the card to the result content resultContent.appendChild(card); // Add styles if they don't exist if (!document.getElementById('arc-verifier-styles')) { const style = document.createElement('style'); style.id = 'arc-verifier-styles'; style.textContent = ` .result-card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-top: 20px; } .status-badge { display: inline-block; padding: 5px 10px; border-radius: 4px; font-weight: bold; margin-bottom: 15px; } .status-badge.success { background-color: #d4edda; color: #155724; } .status-badge.error { background-color: #f8d7da; color: #721c24; } .verification-row { display: flex; margin: 10px 0; padding: 10px; background: #f8f9fa; border-radius: 4px; } .verification-row .label { flex: 1; font-weight: 500; } .verification-row .value { font-weight: bold; } .verification-row .value.success { color: #28a745; } .verification-row .value.error { color: #dc3545; } .issues-section { margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; } .issues-section h4 { margin-top: 0; color: #856404; } .issues-section ul { margin-bottom: 0; padding-left: 20px; } .help-text { margin-top: 20px; padding: 15px; background: #e2f0fd; border-radius: 4px; font-size: 0.9em; } .help-text h4 { margin-top: 0; color: #004085; } details.raw-details { margin-top: 20px; padding: 10px; background: #f8f9fa; border-radius: 4px; } details.raw-details summary { cursor: pointer; font-weight: 500; color: #6c757d; } details.raw-details pre { margin-top: 10px; padding: 10px; background: white; border: 1px solid #dee2e6; border-radius: 4px; max-height: 300px; overflow: auto; }`; document.head.appendChild(style); } // Scroll to the results resultDiv.scrollIntoView({ behavior: 'smooth' }); } function showError(message) { resultDiv.className = 'result error'; resultTitle.textContent = 'Error'; resultContent.textContent = message; resultDiv.style.display = 'block'; } // Initial button state updateVerifyButtonState(); });