Skip to content

Commit 7500da0

Browse files
committed
System Touch
1 parent 4f930f0 commit 7500da0

3 files changed

Lines changed: 269 additions & 1 deletion

File tree

source/database/N21Store.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,121 @@ public static void storeNationalFinanceID(final national.NationalFinanceID N)
198198
"created_at", N.createdAt != null ? N.createdAt.toString() : "");
199199
}
200200

201+
// ── user keypairs ────────────────────────────────────────────────────────
202+
203+
public static void storeKeypair(final long NATIONAL_ID, final national.NationalKeypairGenerator K)
204+
{
205+
if (dbOk())
206+
{
207+
try
208+
{
209+
PreparedStatement ps = N21DataSource.get().prepareStatement(
210+
"INSERT INTO user_keypairs (national_id, rsa_public_key, rsa_private_key, dsa_public_key, dsa_private_key, aes_key) " +
211+
"VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE rsa_public_key=VALUES(rsa_public_key), rsa_private_key=VALUES(rsa_private_key), " +
212+
"dsa_public_key=VALUES(dsa_public_key), dsa_private_key=VALUES(dsa_private_key), aes_key=VALUES(aes_key)");
213+
ps.setLong(1, NATIONAL_ID);
214+
ps.setString(2, K.rsaPublicKey);
215+
ps.setString(3, K.rsaPrivateKey);
216+
ps.setString(4, K.dsaPublicKey);
217+
ps.setString(5, K.dsaPrivateKey);
218+
ps.setString(6, K.aesKey);
219+
ps.executeUpdate(); ps.close();
220+
return;
221+
}
222+
catch (Exception e) { fail("user_keypairs", e); }
223+
}
224+
N21XmlFallback.append("user_keypairs",
225+
"national_id", String.valueOf(NATIONAL_ID),
226+
"rsa_public_key", K.rsaPublicKey, "rsa_private_key", K.rsaPrivateKey,
227+
"dsa_public_key", K.dsaPublicKey, "dsa_private_key", K.dsaPrivateKey,
228+
"aes_key", K.aesKey);
229+
}
230+
231+
public static String[] loadKeypair(final long NATIONAL_ID, final String TYPE)
232+
{
233+
if (dbOk())
234+
{
235+
try
236+
{
237+
PreparedStatement ps = N21DataSource.get().prepareStatement(
238+
"SELECT rsa_public_key, rsa_private_key, dsa_public_key, dsa_private_key, aes_key FROM user_keypairs WHERE national_id=?");
239+
ps.setLong(1, NATIONAL_ID);
240+
java.sql.ResultSet rs = ps.executeQuery();
241+
if (rs.next())
242+
{
243+
String[] result = switch (TYPE.toLowerCase()) {
244+
case "rsa" -> new String[]{rs.getString("rsa_public_key"), rs.getString("rsa_private_key")};
245+
case "dsa" -> new String[]{rs.getString("dsa_public_key"), rs.getString("dsa_private_key")};
246+
case "aes" -> new String[]{rs.getString("aes_key")};
247+
default -> null;
248+
};
249+
rs.close(); ps.close();
250+
return result;
251+
}
252+
rs.close(); ps.close();
253+
}
254+
catch (Exception e) { fail("user_keypairs", e); }
255+
}
256+
return null;
257+
}
258+
259+
public static boolean deleteKeypair(final long NATIONAL_ID, final String TYPE)
260+
{
261+
if (dbOk())
262+
{
263+
try
264+
{
265+
String sql = switch (TYPE.toLowerCase()) {
266+
case "rsa" -> "UPDATE user_keypairs SET rsa_public_key='', rsa_private_key='' WHERE national_id=?";
267+
case "dsa" -> "UPDATE user_keypairs SET dsa_public_key='', dsa_private_key='' WHERE national_id=?";
268+
case "aes" -> "UPDATE user_keypairs SET aes_key='' WHERE national_id=?";
269+
default -> "DELETE FROM user_keypairs WHERE national_id=?";
270+
};
271+
PreparedStatement ps = N21DataSource.get().prepareStatement(sql);
272+
ps.setLong(1, NATIONAL_ID);
273+
int rows = ps.executeUpdate(); ps.close();
274+
return rows > 0;
275+
}
276+
catch (Exception e) { fail("user_keypairs", e); }
277+
}
278+
return false;
279+
}
280+
281+
public static boolean replaceKeypair(final long NATIONAL_ID, final String TYPE)
282+
{
283+
if (dbOk())
284+
{
285+
try
286+
{
287+
national.NationalKeypairGenerator gen = new national.NationalKeypairGenerator();
288+
String sql;
289+
PreparedStatement ps;
290+
switch (TYPE.toLowerCase()) {
291+
case "rsa" -> {
292+
sql = "UPDATE user_keypairs SET rsa_public_key=?, rsa_private_key=? WHERE national_id=?";
293+
ps = N21DataSource.get().prepareStatement(sql);
294+
ps.setString(1, gen.rsaPublicKey); ps.setString(2, gen.rsaPrivateKey); ps.setLong(3, NATIONAL_ID);
295+
}
296+
case "dsa" -> {
297+
sql = "UPDATE user_keypairs SET dsa_public_key=?, dsa_private_key=? WHERE national_id=?";
298+
ps = N21DataSource.get().prepareStatement(sql);
299+
ps.setString(1, gen.dsaPublicKey); ps.setString(2, gen.dsaPrivateKey); ps.setLong(3, NATIONAL_ID);
300+
}
301+
case "aes" -> {
302+
sql = "UPDATE user_keypairs SET aes_key=? WHERE national_id=?";
303+
ps = N21DataSource.get().prepareStatement(sql);
304+
ps.setString(1, gen.aesKey); ps.setLong(2, NATIONAL_ID);
305+
}
306+
default -> { return false; }
307+
}
308+
int rows = ps.executeUpdate(); ps.close();
309+
return rows > 0;
310+
}
311+
catch (Exception e) { fail("user_keypairs", e); }
312+
}
313+
return false;
314+
}
315+
201316
public static national.NationalFinanceID loadNationalFinanceID(final long NATIONALID)
202317
{
203318
if (dbOk())

source/national/NationalFinanceIDFeeder.java

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,13 @@ public static NationalFinanceID greet(final Connection CONN)
135135
// Persist
136136
N21Store.storeNationalFinanceID(nfid);
137137

138+
// Generate per-user cryptographic keypairs (RSA, DSA, AES)
139+
NationalKeypairGenerator keypair = new NationalKeypairGenerator();
140+
N21Store.storeKeypair(nfid.nationalId, keypair);
141+
138142
write(CONN, "");
139143
write(CONN, " ✔ National Finance ID " + nfid.nationalId + " registered and stored.");
144+
write(CONN, " ✔ RSA-2048, DSA-2048, AES-256 keypairs generated and stored.");
140145
write(CONN, "");
141146

142147
financePrompt(CONN, nfid);
@@ -164,7 +169,15 @@ private static void financePrompt(final Connection CONN, final NationalFinanceID
164169
String input = prompt(CONN, line + " > ");
165170
if (input == null || input.equalsIgnoreCase("quit") || input.equalsIgnoreCase("exit")) break;
166171

167-
write(CONN, line + " < " + trade(input, NFID));
172+
if (input.trim().toLowerCase().startsWith("crypto"))
173+
{
174+
cryptoPrompt(CONN, NFID);
175+
write(CONN, line + " < Returned from crypto management.");
176+
}
177+
else
178+
{
179+
write(CONN, line + " < " + trade(input, NFID));
180+
}
168181
line++;
169182
}
170183
}
@@ -183,6 +196,7 @@ private static String trade(final String INPUT, final NationalFinanceID NFID)
183196
if (cmd.startsWith("balance")) return "Promissory balance: $" + String.format("%.2f", NFID.promissoryNote) + " USD.";
184197
if (cmd.startsWith("id")) return "National ID: " + NFID.nationalId + " Trust: " + NFID.trustLevel + " Education: " + NFID.educationLevel + ".";
185198
if (cmd.startsWith("status")) return "National ID " + NFID.nationalId + " active. Trust " + NFID.trustLevel + "/100. Promissory $" + String.format("%.2f", NFID.promissoryNote) + ".";
199+
if (cmd.equals("crypto")) return "Entering crypto key management...";
186200
return "Received: [" + INPUT + "] — National ID " + NFID.nationalId + " logged.";
187201
}
188202

@@ -195,10 +209,98 @@ private static String trade(final String INPUT, final NationalFinanceID NFID)
195209
" balance Show your promissory note balance (USD)\r\n" +
196210
" id Show your National ID and profile summary\r\n" +
197211
" status Show full account status and trust level\r\n" +
212+
" crypto Manage cryptographic keys (RSA/DSA/AES)\r\n" +
198213
" help Show this command list\r\n" +
199214
" quit / exit End this session\r\n" +
200215
" ────────────────────────────────────────────────────";
201216

217+
// ─────────────────────────────────────────────────────────────────────────
218+
// Crypto key management sub-prompt
219+
// ─────────────────────────────────────────────────────────────────────────
220+
221+
private static void cryptoPrompt(final Connection CONN, final NationalFinanceID NFID)
222+
{
223+
write(CONN, "");
224+
write(CONN, " ╔════════════════════════════════════════╗");
225+
write(CONN, " ║ CRYPTO KEY MANAGEMENT ║");
226+
write(CONN, " ╚════════════════════════════════════════╝");
227+
write(CONN, "");
228+
write(CONN, " Commands: create <type> | replace <type>");
229+
write(CONN, " check <type> | delete <type>");
230+
write(CONN, " Types: rsa | dsa | aes");
231+
write(CONN, " back Return to finance prompt");
232+
write(CONN, "");
233+
234+
for (;;)
235+
{
236+
String input = prompt(CONN, " crypto> ");
237+
if (input == null || input.equalsIgnoreCase("back") || input.equalsIgnoreCase("exit")) break;
238+
239+
String[] parts = input.trim().toLowerCase().split("\\s+", 2);
240+
String action = parts[0];
241+
String type = parts.length > 1 ? parts[1] : "";
242+
243+
if (!type.matches("rsa|dsa|aes") && !action.equals("help"))
244+
{
245+
write(CONN, " Usage: <create|replace|check|delete> <rsa|dsa|aes>");
246+
continue;
247+
}
248+
249+
switch (action)
250+
{
251+
case "create" -> {
252+
String[] existing = N21Store.loadKeypair(NFID.nationalId, type);
253+
if (existing != null && existing.length > 0 && !existing[0].isEmpty())
254+
{
255+
write(CONN, " ✗ " + type.toUpperCase() + " key already exists. Use 'replace " + type + "' to regenerate.");
256+
}
257+
else
258+
{
259+
NationalKeypairGenerator gen = new NationalKeypairGenerator();
260+
N21Store.storeKeypair(NFID.nationalId, gen);
261+
write(CONN, " ✔ " + type.toUpperCase() + " keypair created and stored.");
262+
}
263+
}
264+
case "replace" -> {
265+
boolean ok = N21Store.replaceKeypair(NFID.nationalId, type);
266+
if (ok) write(CONN, " ✔ " + type.toUpperCase() + " keypair replaced with new keys.");
267+
else write(CONN, " ✗ No existing keypair to replace. Use 'create " + type + "' first.");
268+
}
269+
case "check" -> {
270+
String[] keys = N21Store.loadKeypair(NFID.nationalId, type);
271+
if (keys == null || keys.length == 0 || keys[0].isEmpty())
272+
{
273+
write(CONN, " ✗ No " + type.toUpperCase() + " key found for National ID " + NFID.nationalId + ".");
274+
}
275+
else
276+
{
277+
write(CONN, " ✔ " + type.toUpperCase() + " key present for National ID " + NFID.nationalId + ".");
278+
if (type.equals("aes"))
279+
{
280+
write(CONN, " AES-256 key: " + keys[0].substring(0, Math.min(12, keys[0].length())) + "...");
281+
}
282+
else
283+
{
284+
write(CONN, " Public: " + keys[0].substring(0, Math.min(20, keys[0].length())) + "...");
285+
write(CONN, " Private: " + keys[1].substring(0, Math.min(20, keys[1].length())) + "...");
286+
}
287+
}
288+
}
289+
case "delete" -> {
290+
boolean ok = N21Store.deleteKeypair(NFID.nationalId, type);
291+
if (ok) write(CONN, " ✔ " + type.toUpperCase() + " key deleted for National ID " + NFID.nationalId + ".");
292+
else write(CONN, " ✗ No " + type.toUpperCase() + " key found to delete.");
293+
}
294+
case "help" -> {
295+
write(CONN, " Commands: create <type> | replace <type>");
296+
write(CONN, " check <type> | delete <type>");
297+
write(CONN, " Types: rsa | dsa | aes");
298+
}
299+
default -> write(CONN, " Unknown command. Try: create, replace, check, delete, help, back");
300+
}
301+
}
302+
}
303+
202304
// ─────────────────────────────────────────────────────────────────────────
203305
// Helpers
204306
// ─────────────────────────────────────────────────────────────────────────
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package national;
2+
3+
import javax.crypto.KeyGenerator;
4+
import javax.crypto.SecretKey;
5+
import java.security.*;
6+
import java.util.Base64;
7+
8+
/**
9+
* Generates a per-user cryptographic keypair bundle (RSA, DSA, AES)
10+
* at National ID registration time.
11+
*
12+
* All keys are stored as Base64-encoded strings for database persistence.
13+
*/
14+
public class NationalKeypairGenerator
15+
{
16+
public final String rsaPublicKey;
17+
public final String rsaPrivateKey;
18+
public final String dsaPublicKey;
19+
public final String dsaPrivateKey;
20+
public final String aesKey;
21+
22+
public NationalKeypairGenerator()
23+
{
24+
try
25+
{
26+
// RSA 2048-bit
27+
KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
28+
rsa.initialize(2048, new SecureRandom());
29+
KeyPair rsaPair = rsa.generateKeyPair();
30+
this.rsaPublicKey = Base64.getEncoder().encodeToString(rsaPair.getPublic().getEncoded());
31+
this.rsaPrivateKey = Base64.getEncoder().encodeToString(rsaPair.getPrivate().getEncoded());
32+
33+
// DSA 2048-bit
34+
KeyPairGenerator dsa = KeyPairGenerator.getInstance("DSA");
35+
dsa.initialize(2048, new SecureRandom());
36+
KeyPair dsaPair = dsa.generateKeyPair();
37+
this.dsaPublicKey = Base64.getEncoder().encodeToString(dsaPair.getPublic().getEncoded());
38+
this.dsaPrivateKey = Base64.getEncoder().encodeToString(dsaPair.getPrivate().getEncoded());
39+
40+
// AES 256-bit
41+
KeyGenerator aesGen = KeyGenerator.getInstance("AES");
42+
aesGen.init(256, new SecureRandom());
43+
SecretKey aesSecret = aesGen.generateKey();
44+
this.aesKey = Base64.getEncoder().encodeToString(aesSecret.getEncoded());
45+
}
46+
catch (Exception e)
47+
{
48+
throw new RuntimeException("Keypair generation failed", e);
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)