|
1 | 1 | [](https://github.com/eXist-db/expath-crypto-module/actions?query=workflow%3ACI) |
2 | 2 |
|
3 | | -# eXist-db implementation for EXPath Cryptographic Module |
| 3 | +# EXPath Crypto Module for eXist-db |
4 | 4 |
|
5 | | -This is an eXist-db implementation of the [EXPath HTTP Crypto Module specification](http://expath.org/spec/crypto). |
6 | | -## Building from source |
| 5 | +Version 7.0 — a drop-in replacement for [expath-crypto-module](https://github.com/eXist-db/expath-crypto-module) 6.x. |
7 | 6 |
|
8 | | -Requires: |
9 | | -* Java 1.8 or newer |
10 | | -* Maven 3.6 or newer |
| 7 | +Implements the [EXPath Cryptographic Module](https://expath.org/spec/crypto) specification for eXist-db using Java's built-in JCE — no external dependencies. Function signatures are compatible with both the old eXist module and [BaseX's crypto module](https://docs.basex.org/main/Cryptographic_Functions) for cross-engine portability. |
| 8 | + |
| 9 | +## Requirements |
| 10 | + |
| 11 | +- **eXist-db 7.0+** (uses `exist.xml` auto-registration) |
| 12 | +- **Java 21+** (uses `HexFormat`, switch expressions, pattern matching) |
| 13 | + |
| 14 | +Users on eXist 6 / Java 8–11 should continue using [expath-crypto-module 6.x](https://github.com/eXist-db/expath-crypto-module). |
| 15 | + |
| 16 | +## Install |
11 | 17 |
|
12 | 18 | ```bash |
13 | | -$ git clone https://github.com/eXist-db/expath-crypto-module.git |
14 | | -$ cd expath-crypto-module |
15 | | -$ mvn clean package |
| 19 | +xst package install target/exist-crypto-7.0.0-SNAPSHOT.xar |
| 20 | +``` |
| 21 | + |
| 22 | +The `pre-install.xq` script automatically removes the old expath-crypto-module if present. The two packages share the same package URI (`http://expath.org/ns/crypto`) and cannot coexist. |
| 23 | + |
| 24 | +## Functions |
| 25 | + |
| 26 | +**Module namespace:** `http://expath.org/ns/crypto` |
| 27 | + |
| 28 | +| Function | Arities | Spec | BaseX | v6 compat | |
| 29 | +|----------|---------|------|-------|-----------| |
| 30 | +| `crypto:hash` | 2, 3 | ✓ §2 | — (use `fn:hash`) | ✓ | |
| 31 | +| `crypto:hmac` | 3, 4 | ✓ §3 | ✓ | ✓ | |
| 32 | +| `crypto:encrypt` | 4, 6 | ✓ §5 | ✓ (4-param) | ✓ | |
| 33 | +| `crypto:decrypt` | 4, 6 | ✓ §6 | ✓ (4-param) | ✓ | |
| 34 | +| `crypto:generate-signature` | 3, 6, 7, 8 | ✓ §7 | ✓ (6+2 optional) | ✓ | |
| 35 | +| `crypto:validate-signature` | 1 | ✓ §8 | ✓ | ✓ | |
| 36 | + |
| 37 | +### Hash |
| 38 | + |
| 39 | +```xquery |
| 40 | +import module namespace crypto = "http://expath.org/ns/crypto"; |
| 41 | +
|
| 42 | +crypto:hash("data", "SHA-256") (: Base64 output :) |
| 43 | +crypto:hash("data", "SHA-256", "hex") (: Hex output :) |
| 44 | +crypto:hash(<node>data</node>, "SHA-256") (: Node input :) |
| 45 | +``` |
| 46 | + |
| 47 | +Algorithms: MD5, SHA-1, SHA-256, SHA-384, SHA-512. For new code, prefer `fn:hash()` (XQuery 4.0) or `util:hash()`. |
| 48 | + |
| 49 | +### HMAC |
| 50 | + |
| 51 | +```xquery |
| 52 | +crypto:hmac("data", "secret-key", "SHA256") (: Base64 :) |
| 53 | +crypto:hmac("data", "secret-key", "SHA256", "hex") (: Hex :) |
| 54 | +crypto:hmac(xs:base64Binary("..."), "key", "SHA256") (: Binary input :) |
| 55 | +``` |
| 56 | + |
| 57 | +Algorithms: MD5, SHA1, SHA256, SHA384, SHA512. Names accept hyphenated (`SHA-256`), bare (`SHA256`), prefixed (`HMAC-SHA-256`), and lowercase (`sha256`) forms for BaseX compatibility. |
| 58 | + |
| 59 | +### Symmetric Encryption |
| 60 | + |
| 61 | +```xquery |
| 62 | +(: 4-param: auto-generated IV, prepended to ciphertext :) |
| 63 | +let $enc := crypto:encrypt("secret", "symmetric", $key, "AES") |
| 64 | +return crypto:decrypt($enc, "symmetric", $key, "AES") |
| 65 | +
|
| 66 | +(: 6-param: explicit IV and provider (v6 backward compat) :) |
| 67 | +let $enc := crypto:encrypt("secret", "symmetric", $key, "AES", $iv, $provider) |
| 68 | +return crypto:decrypt($enc, "symmetric", $key, "AES", $iv, $provider) |
| 69 | +``` |
| 70 | + |
| 71 | +Algorithms: AES (16/24/32-byte key), DES (8-byte key). |
| 72 | + |
| 73 | +### XML Digital Signatures |
| 74 | + |
| 75 | +```xquery |
| 76 | +(: 6-param: generated key pair :) |
| 77 | +let $signed := crypto:generate-signature( |
| 78 | + $doc, "exclusive", "SHA256", "RSA_SHA256", "dsig", "enveloped") |
| 79 | +return crypto:validate-signature($signed) |
| 80 | +
|
| 81 | +(: 7-param: + XPath expression for selective signing :) |
| 82 | +crypto:generate-signature($doc, "exclusive", "SHA256", "RSA_SHA256", |
| 83 | + "dsig", "enveloped", $xpath) |
| 84 | +
|
| 85 | +(: 8-param: + digital certificate from keystore :) |
| 86 | +crypto:generate-signature($doc, "exclusive", "SHA256", "RSA_SHA256", |
| 87 | + "dsig", "enveloped", $xpath, $certificate) |
| 88 | +
|
| 89 | +(: 3-param: simplified signing with PKCS#8 private key :) |
| 90 | +crypto:generate-signature($doc, $base64-private-key, "RSA_SHA256") |
16 | 91 | ``` |
17 | 92 |
|
18 | | -This will create a "expath-crypto-module-<version>.xar" file in the target folder. The .xar file can be uploaded to any eXist-db version > 5.3.0 via the Dashboard. |
19 | | - |
20 | | -### Currently implemented functions |
| 93 | +## Upgrading from expath-crypto-module 6.x |
| 94 | + |
| 95 | +### No changes needed |
| 96 | + |
| 97 | +The module namespace is identical — **import statements require no changes**: |
| 98 | + |
| 99 | +```xquery |
| 100 | +import module namespace crypto = "http://expath.org/ns/crypto"; |
| 101 | +``` |
| 102 | + |
| 103 | +All function names, arities, and algorithm names are backward compatible. Code using `crypto:hash`, `crypto:hmac`, `crypto:generate-signature` (6/7/8-param), `crypto:validate-signature`, and `crypto:encrypt`/`crypto:decrypt` (4-param and 6-param) will work without modification. |
| 104 | + |
| 105 | +### Breaking changes |
21 | 106 |
|
22 | | -* crypto:hash() |
23 | | -* crypto:hmac() (only for xs:string data for now) |
24 | | -* crypto:encrypt() (only for xs:string data and symmetric encryption for now) |
25 | | -* crypto:decrypt() (only for xs:string data and symmetric decryption for now) |
26 | | -* crypto:generate-signature() (only for XML data for now) |
27 | | -* crypto:validate-signature() (only for XML data for now) |
| 107 | +There are two breaking changes for users upgrading from v6: |
28 | 108 |
|
29 | | -### Currently implemented algorithms |
| 109 | +#### 1. `crypto:encrypt` / `crypto:decrypt` output format |
30 | 110 |
|
31 | | -* For crypto:hash(): "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512". |
32 | | -* For crypto:hmac(): "HMAC-MD5", "HMAC-SHA-1", "HMAC-SHA-256", "HMAC-SHA-384", "HMAC-SHA-512". |
| 111 | +| | v6 (expath-crypto-module) | v7 (exist-crypto) | |
| 112 | +|---|---|---| |
| 113 | +| **encrypt output** | Dash-separated bytes: `"51-143-171-200-..."` | `xs:base64Binary` | |
| 114 | +| **decrypt input** | Dash-separated bytes | `xs:base64Binary` | |
| 115 | + |
| 116 | +**Impact:** Data encrypted with v6 **cannot be decrypted with v7** (and vice versa). The wire format is different. |
| 117 | + |
| 118 | +**Migration:** If you have stored encrypted data from v6, decrypt it with v6 first, then re-encrypt with v7. For data encrypted in transit (e.g., JWT tokens), both sides must use the same version. |
| 119 | + |
| 120 | +#### 2. DSA_SHA1 signatures forbidden |
| 121 | + |
| 122 | +Java 21+ forbids DSA_SHA1 in secure signature validation. Use `RSA_SHA256` instead: |
| 123 | + |
| 124 | +```xquery |
| 125 | +(: v6 — worked but now forbidden by Java 21+ :) |
| 126 | +crypto:generate-signature($doc, "inclusive", "SHA1", "DSA_SHA1", "dsig", "enveloped") |
| 127 | +
|
| 128 | +(: v7 — use RSA_SHA256 instead :) |
| 129 | +crypto:generate-signature($doc, "inclusive", "SHA256", "RSA_SHA256", "dsig", "enveloped") |
| 130 | +``` |
33 | 131 |
|
34 | | -### Documentation |
| 132 | +### Improvements over v6 |
35 | 133 |
|
36 | | -For the latest version of the specification for this module see [http://expath.org/spec/crypto/editor](http://expath.org/spec/crypto/editor). |
| 134 | +- **Binary HMAC input**: `crypto:hmac` now accepts `xs:base64Binary` and `xs:hexBinary` for `$data` and `$key` |
| 135 | +- **Flexible algorithm names**: lowercase (`sha256`), bare (`SHA256`), hyphenated (`SHA-256`), and prefixed (`HMAC-SHA-256`) all work |
| 136 | +- **AES key sizes**: 128, 192, and 256-bit keys supported (v6 only supported 128) |
| 137 | +- **RSA_SHA256**: Added as a signature algorithm (v6 only had RSA_SHA1 and DSA_SHA1) |
| 138 | +- **Zero dependencies**: No external `crypto-java` library needed |
| 139 | +- **102 tests**: JUnit + XQSuite, including BaseX cross-compatibility vectors |
37 | 140 |
|
38 | | -The implementation follows this specification. |
| 141 | +## Conformance |
39 | 142 |
|
40 | | -### Unit Tests |
| 143 | +### EXPath Cryptographic Module 1.0 |
41 | 144 |
|
42 | | -Unit Tests use [XQSuite](https://exist-db.org/exist/apps/doc/xqsuite) and can be found in the [src/test/xquery/crypto folder](src/test/xquery/crypto/). |
| 145 | +Implements §2 (hash), §3 (hmac), §5 (encrypt), §6 (decrypt), §7 (generate-signature), §8 (validate-signature). Not implemented: §4 (key management), §9 (secure store) — these are marked "TBD" in the spec. |
43 | 146 |
|
44 | | -To run tests: |
| 147 | +### EXPath Cryptographic Module Editor's Draft |
| 148 | + |
| 149 | +The editor's draft consolidates function parameters into maps (`$parameters as map(xs:string, item())`). We support this via the 2-param map-based `generate-signature` arity alongside the positional arities for backward compatibility. |
| 150 | + |
| 151 | +### [EXPath Crypto 2018 CG Final Report](https://github.com/claudius108/expath-cg/blob/master/specs/crypto/crypto-new.html) |
| 152 | + |
| 153 | +This November 2018 revision by Claudius Teodorescu adds several features we implement: |
| 154 | +- **`crypto:list-providers`/`list-services`/`list-algorithms`** — provider introspection functions returning maps and arrays. Implemented but currently disabled pending an eXist-db binary search fix for zero-param arities in ordered module registries. |
| 155 | +- **Algorithm name aliases** — Java-style names `DSAwithSHA1`, `RSAwithSHA1` accepted alongside `DSA_SHA1`, `RSA_SHA1`. |
| 156 | +- **XML Canonicalization 1.1** — `inclusive-1.1` and `inclusive-with-comments-1.1` options. |
| 157 | +- **Map-based `generate-signature`** — 2-param `($data, $parameters as map(*))` arity. |
| 158 | + |
| 159 | +### BaseX Cryptographic Functions |
| 160 | + |
| 161 | +Function signatures match BaseX for the common arities: `crypto:hmac` (4-param), `crypto:encrypt` (4-param), `crypto:decrypt` (4-param), `crypto:generate-signature` (6+2 optional), `crypto:validate-signature` (1-param). Algorithm names accept BaseX's lowercase convention. |
| 162 | + |
| 163 | +## Build |
45 | 164 |
|
46 | 165 | ```bash |
47 | | -$ mvn verify |
48 | | -``` |
| 166 | +JAVA_HOME=/path/to/java-21 mvn clean package -DskipTests |
| 167 | +``` |
| 168 | + |
| 169 | +Run tests (102 total — requires exist-core in local Maven repo): |
| 170 | + |
| 171 | +```bash |
| 172 | +mvn test -Pintegration-tests |
| 173 | +``` |
| 174 | + |
| 175 | +## License |
| 176 | + |
| 177 | +[GNU Lesser General Public License v2.1](https://opensource.org/licenses/LGPL-2.1) |
0 commit comments