Skip to content

Commit 3fadd68

Browse files
committed
feat(rotation): implement envelope encryption and migration utilities
- Added `envelope.ts` for envelope encryption implementation, including functions for creating, parsing, and validating encrypted envelopes. - Introduced `migration.ts` to handle migration from legacy encrypted data to the new versioned envelope format, supporting batch processing and progress tracking. - Created `rotation-api.ts` to expose key rotation functionalities, including initialization, rotation, migration, and event handling. - Defined types and interfaces in `types.ts` for key versions, encrypted envelopes, rotation policies, and migration results.
1 parent bd8a3f5 commit 3fadd68

12 files changed

Lines changed: 4130 additions & 0 deletions

File tree

android/src/main/java/com/sensitiveinfo/KeyRotation.kt

Lines changed: 432 additions & 0 deletions
Large diffs are not rendered by default.

example/src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Header from './components/Header';
1010
import SecretForm from './components/SecretForm';
1111
import ModeSelector from './components/ModeSelector';
1212
import ActionsPanel from './components/ActionsPanel';
13+
import KeyRotationPanel from './components/KeyRotationPanel';
1314
import SecretsList from './components/SecretsList';
1415
import {
1516
ACCESS_MODES,
@@ -218,6 +219,8 @@ const App: React.FC = () => {
218219
errorMessage={error?.message}
219220
/>
220221

222+
<KeyRotationPanel />
223+
221224
<SecretsList
222225
items={items}
223226
isLoading={isLoading}
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import React, { useCallback, useState } from 'react';
2+
import { StyleSheet, Text, View } from 'react-native';
3+
import type { RotationEvent } from 'react-native-sensitive-info';
4+
import {
5+
initializeKeyRotation,
6+
rotateKeys,
7+
reEncryptAllItems,
8+
getRotationStatus,
9+
on,
10+
} from 'react-native-sensitive-info';
11+
import Card from './Card';
12+
import ActionButton from './ActionButton';
13+
import { formatError } from '../utils/formatError';
14+
15+
interface RotationStatusInfo {
16+
isRotating: boolean;
17+
currentKeyVersion: string | null;
18+
availableKeyVersions: number;
19+
lastRotationTimestamp: string | null;
20+
}
21+
22+
const styles = StyleSheet.create({
23+
description: {
24+
fontSize: 13,
25+
color: '#475569',
26+
marginBottom: 12,
27+
lineHeight: 18,
28+
},
29+
buttonRow: {
30+
flexDirection: 'row',
31+
flexWrap: 'wrap',
32+
gap: 8,
33+
marginBottom: 14,
34+
},
35+
statusContainer: {
36+
marginTop: 14,
37+
backgroundColor: '#f1f5f9',
38+
borderRadius: 12,
39+
padding: 12,
40+
borderLeftWidth: 4,
41+
borderLeftColor: '#3b82f6',
42+
},
43+
statusTitle: {
44+
fontSize: 13,
45+
fontWeight: '600',
46+
color: '#1e293b',
47+
marginBottom: 8,
48+
},
49+
statusDetail: {
50+
fontSize: 12,
51+
color: '#475569',
52+
marginBottom: 4,
53+
fontFamily: 'monospace',
54+
},
55+
messageBubble: {
56+
marginTop: 12,
57+
backgroundColor: '#0f172a0d',
58+
borderRadius: 12,
59+
padding: 12,
60+
minHeight: 60,
61+
justifyContent: 'center',
62+
},
63+
messageText: {
64+
fontSize: 13,
65+
color: '#0f172a',
66+
lineHeight: 18,
67+
},
68+
infoBox: {
69+
marginTop: 12,
70+
backgroundColor: '#f0fdf4',
71+
borderRadius: 12,
72+
padding: 12,
73+
borderLeftWidth: 4,
74+
borderLeftColor: '#22c55e',
75+
},
76+
infoTitle: {
77+
fontSize: 12,
78+
fontWeight: '600',
79+
color: '#166534',
80+
marginBottom: 8,
81+
},
82+
infoItem: {
83+
fontSize: 12,
84+
color: '#15803d',
85+
marginBottom: 4,
86+
lineHeight: 16,
87+
},
88+
});
89+
90+
const KeyRotationPanel: React.FC = () => {
91+
const [statusMessage, setStatusMessage] = useState(
92+
'Key rotation not initialized'
93+
);
94+
const [rotationStatus, setRotationStatus] =
95+
useState<RotationStatusInfo | null>(null);
96+
const [pending, setPending] = useState(false);
97+
const [isInitialized, setIsInitialized] = useState(false);
98+
99+
// Initialize key rotation system
100+
const handleInitialize = useCallback(async () => {
101+
setPending(true);
102+
try {
103+
await initializeKeyRotation({
104+
enabled: true,
105+
rotationIntervalMs: 30 * 24 * 60 * 60 * 1000, // 30 days for demo
106+
rotateOnBiometricChange: true,
107+
rotateOnCredentialChange: true,
108+
manualRotationEnabled: true,
109+
maxKeyVersions: 2,
110+
backgroundReEncryption: true,
111+
});
112+
113+
// Set up event listeners
114+
on('rotation:started', (event: RotationEvent) => {
115+
if (event.type === 'rotation:started') {
116+
setStatusMessage(
117+
`🔄 Rotation started (${event.reason}) at ${event.timestamp}`
118+
);
119+
}
120+
});
121+
122+
on('rotation:completed', (event: RotationEvent) => {
123+
if (event.type === 'rotation:completed') {
124+
setStatusMessage(
125+
`✅ Rotation completed! Re-encrypted ${event.itemsReEncrypted} items in ${event.duration}ms`
126+
);
127+
}
128+
});
129+
130+
on('rotation:failed', (event: RotationEvent) => {
131+
if (event.type === 'rotation:failed') {
132+
setStatusMessage(`❌ Rotation failed: ${event.reason}`);
133+
}
134+
});
135+
136+
on('biometric-change', (event: RotationEvent) => {
137+
if (
138+
event.type === 'biometric-change' ||
139+
event.type === 'credential-change'
140+
) {
141+
setStatusMessage(
142+
`🔐 Change detected on ${event.platform}. Rotation may be triggered.`
143+
);
144+
}
145+
});
146+
147+
setStatusMessage('✅ Key rotation system initialized successfully');
148+
setIsInitialized(true);
149+
} catch (err) {
150+
setStatusMessage(`Failed to initialize: ${formatError(err)}`);
151+
} finally {
152+
setPending(false);
153+
}
154+
}, []);
155+
156+
// Perform manual key rotation
157+
const handleManualRotation = useCallback(async () => {
158+
setPending(true);
159+
try {
160+
setStatusMessage('🔄 Starting manual key rotation...');
161+
162+
await rotateKeys({
163+
reason: 'User-initiated rotation from demo app',
164+
metadata: {
165+
demo: true,
166+
timestamp: new Date().toISOString(),
167+
},
168+
});
169+
170+
setStatusMessage('✅ Key rotation completed successfully');
171+
172+
// Refresh rotation status
173+
const newStatus = await getRotationStatus();
174+
setRotationStatus({
175+
isRotating: newStatus.isRotating,
176+
currentKeyVersion: newStatus.currentKeyVersion?.id ?? null,
177+
availableKeyVersions: newStatus.availableKeyVersions.length,
178+
lastRotationTimestamp: newStatus.lastRotationTimestamp,
179+
});
180+
} catch (err) {
181+
setStatusMessage(`❌ Rotation failed: ${formatError(err)}`);
182+
} finally {
183+
setPending(false);
184+
}
185+
}, []);
186+
187+
// Re-encrypt all items with current key
188+
const handleReEncrypt = useCallback(async () => {
189+
setPending(true);
190+
try {
191+
setStatusMessage('🔐 Starting re-encryption of all items...');
192+
193+
const result = await reEncryptAllItems({
194+
batchSize: 50,
195+
});
196+
197+
setStatusMessage(
198+
`✅ Re-encryption completed! Processed ${result.itemsReEncrypted} items${
199+
result.errors ? ` with ${result.errors.length} errors` : ''
200+
}`
201+
);
202+
203+
if (result.errors) {
204+
result.errors.slice(0, 2).forEach((err: string) => {
205+
// eslint-disable-next-line no-console
206+
console.warn('Re-encryption error:', err);
207+
});
208+
}
209+
} catch (err) {
210+
setStatusMessage(`❌ Re-encryption failed: ${formatError(err)}`);
211+
} finally {
212+
setPending(false);
213+
}
214+
}, []);
215+
216+
// Get current rotation status
217+
const handleCheckStatus = useCallback(async () => {
218+
setPending(true);
219+
try {
220+
const rotationStatusData = await getRotationStatus();
221+
222+
setRotationStatus({
223+
isRotating: rotationStatusData.isRotating,
224+
currentKeyVersion: rotationStatusData.currentKeyVersion?.id ?? null,
225+
availableKeyVersions: rotationStatusData.availableKeyVersions.length,
226+
lastRotationTimestamp: rotationStatusData.lastRotationTimestamp,
227+
});
228+
229+
const lastRotationTime = rotationStatusData.lastRotationTimestamp
230+
? new Date(rotationStatusData.lastRotationTimestamp).toLocaleString()
231+
: 'Never';
232+
233+
setStatusMessage(
234+
`Current key: ${rotationStatusData.currentKeyVersion?.id?.substring(0, 19) || 'None'}\n` +
235+
`Available versions: ${rotationStatusData.availableKeyVersions.length}\n` +
236+
`Last rotation: ${lastRotationTime}`
237+
);
238+
} catch (err) {
239+
setStatusMessage(`Failed to fetch status: ${formatError(err)}`);
240+
} finally {
241+
setPending(false);
242+
}
243+
}, []);
244+
245+
return (
246+
<Card title="🔑 Manual Key Rotation & Re-encryption">
247+
<Text style={styles.description}>
248+
Demonstrates automatic key rotation with manual triggers and data
249+
re-encryption capabilities.
250+
</Text>
251+
252+
<View style={styles.buttonRow}>
253+
<ActionButton
254+
label={isInitialized ? 'Already Init' : 'Initialize'}
255+
onPress={handleInitialize}
256+
loading={pending}
257+
primary
258+
/>
259+
<ActionButton
260+
label="Rotate Keys"
261+
onPress={handleManualRotation}
262+
loading={pending}
263+
/>
264+
<ActionButton
265+
label="Re-encrypt All"
266+
onPress={handleReEncrypt}
267+
loading={pending}
268+
/>
269+
<ActionButton
270+
label="Check Status"
271+
onPress={handleCheckStatus}
272+
loading={pending}
273+
/>
274+
</View>
275+
276+
{rotationStatus && (
277+
<View style={styles.statusContainer}>
278+
<Text style={styles.statusTitle}>Current Rotation Status:</Text>
279+
<Text style={styles.statusDetail}>
280+
Status: {rotationStatus.isRotating ? '🔄 Rotating' : '✅ Ready'}
281+
</Text>
282+
<Text style={styles.statusDetail}>
283+
Current Key:{' '}
284+
{rotationStatus.currentKeyVersion?.substring(0, 19) || 'None'}
285+
</Text>
286+
<Text style={styles.statusDetail}>
287+
Available Versions: {rotationStatus.availableKeyVersions}
288+
</Text>
289+
<Text style={styles.statusDetail}>
290+
Last Rotation:{' '}
291+
{rotationStatus.lastRotationTimestamp
292+
? new Date(rotationStatus.lastRotationTimestamp).toLocaleString()
293+
: 'Never'}
294+
</Text>
295+
</View>
296+
)}
297+
298+
<View style={styles.messageBubble}>
299+
<Text style={styles.messageText}>{statusMessage}</Text>
300+
</View>
301+
302+
<View style={styles.infoBox}>
303+
<Text style={styles.infoTitle}>Features demonstrated:</Text>
304+
<Text style={styles.infoItem}>
305+
• Automatic key rotation with biometric triggers
306+
</Text>
307+
<Text style={styles.infoItem}>
308+
• Zero-loss key rotation with DEK/KEK pattern
309+
</Text>
310+
<Text style={styles.infoItem}>
311+
• Manual re-encryption of all stored secrets
312+
</Text>
313+
<Text style={styles.infoItem}>
314+
• Event-based rotation lifecycle monitoring
315+
</Text>
316+
<Text style={styles.infoItem}>
317+
• Backward compatibility with legacy encrypted data
318+
</Text>
319+
</View>
320+
</Card>
321+
);
322+
};
323+
324+
export default KeyRotationPanel;

0 commit comments

Comments
 (0)