nomyo4J/src/main/java/ai/nomyo/SecureMemory.java

155 lines
4.3 KiB
Java
Raw Normal View History

2026-04-21 17:24:11 +02:00
package ai.nomyo;
import lombok.Getter;
import lombok.Setter;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Map;
/**
2026-04-23 13:36:46 +02:00
* Cross-platform memory locking and secure zeroing for sensitive cryptographic buffers. Fails gracefully if unavailable.
2026-04-21 17:24:11 +02:00
*/
2026-04-29 16:59:33 +02:00
@SuppressWarnings("SameReturnValue")
2026-04-21 17:24:11 +02:00
public final class SecureMemory {
@Getter
private static final boolean HAS_MEMORY_LOCKING;
@Getter
private static final boolean HAS_SECURE_ZEROING;
2026-04-23 13:36:46 +02:00
@Getter
@Setter
private static volatile boolean secureMemoryEnabled = true;
2026-04-21 17:24:11 +02:00
static {
boolean locking = false;
boolean zeroing = false;
2026-04-23 13:36:46 +02:00
2026-04-21 17:24:11 +02:00
try {
locking = initMemoryLocking();
zeroing = true; // Secure zeroing is always available at the JVM level
} catch (Throwable t) {
// Degrade gracefully
}
HAS_MEMORY_LOCKING = locking;
HAS_SECURE_ZEROING = zeroing;
}
private static boolean initMemoryLocking() {
// FFM doesn't support memory locking at this point in time
return false;
}
/**
2026-04-23 13:36:46 +02:00
* Recommended way to handle sensitive data use within try-with-resources for secure zeroing.
*/
public static SecureBuffer secureByteArray(byte[] data, boolean lock) {
return new SecureBuffer(data, lock);
}
/**
* Always attempts locking.
*/
public static SecureBuffer secureByteArray(byte[] data) {
return secureByteArray(data, true);
}
/**
* Returns protection capabilities: enabled, protection_level, has_memory_locking, has_secure_zeroing, supports_full_protection, page_size.
*/
public static Map<String, Object> getMemoryProtectionInfo() {
String protectionLevel;
if (HAS_MEMORY_LOCKING && HAS_SECURE_ZEROING) {
protectionLevel = "full";
} else if (HAS_SECURE_ZEROING) {
protectionLevel = "zeroing_only";
} else {
protectionLevel = "none";
}
boolean supportsFull = HAS_MEMORY_LOCKING && HAS_SECURE_ZEROING && secureMemoryEnabled;
return Map.of("enabled", secureMemoryEnabled, "protection_level", protectionLevel, "has_memory_locking", HAS_MEMORY_LOCKING, "has_secure_zeroing", HAS_SECURE_ZEROING, "supports_full_protection", supportsFull, "page_size", Constants.PAGE_SIZE);
}
/**
* Wraps bytes with memory locking and guaranteed zeroing on close. AutoCloseable for try-with-resources.
2026-04-21 17:24:11 +02:00
*/
2026-04-29 16:59:33 +02:00
@SuppressWarnings("SameReturnValue")
2026-04-21 17:24:11 +02:00
public static class SecureBuffer implements AutoCloseable {
private final Arena arena;
@Getter
private final MemorySegment data;
@Getter
private final long size;
@Getter
private final long address;
private boolean locked;
private boolean closed;
/**
2026-04-23 13:36:46 +02:00
* @param data byte array to wrap
* @param lock whether to attempt memory locking
2026-04-21 17:24:11 +02:00
*/
public SecureBuffer(byte[] data, boolean lock) {
this.arena = Arena.ofConfined();
this.data = data != null ? this.arena.allocate(data.length) : MemorySegment.NULL;
if (data != null) {
this.data.asByteBuffer().put(data);
}
this.size = this.data.byteSize();
this.address = this.data.address();
this.locked = false;
this.closed = false;
if (lock && SecureMemory.secureMemoryEnabled) {
this.locked = lock();
}
}
/**
2026-04-23 13:36:46 +02:00
* Locks buffer in memory (prevents disk swapping). Returns false if unavailable.
2026-04-21 17:24:11 +02:00
*/
public boolean lock() {
return false;
}
/**
2026-04-23 13:36:46 +02:00
* Unlocks buffer (allows disk swapping).
2026-04-21 17:24:11 +02:00
*/
public boolean unlock() {
if (!locked) return false;
locked = false;
return false;
}
/**
2026-04-23 13:36:46 +02:00
* Securely zeros buffer contents.
2026-04-21 17:24:11 +02:00
*/
public void zero() {
if (data != null) {
data.fill((byte) 0);
}
}
@Override
public void close() {
if (closed) return;
zero();
unlock();
arena.close();
closed = true;
}
}
}