Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.codenameone.developerguide.backend.security;

import com.codename1.backend.Crypto;
import com.codename1.backend.security.pki.Certificate;
import com.codename1.backend.security.pki.CertificateValidator;
import com.codename1.backend.security.pki.CertificationRequest;
import com.codename1.backend.security.pki.DistinguishedName;
import com.codename1.backend.security.crypto.KeyFiles;
import com.codename1.backend.security.pki.Pkcs12;
import java.util.Arrays;
import java.util.List;

/** The certificates section of the Backend security chapter, compiled so it cannot drift. */
public final class PkiSnippets {

private PkiSnippets() {
}

public static void pki(String presentedPem, byte[] p12Bytes, String password)
throws Exception {
// tag::backend-security-pki[]
// A PEM chain, validated against the partner's CA and nothing else
List<Certificate> anchors = Certificate.listFromPem(KeyFiles.read("/etc/app/partner-ca.pem"));
List<Certificate> path = new CertificateValidator(anchors)
.forHost("partner.example.com")
.validate(Certificate.listFromPem(presentedPem));

// A key and a certification request to send to a CA
byte[] key = Crypto.generateEcKey(Crypto.P256);
String csr = CertificationRequest.create(DistinguishedName.parse("CN=api.example.com"),
key, Arrays.asList("api.example.com")).toPem();

// A .p12 in, and one out
Pkcs12 store = Pkcs12.decode(p12Bytes, password);
byte[] p12 = Pkcs12.encode(store.getPrivateKey(), store.getCertificates(), "api", password);
// end::backend-security-pki[]
}
}
67 changes: 67 additions & 0 deletions docs/developer-guide/Backend-Security.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,73 @@ recovery code and a rate limit's last permit.
`OAuth2AuthorizationService.purgeExpired` remove rows nobody will present
again. The first two are yours to call from a scheduled job.

[[backend-security-pki]]
=== Certificates, key stores and key agreement

`com.codename1.backend.security.pki` reads and writes the files a server
exchanges with certificate authorities and partners. It's plain Java over the
same primitives as the rest of the layer, so a local run and the native build
give the same answers.

[source,java,role=cn1-no-try]
----
include::../demos/backend/src/main/java/com/codenameone/developerguide/backend/security/PkiSnippets.java[tag=backend-security-pki,indent=0]
----

Keys are bytes in two forms: PKCS#8 DER for a private key and
SubjectPublicKeyInfo DER for a public one. `Crypto.sign`, `Crypto.verify` and
`Crypto.ecdh` take those forms, `KeyFiles` reads them from PEM, and
`Der.publicKeyOf` derives the public half of a private key.

`Crypto.ecdh` returns the shared secret of two P-256 or P-384 keys, as SEC 1
defines it. That secret is key material, not a key: derive the key from it
with a KDF such as HKDF.

A `CertificateValidator` trusts only the anchors it's given. There is no
default trust store, so a server that accepts certificates from a partner
trusts that partner's CA and no other. The validator builds a path from the
leaf through the other certificates, which may come in any order, and checks:

* every signature;
* the validity periods, at the current time or the one `at` gives;
* basic constraints, key usage and path length on every issuer;
* that no critical extension is one it doesn't understand;
* the host name and extended key usage, when `forHost` or `forPurpose` asks
for them.

Host names match the subject alternative names only. The common name is never
used, as RFC 9525 requires. The validator doesn't check revocation.

The algorithm profile is the same on every runtime:

[cols="1,2,2",options="header"]
|===
|Use |Supported |Refused

|Signatures on certificates and requests
|RSA with SHA-256, SHA-384 or SHA-512; RSASSA-PSS with SHA-256; ECDSA with
P-256 and SHA-256, or P-384 and SHA-384
|SHA-1, MD5, ECDSA with a digest that doesn't match its curve

|Keys
|RSA from 2048 to 8192 bits; EC on P-256 and P-384
|Other curves, DSA, Ed25519

|PKCS#12
|PBES2 with PBKDF2 and AES-CBC, with an HMAC integrity check. This is what
OpenSSL 3 and current JDKs write
|The legacy RC2 and 3DES schemes (`openssl pkcs12 -legacy`), stores without
an integrity check

|Encoding
|DER and PEM
|BER indefinite lengths
|===

A file outside the profile is refused with a message that names what it uses.
For a legacy PKCS#12 file, the message also gives the `openssl` command that
re-exports it.

[[backend-security-testing]]
=== Testing

Expand Down
Loading
Loading