Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
1059923
Bastion sample: skeleton, double-entry multi-asset ledger and hash-ch…
shai-almog Oct 21, 2026
cef4d81
Bastion sample: sign-in, second factor, passkeys, lockout and audited…
shai-almog Oct 21, 2026
e5f9850
Bastion sample: accounts, transfers, payees, cards, signed step-up an…
shai-almog Oct 21, 2026
8c2bb23
Bastion sample: crypto exchange on the ledger, crypto withdrawals and…
shai-almog Oct 21, 2026
cd9f8d7
Bastion sample: app foundation -- guarded network, PKCE sign-in with …
shai-almog Oct 21, 2026
df4cfd3
Fix BigInteger division, modInverse and BigDecimal rounding; add EC P…
shai-almog Oct 21, 2026
65dc3e8
Bastion sample: every screen -- onboarding, dashboard, accounts, tran…
shai-almog Oct 21, 2026
081612a
Bastion sample: device key on core ES256, assets from the server, pas…
shai-almog Oct 21, 2026
ec0fffa
Bastion sample: CI workflow, live and web e2e, user's and implementor…
shai-almog Oct 21, 2026
ab7f9bb
Bastion sample: offer it as an Initializr template
shai-almog Oct 22, 2026
28a9c2e
Bastion sample: browser sign-in with PKCE and TOTP, passkeys in the J…
shai-almog Oct 22, 2026
df69022
Bastion sample: biometric-bound app key, PIN vault fallback, five lan…
shai-almog Oct 22, 2026
097dba5
Bastion sample: guide screenshots, diagrams and reference tables; web…
shai-almog Oct 22, 2026
eb4b5f6
Bastion sample: client review round - sign-out wipes keys, owner-boun…
shai-almog Oct 22, 2026
b370d5a
Bastion sample: server and framework review round
shai-almog Oct 22, 2026
31f8668
Sensitive clipboard API; Bastion reload keeps the session; password s…
shai-almog Oct 22, 2026
90b6e6b
Bastion review round 2: step-up lockout, signed card/payee/device cha…
shai-almog Oct 22, 2026
0e96a91
Bastion review round 3: signed and limited orders with a price collar…
shai-almog Oct 22, 2026
c1c9aaa
Bastion review round 4: locking frozen-account read, transition-free …
shai-almog Oct 22, 2026
aeb6e17
Merge remote-tracking branch 'origin/master' into bastion-bank-sample
shai-almog Oct 22, 2026
7d4d15e
CI: guide snippets by include, copyright headers, Play Integrity rais…
shai-almog Oct 23, 2026
36bd2e2
iOS: publish a sensitive clip local only and expiring on the first write
shai-almog Oct 23, 2026
97506d0
Bastion: a passkey spares the password only when the user was verified
shai-almog Oct 23, 2026
d49007c
Bastion app: step-up dialog field keeps its height; client matches th…
shai-almog Oct 23, 2026
22391ed
Initializr: resync the Bastion template with the sample
shai-almog Oct 23, 2026
d6e52af
Bastion guide: US spelling for LanguageTool
shai-almog Oct 23, 2026
45f655a
Bastion web e2e: check what a field holds, and retype on a doubled ke…
shai-almog Oct 23, 2026
d8a9a81
Bastion: the bank's books - chart of accounts, statements, close, audit
shai-almog Oct 23, 2026
6aebe01
FileDialogSupport: mark the intentional Command identity check for PMD
shai-almog Oct 23, 2026
3755090
Bastion app: charts, a staff console, and one way back on every screen
shai-almog Oct 23, 2026
25a22ef
Bastion: refined charts, honest volume, fitted phone tables, composed…
shai-almog Oct 23, 2026
08103d3
Merge origin/master into bastion-bank-sample
shai-almog Oct 23, 2026
432e5f5
Bastion: close the red-team findings in the books
shai-almog Oct 23, 2026
8d23a71
Bastion guide: name the chart styles rather than a wildcard
shai-almog Oct 23, 2026
17f4874
Bastion guides: three LanguageTool findings
shai-almog Oct 23, 2026
99cd5f4
Bastion: serialize a maker's approvals, explain a refused e2e code
shai-almog Oct 23, 2026
a72ade9
Android clipboard: clear a timed clip by in-process ownership, not re…
shai-almog Oct 23, 2026
cb79ae8
Android clipboard: defer a background clear until the clip is provabl…
shai-almog Oct 23, 2026
14be48e
Android clipboard: retry a deferred clear on window focus, document p…
shai-almog Oct 23, 2026
bc4500e
Bastion: keep the clipboard record until a read decides it, key its d…
shai-almog Oct 23, 2026
22e6a62
Bastion web e2e: let a typed field settle before reading it back
shai-almog Oct 23, 2026
3d462bc
Flutter bench: accept the Android code-size move from the clipboard h…
shai-almog Oct 24, 2026
da86028
Merge master; gate Bastion in the Initializr on a release that has it…
shai-almog Oct 24, 2026
0a24a5f
Initializr: generate Bastion's probes at its own release, test the re…
shai-almog Oct 24, 2026
1d248e6
Bastion: button spacing, a real card, full receipts and statements, n…
shai-almog Oct 24, 2026
dc39499
Bastion: copy buttons, printable reports, and sign-in held to the str…
shai-almog Oct 24, 2026
bac6c12
JavaScript port: typing on a form with text selection on is no longer…
shai-almog Oct 24, 2026
0f36d11
Bastion: print reports as a paginated PDF with repeated headers and p…
shai-almog Oct 24, 2026
1aff508
Bastion: errors and sentences wrap, text selection on every form
shai-almog Oct 24, 2026
a398fd3
Bastion web e2e: one tap opens a market, no retry
shai-almog Oct 24, 2026
df527af
Initializr: resync the Bastion template with the sample
shai-almog Oct 24, 2026
39097de
docs: Bastion guides, contractions for Vale
shai-almog Oct 24, 2026
1d79b24
Security: Keccak-256, SHA3-256 and secp256k1 ECDSA in core, shared wi…
shai-almog Oct 25, 2026
3f6ee30
Bastion: real Ether on chain, security audit fixes, checklist, UI polish
shai-almog Oct 25, 2026
5f97f49
docs: compilable secp256k1 snippet, Bastion guide prose gates
shai-almog Oct 25, 2026
d8cede5
Merge remote-tracking branch 'origin/master' into bastion-bank-sample
shai-almog Oct 25, 2026
1beee9d
Bastion: adopt master's Instant mapping and PKI; App Attest; skill up…
shai-almog Oct 25, 2026
9b9d7c3
Merge origin/master; Bastion: App Attest builds on the compiled backe…
shai-almog Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
711 changes: 711 additions & 0 deletions .github/workflows/bastion.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/workflows/check-doc-snippets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
- 'docs/demos/**'
- 'scripts/wayline/**'
- 'scripts/savor/**'
- 'scripts/bastion/**'
- 'scripts/skirmish/**'
- 'docs/website/content/blog/**'
- 'CodenameOne/src/**/*.java'
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/developer-guide-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ on:
# The Savor and Parley chapters, the same way; savor.yml and parley.yml
# compile and test the samples.
- 'scripts/savor/**'
# The Bastion chapters, the same way; bastion.yml compiles and tests it.
- 'scripts/bastion/**'
- 'scripts/skirmish/**'
- 'scripts/parley/**'
# The API-name check derives the published pages from the javadoc's own
Expand Down Expand Up @@ -130,12 +132,13 @@ jobs:
- 'Ports/JavaSE/**'
- 'maven/core/**'
- 'maven/javase/**'
# The Wayline, Savor and Parley chapters include these trees by tag, so a
# The Wayline, Savor, Parley and Bastion chapters include these trees by tag, so a
# renamed or deleted tag there has to reach the snippet check.
- 'scripts/wayline/**'
- 'scripts/savor/**'
- 'scripts/skirmish/**'
- 'scripts/parley/**'
- 'scripts/bastion/**'
workflow:
- '.github/workflows/developer-guide-docs.yml'

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/initializr-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ on:
- 'scripts/savor/common/src/test/**'
- 'scripts/skirmish/common/src/test/**'
- 'scripts/parley/common/src/test/**'
- 'scripts/bastion/common/src/test/**'
schedule:
- cron: '17 3 * * *'
workflow_dispatch:
Expand All @@ -42,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
template: [BAREBONES, KOTLIN, WAYLINE, SAVOR, SKIRMISH, PARLEY]
template: [BAREBONES, KOTLIN, WAYLINE, SAVOR, SKIRMISH, PARLEY, BASTION]
steps:
- uses: actions/checkout@v6

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/parparvm-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ on:
- 'CodenameOne/src/com/codename1/security/Hash.java'
- 'CodenameOne/src/com/codename1/security/Hmac.java'
- 'CodenameOne/src/com/codename1/security/MessageDigestImpl.java'
- 'CodenameOne/src/com/codename1/security/Secp256k1.java'
- 'CodenameOne/src/com/codename1/security/Otp.java'
- 'CodenameOne/src/com/codename1/security/Base32.java'
- 'CodenameOne/src/com/codename1/security/CryptoException.java'
Expand Down Expand Up @@ -122,6 +123,7 @@ on:
- 'CodenameOne/src/com/codename1/security/Hash.java'
- 'CodenameOne/src/com/codename1/security/Hmac.java'
- 'CodenameOne/src/com/codename1/security/MessageDigestImpl.java'
- 'CodenameOne/src/com/codename1/security/Secp256k1.java'
- 'CodenameOne/src/com/codename1/security/Otp.java'
- 'CodenameOne/src/com/codename1/security/Base32.java'
- 'CodenameOne/src/com/codename1/security/CryptoException.java'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/website-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ on:
# requests: developer-guide-docs.yml already renders the guide for those.
- 'scripts/wayline/**'
- 'scripts/savor/**'
- 'scripts/bastion/**'
- 'scripts/skirmish/**'
- 'scripts/parley/**'
- 'scripts/website/**'
Expand Down
18 changes: 18 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6160,6 +6160,24 @@ public void copyToClipboard(ClipboardContent content) {
copyToClipboard((Object) content);
}

/// Takes a content copied with `ClipboardContent#setClearAfter(int)` off the clipboard once
/// its time is up. Called only when this application has copied nothing since. The default
/// replaces it with an empty clip, and only while the clipboard's text is still this
/// content's: another application may have copied over it meanwhile, and that copy is the
/// user's. A port that can identify its own clip checks that instead, and one whose platform
/// expires the content by itself does nothing.
///
/// #### Parameters
///
/// - `content`: the content that was copied
public void clearClipboardContent(ClipboardContent content) {
String copied = getPlainTextForClipboard(content);
if (copied == null || !copied.equals(getPlainTextForClipboard(getPasteDataFromClipboard()))) {
return;
}
copyToClipboard(new ClipboardContent());
}

/// Returns the current content of the clipboard
///
/// #### Returns
Expand Down
2 changes: 2 additions & 0 deletions CodenameOne/src/com/codename1/impl/FileDialogSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ public void run() {
d.placeButtonCommands(new Command[]{cancel, open});
Command picked = d.showAtPosition(2, 2, 2, 2, true);
String[] paths = tree.getSelectedPaths();
// Identity on purpose: Command.equals compares names, and an accept label of
// "Cancel" would make the two buttons equal.
if (picked == open && paths.length > 0) { //NOPMD CompareObjectsWithEquals
deliver(new FileDialogResult(request, paths, -1, null), callback);
} else {
Expand Down
197 changes: 197 additions & 0 deletions CodenameOne/src/com/codename1/security/EcKeys.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.security;

import com.codename1.impl.EcP256Codec;
import com.codename1.util.BigInteger;

/// Completes a P-256 PKCS#8 private key with its public point.
///
/// RFC 5915 makes the `publicKey` field of ECPrivateKey optional, and
/// java.security leaves it out -- so a key generated on a server or on the
/// JavaSE port has none. Apple's SecKey cannot import an EC private key
/// without the point, and cannot derive it, so [Signature#sign] hands the
/// port this completed form. The point is computed once per key (see
/// [PrivateKey]) with a fixed-length Montgomery ladder, see [#multiply].
final class EcKeys {
private static final BigInteger P = new BigInteger(
"ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
private static final BigInteger GX = new BigInteger(
"6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);
private static final BigInteger GY = new BigInteger(
"4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);

private EcKeys() {}

/// Returns `pkcs8` itself when it already carries the point or is not a
/// P-256 key [EcP256Codec] understands, otherwise the same key re-encoded
/// with the point.
static byte[] withPublicPoint(byte[] pkcs8) {
byte[][] parts;
try {
parts = EcP256Codec.fromPkcs8(pkcs8);
} catch (RuntimeException notP256) {
return pkcs8;
}
if (parts[1] != null) {
return pkcs8;
}
BigInteger[] q = multiply(new BigInteger(1, parts[0]));
if (q == null) {
return pkcs8;
}
byte[] point = new byte[EcP256Codec.POINT_LENGTH];
point[0] = 0x04;
putFixed(q[0], point, 1);
putFixed(q[1], point, 33);
return EcP256Codec.pkcs8FromScalar(parts[0], point);
}

private static void putFixed(BigInteger v, byte[] out, int off) {
byte[] b = v.toByteArray();
int start = b.length > 32 ? b.length - 32 : 0;
int len = b.length - start;
System.arraycopy(b, start, out, off + 32 - len, len);
}

/// `k * G` in affine coordinates, or null for the point at infinity.
///
/// A Montgomery ladder over all 256 bits of the scalar, on projective
/// points with the complete addition formulas of Renes, Costello and
/// Batina ("Complete addition formulas for prime order elliptic curves",
/// 2016, algorithm 4 for a = -3). Every bit costs the same: one addition
/// and one doubling, both through the one formula, which has no special
/// case for the identity or for equal points, and no field inversion until
/// the single one at the end. The two ladder points trade places by array
/// index, never by a branch on the bit. What the private scalar decides is
/// therefore not which operations run, nor how many, nor whether a short
/// key ends early.
///
/// It is not constant time end to end: the big-integer arithmetic
/// underneath (`multiply`, `mod`) takes time that depends on its operands'
/// values and lengths, so the timing of a derivation still leaks something
/// about the scalar to an observer who can measure it closely and many
/// times. That is acceptable for its one use here -- deriving a stored
/// key's public point once, on the device -- and is why a port with a
/// native derivation should prefer it.
///
/// A scalar outside `[1, n - 1]` is no private key: zero and `n` give the
/// point at infinity, and anything larger, or negative, would alias a
/// smaller key or fall outside the 256 bits the ladder walks. It answers
/// null, as the point at infinity does.
static BigInteger[] multiply(BigInteger k) {
if (k == null || k.signum() <= 0 || k.compareTo(N) >= 0) {
return null;
}
BigInteger[][] r = {{BigInteger.ZERO, BigInteger.ONE, BigInteger.ZERO},
{GX, GY, BigInteger.ONE}};
for (int i = 255; i >= 0; i--) {
int bit = k.testBit(i) ? 1 : 0;
// With bit = 1 the roles swap: r[1] is doubled and r[0] gets the sum.
BigInteger[] lo = r[bit];
BigInteger[] hi = r[1 - bit];
BigInteger[] sum = add(lo, hi);
BigInteger[] twice = add(lo, lo);
r[bit] = twice;
r[1 - bit] = sum;
}
BigInteger[] q = r[0];
if (q[2].signum() == 0) {
return null;
}
// P is prime and 0 < z < P, so the inverse exists (BigIntegerModInverseTest
// holds modInverse to 256-bit operands).
BigInteger zInv = q[2].mod(P).modInverse(P);
return new BigInteger[]{q[0].multiply(zInv).mod(P), q[1].multiply(zInv).mod(P)};
}

/// The order of the base point.
private static final BigInteger N = new BigInteger(
"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);

/// The curve's `b`.
private static final BigInteger B = new BigInteger(
"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);

private static BigInteger m(BigInteger a, BigInteger b) {
return a.multiply(b).mod(P);
}

private static BigInteger p(BigInteger a, BigInteger b) {
return a.add(b).mod(P);
}

private static BigInteger s(BigInteger a, BigInteger b) {
return a.subtract(b).mod(P);
}

/// Complete projective addition, `u + v`, for a = -3 (RCB algorithm 4).
/// Valid for every pair of points, the identity (0:1:0) and `u == v`
/// included.
private static BigInteger[] add(BigInteger[] u, BigInteger[] v) {
BigInteger x1 = u[0];
BigInteger y1 = u[1];
BigInteger z1 = u[2];
BigInteger x2 = v[0];
BigInteger y2 = v[1];
BigInteger z2 = v[2];
BigInteger t0 = m(x1, x2);
BigInteger t1 = m(y1, y2);
BigInteger t2 = m(z1, z2);
BigInteger t3 = m(p(x1, y1), p(x2, y2));
BigInteger t4 = p(t0, t1);
t3 = s(t3, t4);
t4 = m(p(y1, z1), p(y2, z2));
BigInteger x3 = p(t1, t2);
t4 = s(t4, x3);
x3 = m(p(x1, z1), p(x2, z2));
BigInteger y3 = p(t0, t2);
y3 = s(x3, y3);
BigInteger z3 = m(B, t2);
x3 = s(y3, z3);
z3 = p(x3, x3);
x3 = p(x3, z3);
z3 = s(t1, x3);
x3 = p(t1, x3);
y3 = m(B, y3);
t1 = p(t2, t2);
t2 = p(t1, t2);
y3 = s(y3, t2);
y3 = s(y3, t0);
t1 = p(y3, y3);
y3 = p(t1, y3);
t1 = p(t0, t0);
t0 = p(t1, t0);
t0 = s(t0, t2);
t1 = m(t4, y3);
t2 = m(t0, y3);
y3 = m(x3, z3);
y3 = p(y3, t2);
x3 = m(t3, x3);
x3 = s(x3, t1);
z3 = m(t4, z3);
t1 = m(t3, t0);
z3 = p(z3, t1);
return new BigInteger[]{x3, y3, z3};
}
}
20 changes: 19 additions & 1 deletion CodenameOne/src/com/codename1/security/Hash.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
/// - [#SHA256] -- 256 bit (SHA-2 family, recommended general-purpose hash)
/// - [#SHA384] -- 384 bit (SHA-2 family)
/// - [#SHA512] -- 512 bit (SHA-2 family)
/// - [#SHA3_256] -- 256 bit (SHA-3, FIPS 202)
/// - [#KECCAK256] -- 256 bit original Keccak, the hash Ethereum uses
/// for addresses, transaction hashes and ABI selectors. It is NOT SHA3-256:
/// the two differ in the padding byte and give different digests.
///
/// #### Quick example
///
Expand Down Expand Up @@ -74,6 +78,14 @@ public final class Hash {
/// SHA-512 algorithm identifier (512-bit digest, SHA-2 family).
public static final String SHA512 = "SHA-512";

/// SHA3-256 algorithm identifier (256-bit digest, FIPS 202).
public static final String SHA3_256 = "SHA3-256";

/// Keccak-256 algorithm identifier (256-bit digest). This is the original
/// Keccak submission with the `0x01` padding byte, as used throughout
/// Ethereum; standardised SHA3-256 pads with `0x06` and is [#SHA3_256].
public static final String KECCAK256 = "KECCAK-256";

private final MessageDigestImpl impl;

private Hash(MessageDigestImpl impl) {
Expand All @@ -85,7 +97,8 @@ private Hash(MessageDigestImpl impl) {
/// #### Parameters
///
/// - `algorithm`: one of [#MD5], [#SHA1], [#SHA224], [#SHA256], [#SHA384],
/// [#SHA512] -- case insensitive, with or without the dash
/// [#SHA512], [#SHA3_256], [#KECCAK256] -- case insensitive, with or
/// without the dash
///
/// #### Returns
///
Expand Down Expand Up @@ -184,6 +197,11 @@ public static byte[] sha512(byte[] data) {
return create(SHA512).digest(data);
}

/// One-shot Keccak-256 hash (Ethereum's hash, see [#KECCAK256]).
public static byte[] keccak256(byte[] data) {
return create(KECCAK256).digest(data);
}

// ----------------------------------------------------------------
// hex helpers -- handy for displaying digests and writing test vectors

Expand Down
5 changes: 5 additions & 0 deletions CodenameOne/src/com/codename1/security/Hmac.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ private static int blockSizeFor(String algorithm) {
if ("SHA384".equals(a) || "SHA512".equals(a)) {
return 128;
}
if ("SHA3256".equals(a) || "KECCAK256".equals(a)) {
// FIPS 202: the block size of a sponge is its rate, 136 bytes for
// a 256-bit output.
return 136;
}
throw new CryptoException("unsupported HMAC algorithm: " + algorithm);
}

Expand Down
Loading
Loading