Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2026-06-16 - Unhandled IndexError Hardening in Cryptography Routines
**Vulnerability:** Amber's `MessageVerifier` and `MessageEncryptor` attempted to slice arrays and decrypt payloads before checking the bounds of the provided data (`IndexError` due to missing `split` size check and negative offsets in slices). While top-level middleware rescues and returns a 500 status code, these classes should gracefully reject malformed payloads themselves with custom exceptions.
**Learning:** Crystal's array slicing and destructuring raise `IndexError` when bounds are not met, which propagates to an uncaught exception instead of a graceful rejection. Always check payload sizes before executing cryptography functions.
**Prevention:** Always validate size and structure before extracting signatures, IVs, or payload data from untrusted inputs in cryptography routines. Add `size` bounds checks and safely handle destructuring, raising typed exceptions like `InvalidSignature` and `InvalidMessage`.
31 changes: 31 additions & 0 deletions spec/amber/support/message_encryptor_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require "../../spec_helper"

module Amber::Support
describe MessageEncryptor do
secret = "a" * 32
encryptor = MessageEncryptor.new(secret)

it "encrypts and decrypts valid messages successfully" do
message = "Hello, world!"
encrypted = encryptor.encrypt_and_sign(message)
decrypted = encryptor.verify_and_decrypt(encrypted)
String.new(decrypted).should eq message
end

it "raises Exceptions::InvalidMessage for undersized payloads in verify_and_decrypt" do
# signature_size is 32, block_size is 16. Total required: >= 48 bytes
undersized = Bytes.new(20)
expect_raises(Exceptions::InvalidMessage, "Invalid message size") do
encryptor.verify_and_decrypt(undersized)
end
end

it "raises Exceptions::InvalidMessage for undersized payloads in decrypt" do
# block_size is 16. decrypt expects >= 16 bytes
undersized = Bytes.new(10)
expect_raises(Exceptions::InvalidMessage, "Invalid message size") do
encryptor.decrypt(undersized)
end
end
end
end
27 changes: 27 additions & 0 deletions spec/amber/support/message_verifier_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require "../../spec_helper"

module Amber::Support
describe MessageVerifier do
secret = "a" * 32
verifier = MessageVerifier.new(secret)

it "signs and verifies valid messages successfully" do
message = "Hello, world!"
signed = verifier.generate(message)
verified = verifier.verified(signed)
verified.should eq message
end

it "returns nil for malformed/no-delimiter payloads in verified" do
malformed = "invalidpayload"
verifier.verified(malformed).should be_nil
end

it "raises Exceptions::InvalidSignature for malformed/no-delimiter payloads in verify_raw" do
malformed = "invalidpayload"
expect_raises(Exceptions::InvalidSignature) do
verifier.verify_raw(malformed)
end
end
end
end
8 changes: 8 additions & 0 deletions src/amber/support/message_encryptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ module Amber::Support
# Verify and Decrypt a message. We need to verify the message in order to
# avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
def verify_and_decrypt(value : Bytes) : Bytes
if value.size < @signature_size + @block_size
raise Exceptions::InvalidMessage.new("Invalid message size")
end

signature = value[value.size - @signature_size, @signature_size]
data_iv = value[0, value.size - @signature_size]
if Crypto::Subtle.constant_time_compare(sign_bytes(data_iv), signature)
Expand Down Expand Up @@ -48,6 +52,10 @@ module Amber::Support
end

def decrypt(value : Bytes)
if value.size < @block_size
raise Exceptions::InvalidMessage.new("Invalid message size")
end

cipher = OpenSSL::Cipher.new(@cipher_algorithm)
data = value[0, value.size - @block_size]
iv = value[value.size - @block_size, @block_size]
Expand Down
10 changes: 8 additions & 2 deletions src/amber/support/message_verifier.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ module Amber::Support
end

def verified(signed_message : String)
data, digest = signed_message.split("--")
parts = signed_message.split("--", 2)
return unless parts.size == 2
data, digest = parts[0], parts[1]

if valid_message?(data, digest)
String.new(decode(data))
end
Expand All @@ -25,7 +28,10 @@ module Amber::Support
end

def verify_raw(signed_message : String) : Bytes
data, digest = signed_message.split("--")
parts = signed_message.split("--", 2)
raise(Exceptions::InvalidSignature.new) unless parts.size == 2
data, digest = parts[0], parts[1]

if valid_message?(data, digest)
decode(data)
else
Expand Down