-
Notifications
You must be signed in to change notification settings - Fork 0
How it works
The design, and the reasoning behind the parts that look strange.
BlackBerry code signing was a runtime check inside the device VM: it fired when a module touched a protected API, and every signature had to be issued by BlackBerry's signing authority, which no longer exists. Applications that need a signature can no longer be built by anyone.
Code that touches no protected API needs no signature. So berryssh uses only standard MIDP 2.0 and CLDC 1.1, and the build needs no BlackBerry tooling at all — the API stubs come from microemu, off Maven Central.
The cost is real: this rules out BlackBerry's own UI framework. The screens are MIDP, drawn by the device in its own theme, and they are not — and cannot be — the native widgets.
CLDC 1.1 has no java.security, no javax.crypto, and no BigInteger. Every
primitive is implemented in the project.
That sounds worse than it is, because the algorithm choices make it tractable:
| Why it suits this hardware | |
|---|---|
curve25519-sha256 |
Fixed 32-byte field arithmetic, no BigInteger
|
ssh-ed25519 |
Same curve arithmetic, nothing extra to carry |
chacha20-poly1305 |
32-bit add/xor/rotate, no lookup tables; AEAD, so no separate MAC |
SHA-256 |
Small and self-contained |
The classic 2011 SSH stack is anchored on 2048-bit modular exponentiation, which is genuinely slow in interpreted Java on this class of CPU. Modern cryptography is the cheaper option here, not the more expensive one — and it means the handset reaches a current OpenSSH without that server being weakened to meet it.
Connection takes an InputStream and an OutputStream. It has never known
where they came from.
That one decision is why the protocol code — transport, key exchange, authentication, channels — is free of MIDP entirely, and can therefore run on a desktop JVM against a real OpenSSH server. Encodings can be proved with vectors; a protocol cannot. A key exchange either convinces OpenSSH or it does not.
It is also why the WebSocket cost almost nothing: a WebSocket-backed stream pair substitutes with no change above it at all.
Two halves, neither needing the device:
-
Compiling at
-source 1.3against the CLDC bootclasspath proves the code will run on the handset. - Executing on the host JVM proves it is correct.
228 offline vectors: FIPS 180-4, RFC 8439, RFC 7748, RFC 8032, RFC 4231, RFC 4251 §5, RFC 4253 §6 and §7, RFC 8731 §3, plus the terminal, the character mapping, and every path that has to reject malformed input.
Then 39 more against a real server, covering what vectors cannot: a negotiated key exchange, an authenticated session, a pty, a shell, a rekey, typing while output arrives, and a bridge refusing a wrong key.
Everything runs under a Turkish default locale, deliberately. "I".toLowerCase()
is a dotless ı there, and protocol code that case-folds anything is broken in
a way that raises no error. Running the suite in the condition that exposes it
beats running it in the one that hides it.
CLDC has no SecureRandom, and java.util.Random is a 48-bit LCG seeded from
the clock — useless for key material. EntropyPool harvests timing jitter into
a SHA-256 based DRBG, refuses to produce anything until it holds 256 bits, and
ratchets its state after every output so recovering it afterwards reveals
nothing about what came before.
java.util.Random in CLDC also has no nextBytes. That kind of thing is why
the build compiles against the real CLDC stubs rather than trusting a memory of
what the API contains.
VT320 is ported from BBSSH, which
solved this well and is credited in the file. The rendering is not: MIDP
exposes three font sizes and the smallest gives a 21×13 terminal on a 480×360
screen. berryssh renders from a bitmap font atlas instead, which is what makes
60×25 possible.
The atlas covers ASCII, Latin-1, the Turkish letters, box drawing and block elements. A character with no cell draws a hollow box rather than nothing, so a missing glyph looks like a missing glyph instead of a rendering bug.
Two, and they share a transport. A reader parked on a socket read must not block a keystroke from being sent, so the transport has separate locks per direction — one lock would have meant every keypress waiting on the network.
Channel writes wait on a window monitor rather than pumping the socket themselves; a second reader on the same socket corrupts the stream, and it does so intermittently enough to look like a network problem.
Saved connections and host keys live in RMS, the device's record store. Both formats are versioned and every older layout is still read: a connection list that empties itself on upgrade is worse than whatever feature caused it.
Everything is UTF-8 on the way in and out. The device's default encoding is
ISO8859_1, which would quietly mangle a hostname or a password containing a
Turkish letter.