File size: 3,020 Bytes
0da308d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
Utility functions for DFIR iOS Enhancement Application
"""

from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Optional
import hashlib
import sqlite3

# Apple epoch (January 1, 2001)
APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc)


def apple_time_to_datetime(ts: Any) -> Optional[datetime]:
    """
    Convert Apple timestamp to Python datetime.
    
    Apple uses a custom epoch starting from January 1, 2001.
    
    Args:
        ts: Apple timestamp (can be seconds or nanoseconds)
        
    Returns:
        datetime object or None if conversion fails
    """
    if ts is None:
        return None
    try:
        ts = float(ts)
        # Handle nanoseconds
        if ts > 1e12:
            ts = ts / 1e9
        return APPLE_EPOCH + timedelta(seconds=ts)
    except Exception:
        return None


def normalize_phone(phone: Optional[str]) -> Optional[str]:
    """
    Normalize phone number by removing common formatting characters.
    
    Args:
        phone: Raw phone number string
        
    Returns:
        Normalized phone number or None
    """
    if not phone:
        return phone
    value = str(phone).replace("+1", "").replace(" ", "").replace("-", "")
    value = value.replace("(", "").replace(")", "")
    return value.strip() or None


def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
    """
    Calculate SHA256 hash of a file.
    
    Args:
        path: Path to the file
        chunk_size: Size of chunks to read (default 1MB)
        
    Returns:
        Hexadecimal SHA256 hash string
    """
    h = hashlib.sha256()
    with path.open("rb") as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            h.update(chunk)
    return h.hexdigest()


def get_manifest_files(manifest_db: Path) -> list:
    """
    Query Manifest.db for file entries.
    
    Args:
        manifest_db: Path to Manifest.db
        
    Returns:
        List of file entries from manifest
    """
    if not manifest_db.exists():
        return []
    
    conn = sqlite3.connect(str(manifest_db))
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    
    try:
        cursor.execute("""
            SELECT fileID, domain, relativePath, flags, file
            FROM Files
            WHERE relativePath IS NOT NULL
        """)
        return [dict(row) for row in cursor.fetchall()]
    except Exception:
        return []
    finally:
        conn.close()


def find_artifact_in_manifest(manifest_files: list, artifact_name: str) -> Optional[dict]:
    """
    Find a specific artifact in manifest files.
    
    Args:
        manifest_files: List of manifest file entries
        artifact_name: Name of artifact to find
        
    Returns:
        File entry dict or None
    """
    for file_entry in manifest_files:
        if artifact_name in (file_entry.get('relativePath') or ''):
            return file_entry
    return None