diff --git a/src/algorithms/index.ts b/src/algorithms/index.ts new file mode 100644 index 0000000..fc71b6f --- /dev/null +++ b/src/algorithms/index.ts @@ -0,0 +1,94 @@ +// OTP Algorithm Registry +// Central registry for all OTP algorithm versions + +import { OTPAlgorithm, OTPVersion, DEFAULT_OTP_VERSION, LATEST_OTP_VERSION, OTP_VERSION_LABELS, OTP_VERSION_DESCRIPTIONS } from './types'; +import otpV0Algorithm from './otp_v0'; +import otpV1Algorithm from './otp_v1'; + +// Algorithm registry +const algorithms: Map = new Map([ + ['otp_v0', otpV0Algorithm], + ['otp_v1', otpV1Algorithm], +]); + +/** + * Get an OTP algorithm by version + */ +export function getAlgorithm(version: OTPVersion): OTPAlgorithm { + const algorithm = algorithms.get(version); + if (!algorithm) { + console.warn(`Unknown OTP version: ${version}, falling back to ${DEFAULT_OTP_VERSION}`); + const defaultAlgorithm = algorithms.get(DEFAULT_OTP_VERSION); + if (!defaultAlgorithm) { + throw new Error(`Default OTP algorithm (${DEFAULT_OTP_VERSION}) is not registered`); + } + return defaultAlgorithm; + } + return algorithm; +} + +/** + * Get all available OTP versions + */ +export function getAvailableVersions(): OTPVersion[] { + return Array.from(algorithms.keys()); +} + +/** + * Check if a version is valid + */ +export function isValidVersion(version: string): version is OTPVersion { + return algorithms.has(version as OTPVersion); +} + +/** + * Encrypt a message using the specified OTP version + */ +export function encryptWithVersion( + message: string, + rawKey: string, + offset: number, + version: OTPVersion = DEFAULT_OTP_VERSION +): string { + const algorithm = getAlgorithm(version); + const transformedKey = algorithm.transformKey(rawKey); + return algorithm.encrypt(message, transformedKey, offset); +} + +/** + * Decrypt a message using the specified OTP version + */ +export function decryptWithVersion( + encrypted: string, + rawKey: string, + offset: number, + version: OTPVersion = DEFAULT_OTP_VERSION +): string { + const algorithm = getAlgorithm(version); + const transformedKey = algorithm.transformKey(rawKey); + return algorithm.decrypt(encrypted, transformedKey, offset); +} + +/** + * Transform a raw key using the specified OTP version + */ +export function transformKeyWithVersion( + rawKey: string, + version: OTPVersion = DEFAULT_OTP_VERSION +): string { + const algorithm = getAlgorithm(version); + return algorithm.transformKey(rawKey); +} + +// Re-export types and constants +export { + OTPAlgorithm, + OTPVersion, + DEFAULT_OTP_VERSION, + LATEST_OTP_VERSION, + OTP_VERSION_LABELS, + OTP_VERSION_DESCRIPTIONS, +}; + +// Export individual algorithms +export { otpV0Algorithm, otpV1Algorithm }; diff --git a/src/algorithms/otp_v0/index.ts b/src/algorithms/otp_v0/index.ts new file mode 100644 index 0000000..e73d201 --- /dev/null +++ b/src/algorithms/otp_v0/index.ts @@ -0,0 +1,74 @@ +// OTP v0 Algorithm - Legacy Implementation +// This is the original OTP algorithm with basic key transformation + +import { OTPAlgorithm, OTPVersion } from '../types'; + +/** + * Simple XOR-based encryption using key stream + */ +function xorEncrypt(message: string, key: string, offset: number): string { + const result: number[] = []; + const keyLength = key.length; + + if (keyLength === 0) { + return message; + } + + for (let i = 0; i < message.length; i++) { + const messageCharCode = message.charCodeAt(i); + const keyIndex = (offset + i) % keyLength; + const keyCharCode = key.charCodeAt(keyIndex); + result.push(messageCharCode ^ keyCharCode); + } + + // Convert to base64 for safe transmission + return btoa(String.fromCharCode(...result)); +} + +/** + * Simple XOR-based decryption using key stream + */ +function xorDecrypt(encrypted: string, key: string, offset: number): string { + const keyLength = key.length; + + if (keyLength === 0) { + return encrypted; + } + + try { + // Decode from base64 + const decoded = atob(encrypted); + const result: number[] = []; + + for (let i = 0; i < decoded.length; i++) { + const encryptedCharCode = decoded.charCodeAt(i); + const keyIndex = (offset + i) % keyLength; + const keyCharCode = key.charCodeAt(keyIndex); + result.push(encryptedCharCode ^ keyCharCode); + } + + return String.fromCharCode(...result); + } catch { + // If decryption fails, return an empty string to avoid leaking encryption format info + console.warn('Decryption failed in OTP v0'); + return ''; + } +} + +/** + * Basic key transformation - just returns the key as-is + */ +function transformKey(rawKey: string): string { + return rawKey; +} + +const otpV0Algorithm: OTPAlgorithm = { + version: 'otp_v0' as OTPVersion, + name: 'OTP v0 (Legacy)', + description: 'Original OTP encryption with basic key transformation', + transformKey, + encrypt: xorEncrypt, + decrypt: xorDecrypt, +}; + +export default otpV0Algorithm; diff --git a/src/algorithms/otp_v1/index.ts b/src/algorithms/otp_v1/index.ts new file mode 100644 index 0000000..8bb03bf --- /dev/null +++ b/src/algorithms/otp_v1/index.ts @@ -0,0 +1,142 @@ +// OTP v1 Algorithm - Enhanced Implementation +// This is the enhanced OTP algorithm with improved key expansion and security + +import { OTPAlgorithm, OTPVersion } from '../types'; + +/** + * System message threshold - keys larger than this trigger a system message + */ +export const SYSTEM_MESSAGE_KEY_THRESHOLD = 256; + +/** + * Simple hash function for key expansion + * Uses a basic mixing algorithm to expand short keys + */ +function hashExpand(input: string, targetLength: number): string { + if (input.length === 0) { + return ''; + } + + // Start with the input repeated to at least target length + let expanded = input; + while (expanded.length < targetLength) { + expanded += input; + } + + // Mix the bytes using a simple algorithm + const result: number[] = []; + let accumulator = 0; + + for (let i = 0; i < targetLength; i++) { + const charCode = expanded.charCodeAt(i % expanded.length); + accumulator = (accumulator + charCode * 31 + i * 17) % 256; + result.push(accumulator ^ charCode); + } + + return String.fromCharCode(...result); +} + +/** + * Enhanced key transformation with expansion + * Expands short keys to improve security + */ +function transformKey(rawKey: string): string { + if (rawKey.length === 0) { + return ''; + } + + // Minimum expanded key length for v1 + const minLength = Math.max(rawKey.length * 4, 1024); + return hashExpand(rawKey, minLength); +} + +/** + * Enhanced XOR encryption with key stream + */ +function encrypt(message: string, key: string, offset: number): string { + const result: number[] = []; + const keyLength = key.length; + + if (keyLength === 0) { + return message; + } + + for (let i = 0; i < message.length; i++) { + const messageCharCode = message.charCodeAt(i); + // Use modular arithmetic with offset for key position + const keyIndex = (offset + i) % keyLength; + const keyCharCode = key.charCodeAt(keyIndex); + // Additional mixing step for v1 + const mixedKey = (keyCharCode + i) % 256; + result.push(messageCharCode ^ mixedKey); + } + + // Convert to base64 for safe transmission + return btoa(String.fromCharCode(...result)); +} + +/** + * Enhanced XOR decryption with key stream + */ +function decrypt(encrypted: string, key: string, offset: number): string { + const keyLength = key.length; + + if (keyLength === 0) { + return encrypted; + } + + try { + // Decode from base64 + const decoded = atob(encrypted); + const result: number[] = []; + + for (let i = 0; i < decoded.length; i++) { + const encryptedCharCode = decoded.charCodeAt(i); + const keyIndex = (offset + i) % keyLength; + const keyCharCode = key.charCodeAt(keyIndex); + // Same mixing step as encryption for v1 + const mixedKey = (keyCharCode + i) % 256; + result.push(encryptedCharCode ^ mixedKey); + } + + return String.fromCharCode(...result); + } catch { + // If decryption fails, return an empty string to avoid leaking encryption format info + console.warn('Decryption failed in OTP v1'); + return ''; + } +} + +/** + * Check if key length exceeds the system message threshold + */ +export function shouldShowSystemMessage(keyLength: number): boolean { + return keyLength > SYSTEM_MESSAGE_KEY_THRESHOLD; +} + +/** + * Generate system message for large keys + * @param keyLength Length of the encryption key + * @param messageGenerator Optional custom message generator function + */ +export function generateLargeKeySystemMessage( + keyLength: number, + messageGenerator?: (keyLength: number) => string +): string { + if (messageGenerator) { + return messageGenerator(keyLength); + } + // Default message (Turkish) - use i18n in calling code for proper localization + return `Şifreleme anahtarı ${keyLength} karakter uzunluğunda. Bu uzun bir anahtar olduğundan, güvenlik artırılmış durumda.`; +} + +const otpV1Algorithm: OTPAlgorithm = { + version: 'otp_v1' as OTPVersion, + name: 'OTP v1 (Enhanced)', + description: 'Enhanced OTP encryption with improved key expansion and security', + transformKey, + encrypt, + decrypt, +}; + +export default otpV1Algorithm; diff --git a/src/algorithms/types.ts b/src/algorithms/types.ts new file mode 100644 index 0000000..bb5b931 --- /dev/null +++ b/src/algorithms/types.ts @@ -0,0 +1,71 @@ +// OTP Algorithm Type Definitions + +/** + * Supported OTP versions + */ +export type OTPVersion = 'otp_v0' | 'otp_v1'; + +/** + * Default OTP version for new DMs + */ +export const DEFAULT_OTP_VERSION: OTPVersion = 'otp_v1'; + +/** + * Latest OTP version available + */ +export const LATEST_OTP_VERSION: OTPVersion = 'otp_v1'; + +/** + * Human-readable labels for OTP versions + */ +export const OTP_VERSION_LABELS: Record = { + otp_v0: 'OTP v0 (Legacy)', + otp_v1: 'OTP v1 (Enhanced)', +}; + +/** + * Descriptions for OTP versions + */ +export const OTP_VERSION_DESCRIPTIONS: Record = { + otp_v0: 'Original OTP encryption with basic key transformation', + otp_v1: 'Enhanced OTP encryption with improved key expansion and security', +}; + +/** + * Interface for OTP algorithm implementations + */ +export interface OTPAlgorithm { + /** Version identifier */ + version: OTPVersion; + + /** Display name */ + name: string; + + /** Description of the algorithm */ + description: string; + + /** + * Transform a raw key into the algorithm's internal format + * @param rawKey The original key provided by user + * @returns Transformed key ready for encryption/decryption + */ + transformKey(rawKey: string): string; + + /** + * Encrypt a message using the transformed key + * @param message Plaintext message to encrypt + * @param key Transformed key (output of transformKey) + * @param offset Current offset in the key stream + * @returns Encrypted message + */ + encrypt(message: string, key: string, offset: number): string; + + /** + * Decrypt a message using the transformed key + * @param encrypted Encrypted message + * @param key Transformed key (output of transformKey) + * @param offset Current offset in the key stream + * @returns Decrypted plaintext message + */ + decrypt(encrypted: string, key: string, offset: number): string; +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..db81620 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,3 @@ +// i18n Index +export { translations, t, setLanguage, getLanguage, stripSystemTags } from './translations'; +export type { Language, TranslationKey } from './translations'; diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts new file mode 100644 index 0000000..b29a1c1 --- /dev/null +++ b/src/i18n/translations.ts @@ -0,0 +1,95 @@ +// i18n Translations +// Translation strings for internationalization + +export const translations = { + tr: { + // System messages + largeKeySystemMessage: (keyLength: number) => + `Şifreleme anahtarı ${keyLength} karakter uzunluğunda. Bu uzun bir anahtar olduğundan, güvenlik artırılmış durumda.`, + + // Encryption status + encryptionDisabled: 'Şifreleme devre dışı', + encryptedWith: (algorithmName: string) => `${algorithmName} ile şifreli`, + + // UI labels + advancedSettings: 'Gelişmiş Ayarlar', + encryptionType: 'Şifreleme Türü', + encryptionTypeDescription: 'Mesajlarınız için kullanılacak OTP şifreleme sürümünü seçin.', + encryptionKey: 'Şifreleme Anahtarı', + encryptionKeyDescription: (threshold: number) => + `Mesajları şifrelemek için kullanılacak anahtar. ${threshold} karakterden uzun anahtarlar için sistem bildirimi gösterilir.`, + keyPlaceholder: 'Şifreleme anahtarını girin...', + keyLength: (length: number) => `Anahtar uzunluğu: ${length} karakter`, + largeKeyBadge: 'Büyük Anahtar', + defaultBadge: 'Varsayılan', + cancel: 'İptal', + save: 'Kaydet', + back: 'Geri', + send: 'Gönder', + messagePlaceholder: 'Mesaj yazın...', + }, + en: { + // System messages + largeKeySystemMessage: (keyLength: number) => + `Encryption key is ${keyLength} characters long. Security is enhanced due to large key size.`, + + // Encryption status + encryptionDisabled: 'Encryption disabled', + encryptedWith: (algorithmName: string) => `Encrypted with ${algorithmName}`, + + // UI labels + advancedSettings: 'Advanced Settings', + encryptionType: 'Encryption Type', + encryptionTypeDescription: 'Select the OTP encryption version for your messages.', + encryptionKey: 'Encryption Key', + encryptionKeyDescription: (threshold: number) => + `Key used for message encryption. System notification shown for keys longer than ${threshold} characters.`, + keyPlaceholder: 'Enter encryption key...', + keyLength: (length: number) => `Key length: ${length} characters`, + largeKeyBadge: 'Large Key', + defaultBadge: 'Default', + cancel: 'Cancel', + save: 'Save', + back: 'Back', + send: 'Send', + messagePlaceholder: 'Type a message...', + }, +}; + +export type Language = keyof typeof translations; +export type TranslationKey = keyof typeof translations.tr; + +/** + * Current language setting + */ +let currentLanguage: Language = 'tr'; + +/** + * Set the current language + */ +export function setLanguage(lang: Language): void { + currentLanguage = lang; +} + +/** + * Get the current language + */ +export function getLanguage(): Language { + return currentLanguage; +} + +/** + * Get translations for the current language + */ +export function t(): typeof translations.tr { + return translations[currentLanguage]; +} + +/** + * Utility to remove system message tags + */ +export function stripSystemTags(message: string): string { + return message.replace(/<\/?system>/g, ''); +} + +export default translations; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..6b01aad --- /dev/null +++ b/src/index.ts @@ -0,0 +1,17 @@ +// Main Entry Point +// Export all modules for the OTP versioning system for DM + +// Algorithms +export * from './algorithms'; + +// Stores +export * from './stores'; + +// Services +export * from './services'; + +// i18n +export * from './i18n'; + +// Screens +export { DMChatScreen } from './screens/dm'; diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..24239ec --- /dev/null +++ b/src/package.json @@ -0,0 +1,23 @@ +{ + "name": "pattern-analyzer-dm", + "version": "1.0.0", + "description": "OTP versioning system for Direct Messages", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "test": "jest", + "lint": "eslint src --ext .ts,.tsx" + }, + "dependencies": { + "react": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0" + } +} diff --git a/src/screens/dm/DMChatScreen.tsx b/src/screens/dm/DMChatScreen.tsx new file mode 100644 index 0000000..4fcce0a --- /dev/null +++ b/src/screens/dm/DMChatScreen.tsx @@ -0,0 +1,571 @@ +// DM Chat Screen +// Direct Message chat screen with OTP versioning support and advanced settings + +import React, { useState, useCallback, useMemo } from 'react'; +import { + OTPVersion, + DEFAULT_OTP_VERSION, + OTP_VERSION_LABELS, + OTP_VERSION_DESCRIPTIONS, + getAvailableVersions +} from '../../algorithms'; +import { dmService, DMMessage } from '../../services/dmService'; +import { t, stripSystemTags } from '../../i18n'; + +/** + * Props for DMChatScreen component + */ +interface DMChatScreenProps { + conversationId: string; + participantName: string; + onBack?: () => void; +} + +/** + * Props for Advanced Settings Modal + */ +interface AdvancedSettingsProps { + isOpen: boolean; + onClose: () => void; + conversationId: string; + currentVersion: OTPVersion; + currentKey: string; + onVersionChange: (version: OTPVersion) => void; + onKeyChange: (key: string) => void; +} + +/** + * Advanced Settings Modal Component + */ +const AdvancedSettings: React.FC = ({ + isOpen, + onClose, + conversationId, + currentVersion, + currentKey, + onVersionChange, + onKeyChange, +}) => { + const [selectedVersion, setSelectedVersion] = useState(currentVersion); + const [encryptionKey, setEncryptionKey] = useState(currentKey); + const availableVersions = useMemo(() => getAvailableVersions(), []); + + const handleSave = useCallback(() => { + if (selectedVersion !== currentVersion) { + onVersionChange(selectedVersion); + } + if (encryptionKey !== currentKey) { + onKeyChange(encryptionKey); + } + onClose(); + }, [selectedVersion, currentVersion, encryptionKey, currentKey, onVersionChange, onKeyChange, onClose]); + + if (!isOpen) return null; + + const translations = t(); + + return ( +
+
+
+

{translations.advancedSettings}

+ +
+

{translations.encryptionType}

+

+ {translations.encryptionTypeDescription} +

+ +
+ {availableVersions.map((version) => ( +
setSelectedVersion(version)} + > +
+ setSelectedVersion(version)} + /> + {OTP_VERSION_LABELS[version]} + {version === DEFAULT_OTP_VERSION && ( + {translations.defaultBadge} + )} +
+

{OTP_VERSION_DESCRIPTIONS[version]}

+
+ ))} +
+
+ +
+

{translations.encryptionKey}

+

+ {translations.encryptionKeyDescription(dmService.getKeyThreshold())} +

+ +
+