Class: PqcAsn1::SecureBuffer
- Inherits:
-
Object
- Object
- PqcAsn1::SecureBuffer
- Defined in:
- lib/pqc_asn1.rb,
lib/pqc_asn1.rb,
ext/pqc_asn1/secure_buffer.c
Overview
Secure memory buffer backed by mmap(2) with mprotect(2) and mlock(2).
SecureBuffer holds secret key material in memory that is:
- mlock'd to prevent swapping to disk
- mprotect'd to PROT_NONE when not in use (inaccessible)
- securely zeroed on GC or explicit wipe!
- guarded by canary bytes to detect buffer overflow
Use #use to temporarily unlock the memory for reading:
secure_buf.use { |bytes| do_something_with(bytes) }
Class Method Summary collapse
Instance Method Summary collapse
- #==(other) ⇒ Object
-
#bytesize ⇒ Object
------------------------------------------------------------------.
-
#canary_ok? ⇒ Boolean
True if the guard canary bytes are intact.
- #eql?(other) ⇒ Boolean
- #hash ⇒ Object
-
#inspect ⇒ String
Redacted representation (never reveals contents).
- #marshal_dump ⇒ Object
-
#size ⇒ Object
------------------------------------------------------------------.
-
#slice(rb_offset, rb_length) ⇒ PqcAsn1::SecureBuffer
Extract a sub-range of the buffer as a new SecureBuffer.
-
#to_pem(label = "PRIVATE KEY") ⇒ String
PEM-encode the DER contents of this SecureBuffer.
-
#to_pem_io(io, label = "PRIVATE KEY") ⇒ Integer
PEM-encode and write directly to an IO object.
-
#to_s ⇒ String
Frozen binary copy of the buffer contents.
-
#use ⇒ Object
SecureBuffer#use { |bytes| ... } — yield temporary heap copy for block.
-
#wipe! ⇒ self
Securely zero the buffer contents and mark it as wiped.
-
#wiped? ⇒ Boolean
True if #wipe! has been called.
-
#write_to(io) ⇒ Integer
Write the raw DER bytes directly to an IO object.
Class Method Details
.from_string(rb_str) ⇒ Object
462 463 464 465 466 467 468 469 470 471 |
# File 'ext/pqc_asn1/secure_buffer.c', line 462
static VALUE
pqcsb_from_string(VALUE klass, VALUE rb_str)
{
StringValue(rb_str);
const uint8_t *ptr = (const uint8_t *)RSTRING_PTR(rb_str);
size_t len = (size_t)RSTRING_LEN(rb_str);
if (len == 0)
rb_raise(rb_eArgError, "string must not be empty");
return pqcsb_rb_create(klass, ptr, len);
}
|
.random(rb_n) ⇒ Object
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'ext/pqc_asn1/secure_buffer.c', line 379
static VALUE
pqcsb_random(VALUE klass, VALUE rb_n)
{
long n_signed = NUM2LONG(rb_n);
if (n_signed <= 0)
rb_raise(rb_eArgError, "size must be > 0");
size_t n = (size_t)n_signed;
pqcsb_buf_t *buf = NULL;
pqcsb_status_t rc = pqcsb_create_random(n, &buf);
if (rc != PQCSB_OK)
rb_raise(rb_eRuntimeError, "pqcsb_create_random failed: %s",
pqcsb_error_message(rc));
VALUE obj = TypedData_Wrap_Struct(klass, &pqcsb_buf_type, buf);
rb_obj_freeze(obj);
return obj;
}
|
Instance Method Details
#==(other) ⇒ Object
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'ext/pqc_asn1/secure_buffer.c', line 293
static VALUE
pqcsb_eq(VALUE self, VALUE other)
{
pqcsb_buf_t *buf = (pqcsb_buf_t *)RTYPEDDATA_DATA(self);
pqcsb_check_wiped(buf);
if (rb_obj_is_kind_of(other, s_cSecureBuffer)) {
pqcsb_buf_t *other_buf = (pqcsb_buf_t *)RTYPEDDATA_DATA(other);
pqcsb_check_wiped(other_buf);
return pqcsb_ct_equal_bufs(buf, other_buf) ? Qtrue : Qfalse;
}
if (RB_TYPE_P(other, T_STRING)) {
const uint8_t *other_ptr = (const uint8_t *)RSTRING_PTR(other);
size_t other_len = (size_t)RSTRING_LEN(other);
return pqcsb_ct_equal(buf, other_ptr, other_len) ? Qtrue : Qfalse;
}
return Qfalse;
}
|
#canary_ok? ⇒ Boolean
Returns true if the guard canary bytes are intact.
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#eql?(other) ⇒ Boolean
314 315 316 317 318 |
# File 'ext/pqc_asn1/secure_buffer.c', line 314
static VALUE
pqcsb_eql(VALUE self, VALUE other)
{
return pqcsb_eq(self, other);
}
|
#hash ⇒ Object
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'ext/pqc_asn1/secure_buffer.c', line 338
static VALUE
pqcsb_hash(VALUE self)
{
pqcsb_buf_t *buf = (pqcsb_buf_t *)RTYPEDDATA_DATA(self);
pqcsb_check_wiped(buf);
pqcsb_read_guard_t guard = pqcsb_begin_read(buf);
if (guard.status != PQCSB_OK)
rb_raise(rb_eRuntimeError, "pqcsb_begin_read failed: %s",
pqcsb_error_message(guard.status));
st_index_t h = rb_memhash(guard.data, (long)guard.len);
pqcsb_end_read(&guard);
h ^= s_hash_salt;
return ST2FIX(h);
}
|
#inspect ⇒ String
Returns redacted representation (never reveals contents).
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#marshal_dump ⇒ Object
329 330 331 332 333 334 335 336 |
# File 'ext/pqc_asn1/secure_buffer.c', line 329
static VALUE
pqcsb_marshal_dump(VALUE self)
{
(void)self;
rb_raise(rb_eTypeError,
"can't dump PqcAsn1::SecureBuffer (contains secret key material)");
return Qnil;
}
|
#slice(rb_offset, rb_length) ⇒ PqcAsn1::SecureBuffer
Extract a sub-range of the buffer as a new SecureBuffer. The sub-range is copied into a fresh mmap-protected region so the extracted material gets the same security guarantees as the source.
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#to_pem(label = "PRIVATE KEY") ⇒ String
PEM-encode the DER contents of this SecureBuffer.
The returned PEM String is an ordinary Ruby String on the heap, NOT a SecureBuffer. Discard it as soon as possible to limit exposure of secret key material.
1094 1095 1096 |
# File 'lib/pqc_asn1.rb', line 1094 def to_pem(label = "PRIVATE KEY") PqcAsn1::PEM.encode(self, label) end |
#to_pem_io(io, label = "PRIVATE KEY") ⇒ Integer
PEM-encode and write directly to an IO object. The PEM string is written and then discarded immediately.
1114 1115 1116 1117 |
# File 'lib/pqc_asn1.rb', line 1114 def to_pem_io(io, label = "PRIVATE KEY") pem = to_pem(label) io.write(pem) end |
#to_s ⇒ String
Returns frozen binary copy of the buffer contents.
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#use ⇒ Object
SecureBuffer#use { |bytes| ... } — yield temporary heap copy for block.
SECURITY MODEL
This method provides secure access to encrypted key material by:
-
GUARD-BASED UNLOCKING
- Calls pqcsb_begin_read() which returns a guard struct containing a pointer to the protected mmap region (temporarily unprotected to PROT_READ)
- Guard tracks access via atomic refcount (multiple #use calls can nest)
-
TEMPORARY HEAP COPY
- Creates a new Ruby String on the heap containing a copy of the key material
- This allows Ruby code to work with the bytes (Ruby APIs need mutable Strings)
- Trade-off: temporary copy exists on heap during block execution (unavoidable limitation of Ruby String semantics)
-
GUARANTEED CLEANUP via rb_ensure
- Even if the block raises an exception, pqcsb_use_ensure() guarantees: a) Heap copy is securely zeroed b) Heap copy is frozen (escaped references become useless) c) mmap region is re-protected to PROT_NONE (via pqcsb_end_read)
-
NESTED CALL SAFETY
- Multiple #use calls can nest (read_refs tracks depth)
- Re-entry only re-protects on the outermost exit
- Prevents premature re-protection while outer caller still needs access
WHY GUARD IN CONTEXT?
The guard MUST be stored as a pointer in the context struct so it remains valid through the rb_ensure lifecycle:
pqcsb_read_guard_t guard = pqcsb_begin_read(...); // Guard on stack
ctx = { .guard = &guard }; // Store POINTER
rb_ensure(..., pqcsb_use_ensure, (VALUE)&ctx); // Ensure sees &guard
If we passed guard by value, it might go out of scope before ensure runs. Storing &guard ensures the original stack-allocated guard is accessible.
RETURN VALUE
Returns the block's return value (not the bytes), allowing: SecureBuffer#use { |b| b.bytesize } # => integer SecureBuffer#use { |b| b.reverse } # => reversed bytes
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#wipe! ⇒ self
Securely zero the buffer contents and mark it as wiped. Subsequent #use calls will raise.
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#wiped? ⇒ Boolean
Returns true if #wipe! has been called.
717 |
# File 'lib/pqc_asn1.rb', line 717 class SecureBuffer; end |
#write_to(io) ⇒ Integer
Write the raw DER bytes directly to an IO object. The bytes pass through a temporary #use block and are written immediately, minimising exposure time on the Ruby heap.
1104 1105 1106 |
# File 'lib/pqc_asn1.rb', line 1104 def write_to(io) use { |bytes| io.write(bytes) } end |