diff --git a/.github/workflows/publish-dry-run.yml b/.github/workflows/publish-dry-run.yml index 18e4b37b..cba56a5b 100644 --- a/.github/workflows/publish-dry-run.yml +++ b/.github/workflows/publish-dry-run.yml @@ -25,12 +25,19 @@ jobs: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.PUBLISH_SONATYPE_USER }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.PUBLISH_SONATYPE_PASSWORD }} deploy-docs: - needs: build - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v3 with: submodules: recursive + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install mkdocs-material - name: Build Dokka HTML - run: ./gradlew dokkaHtmlMultiModule + run: ./gradlew mkDocsBuild \ No newline at end of file diff --git a/.github/workflows/publish-pages-only.yml b/.github/workflows/publish-pages-only.yml index e4bf9728..dffce8b9 100644 --- a/.github/workflows/publish-pages-only.yml +++ b/.github/workflows/publish-pages-only.yml @@ -9,7 +9,7 @@ jobs: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -19,15 +19,19 @@ jobs: with: distribution: 'temurin' java-version: '17' + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install mkdocs-material - name: Build Dokka HTML - run: ./gradlew dokkaHtmlMultiModule + run: ./gradlew mkDocsBuild - name: Setup Pages uses: actions/configure-pages@v3 - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: # Upload docs folder - path: './build/dokka' + path: './docs/site' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b42d88ca..b12bfdf1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,11 +25,10 @@ jobs: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.PUBLISH_SONATYPE_USER }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.PUBLISH_SONATYPE_PASSWORD }} deploy-docs: - needs: build environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -39,15 +38,20 @@ jobs: with: distribution: 'temurin' java-version: '17' + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: pip install mkdocs-material - name: Build Dokka HTML - run: ./gradlew dokkaHtmlMultiModule + run: ./gradlew mkDocsBuild - name: Setup Pages uses: actions/configure-pages@v3 - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: # Upload docs folder - path: './build/dokka' + path: './docs/site' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 + diff --git a/.gitignore b/.gitignore index b049cf5a..fd97bf7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +docs/docs/CHANGELOG.md +docs/docs/dokka/ +docs/site/ + */generated .gradle demoapp/build diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ffb0cae..2f2accd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,11 +25,11 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. ### 3.8.0 (Supreme 0.3.0) Breaking Changes Ahead! * Completely revamped ASN.1 Tag Handling - * Properly handle multi-byte tags - * Introduce a new data structure `TLV.Tag` with an accompanying `TagClass` enum and a `constructed` flag to accurately represent arbitrary tags up to `ULong.MAX_VALUE` - * Make all `tag` parameters `ULong` to reflect support for multi-byte tags - * Remove `DERTags` - * Revamp implicit tagging (there is still work to be done, but at least it supports CONSTRUCTED ASN.1 elements) + * Properly handle multi-byte tags + * Introduce a new data structure `TLV.Tag` with an accompanying `TagClass` enum and a `constructed` flag to accurately represent arbitrary tags up to `ULong.MAX_VALUE` + * Make all `tag` parameters `ULong` to reflect support for multi-byte tags + * Remove `DERTags` + * Revamp implicit tagging (there is still work to be done, but at least it supports CONSTRUCTED ASN.1 elements) * Refactor `Int.Companion.decodeFromDer` -> `Int.Companion.decodeFromDerValue()` * Refactor `Long.Companion.decodeFromDer` -> `Long.Companion.decodeFromDerValue()` * Introduce `ULong.Companion.decodeFromDer` which can handle overlong inputs, as long as they start with a valid ULong encoding @@ -50,19 +50,19 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * `ByteArray.decodeAsn1VarUInt()` * Revamp implicit tagging * Revamp `Asn1Element.parse()`, introducing new variants. This yields: - * `Asn1Element.parse()` with the same semantics as before - * `Asn1Element.parse()` alternative introduced, which takes a `ByteIterator` instead of a `ByteArray` - * `Asn1Element.parseAll()` introduced, which consumes all bytes and returns a list of all ASN.1 elements (if parsing works) - * Variant 1 takes a `ByteIterator` - * Variant 2 takes a `ByteArray` - * `Asn1Element.parseFirst()` introduced, which tries to only parse a single ASN.1 element from the input and leaves the rest untouched. - * Variant 1 takes a `ByteIterator` and returns the element; the `ByteIterator` is advanced accordingly - * Variant 2 takes a `ByteArray` and returns a `Pair` of `(element, remainingBytes)` + * `Asn1Element.parse()` with the same semantics as before + * `Asn1Element.parse()` alternative introduced, which takes a `ByteIterator` instead of a `ByteArray` + * `Asn1Element.parseAll()` introduced, which consumes all bytes and returns a list of all ASN.1 elements (if parsing works) + * Variant 1 takes a `ByteIterator` + * Variant 2 takes a `ByteArray` + * `Asn1Element.parseFirst()` introduced, which tries to only parse a single ASN.1 element from the input and leaves the rest untouched. + * Variant 1 takes a `ByteIterator` and returns the element; the `ByteIterator` is advanced accordingly + * Variant 2 takes a `ByteArray` and returns a `Pair` of `(element, remainingBytes)` * More consistent low-level encoding and decoding function names: - * `encodeToAsn1Primitive` to produce an `Asn1Primitive` that can directly be DER-encoded - * `encodeToAsn1ContentBytes` to produce the content bytes of a TLV primitive (the _V_ in TLV) - * `decodeToXXX` to be invoked on an `Asn1Primitive` to decode a DER-encoded primitive into the target type - * `decodeFromAsn1ContentBytes` to be invoked on the companion of the target type to decode the content bytes of a TLV primitive (the _V_ in TLV) + * `encodeToAsn1Primitive` to produce an `Asn1Primitive` that can directly be DER-encoded + * `encodeToAsn1ContentBytes` to produce the content bytes of a TLV primitive (the _V_ in TLV) + * `decodeToXXX` to be invoked on an `Asn1Primitive` to decode a DER-encoded primitive into the target type + * `decodeFromAsn1ContentBytes` to be invoked on the companion of the target type to decode the content bytes of a TLV primitive (the _V_ in TLV) * Update conventions -> Coroutines 1.9.0 * replace `runCatching` with `catching` to be extra-safe @@ -71,27 +71,27 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * Implement supreme signing capabilities * Introduce Attestation Data Structure * Dependency Updates: - * Kotlin 2.0.20 - * kotlinx.serialization 1.7.2 stable (bye, bye unofficial snapshot dependency!) - * kotlinx-datetime 0.6.1 + * Kotlin 2.0.20 + * kotlinx.serialization 1.7.2 stable (bye, bye unofficial snapshot dependency!) + * kotlinx-datetime 0.6.1 ### 3.6.1 * Externalise `UVarInt` to multibase ### 3.6.0 * Rebranding to Signum - * maven coordinates: `at.asitplus.signum:$module` - * modules - * datatypes -> indispensable - * datatypes-jws -> indispensable-josef - * datatypes-cose -> indispensable-cosef - * provider -> supreme - * package renames - * `crypto` -> `signum` - * `datatypes` -> `indispensable` - * `jws` -> `josef` - * `cose` -> `cosef` - * `provider` -> `supreme` + * maven coordinates: `at.asitplus.signum:$module` + * modules + * datatypes -> indispensable + * datatypes-jws -> indispensable-josef + * datatypes-cose -> indispensable-cosef + * provider -> supreme + * package renames + * `crypto` -> `signum` + * `datatypes` -> `indispensable` + * `jws` -> `josef` + * `cose` -> `cosef` + * `provider` -> `supreme` ### 3.5.1 @@ -103,7 +103,7 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * Depend on newer conventions, which don't pull serialization snapshots in: * `datatypes`, `datatypes-jws`, and `provider` depend on stable serialization **WITHOUT COSE SUPPORT** - * `datatypes-cose` pulls in latest 1.8.0 serialization SNAPSHOT from upstream + * `datatypes-cose` pulls in latest 1.8.0 serialization SNAPSHOT from upstream * `ByteStringWrapper` is not part of upstream snapshot cose serialization anymore, but implemented as part of `datatypes-cose` in package `at.asitplus.crypto.datatypes.cose.io` @@ -111,9 +111,11 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. ### 3.5.0 **Fixes** + * Fix calculation of JWK thumbprints according to [RFC7638](https://www.rfc-editor.org/rfc/rfc7638.html) **Changes** + * Add `provider` module that actually implements cryptography! (Currently in preview, signature verification only) * Add `COSE_Key` header to `CoseHeader`, defined in OpenID for Verifiable Credential Issuance draft 13 * Fix serialization of COSE signature structures @@ -143,11 +145,13 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * KmmResult 1.6.2 **Fixes** + * Move `curve` from `CryptoAlgorithm` to `JwsAlgorithm` * Don't assume curve information for the X.509 signature when, in fact, none exists * `CryptoSignature`s in X.509 are now indefinite length **Changes** + * Always DID-encode keys in compressed form (but keep decoding support) * Rename `CryptoAlgorithm` to `X509SignatureAlgorithm` to better describe what it is * Rename `toCryptoAlgorithm` to `toX509SignatureAlgorithm` accordingly @@ -158,12 +162,14 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. ### 3.1.0 **Fixes** + * Standardize class names: `Ec` -> `EC` everywhere * Fix an edge case where very small `r`/`s` in `CryptoSignature.EC` would be corrupted * Remove bogus ASN.1 encoding from JWS Algorithms * `CryptoSignature.EC` now requires specification of a curve or size when reading raw bytes **Features** + * Support ASN.1 encoding/decoding for `BigInteger` * Expose `generator`, `order` and `cofactor` of `ECCurve` * Extend list of values in `JweAlgorithm` and `JweEncryption` @@ -186,6 +192,7 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. ### 3.0.0 **Fixes** + * Restructure and fix `RelativeDistinguishedName`. **THIS IS A BREAKING CHANGE** * Fix `Asn1Time` not truncating to seconds * Fix parsing of CryptoSignature when decoding Certificates @@ -193,6 +200,7 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. **Features** + * Wrap exceptions during deserialization in `KmmResult`, i.e. changing all `deserialize()` methods in companion objects **THIS IS A BREAKING CHANGE** * Move class `JweDecrypted` from package `at.asitplus.wallet.lib.jws` to `at.asitplus.crypto.datatypes.jws` **THIS IS A BREAKING CHANGE** * Support more JWE algorithms, e.g. AES @@ -207,40 +215,49 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * Expose `TbsCertificate.issuerAltNames` and `TbsCertificte.subjectAltnames`, which contain (somewhat) parsed `AlternativeNames` structures for easy access to `dnsName`. `iPAddress`, etc. - ---- - -## 1.0 - -### 1.0.0 - First public release ## 2.0 -### 2.0.0 - * JWS Support - * Bugfixes and streamlining all over the place - * Proper BIT STRING - * BitSet (100% Kotlin BitSet implementation) - * Recursively parsing (and encapsulating) ASN.1 structures in OCTET Strings - * Initial pretty-printing of ASN.1 Strucutres - * Massive ASN.1 builder DSL streamlining - * More convenient explicit tagging +### 2.6.0 +* Pull in `JsonWebKeySet` from `vclib` +* Implement JWK Set Url (`jku`) in JWS headers +* Implement Attestation JWT (`jwt`) in JWS headers +* Implement Confirmation keys (`cnf`) in JWT +* Implement `CborWebToken` (RFC 8392) +* Boolean ASN.1 decoding helper function +* Certificate to/from JCA certificate conversion functions +### 2.5.0 +* Parse more certificates from `x5c` in JWS headers +* Kotlin 1.9.23 thanks to updated conventions +* Generate `KnownOIDs` using [KotlinPoet](https://square.github.io/kotlinpoet/) +* Work around KT-65315 thanks to updated conventions +* BigNum as API dependency and iOS export (seems nonsensical, + but is somehow required when using this inside a compose multiplatform app) +* Rename `BERTags.NULL` to `BERTags.ASN1_NULL` to fix broken ObjC export -### 2.1.0 -* Kotlin 1.9.20 -* COSE Support -* Full RSA and HMAC Support -* New interface `Asn1OctetString` to unify both ASN.1 OCTET STREAM classes -* Fix broken `content` property of `Asn1EncapsulatingOctetString` -* Refactor `.derEncoded` property of `Asn1Encodable` interface to function `.encodeToDer()` -* Consistent exception handling behaviour - * Throw new type `Asn1Exception` for ASN.1-related errors - * Throw `IllegalArgumentException` for input-related errors - * Add `xxxOrNull()` functions for all encoding/decoding/parsing functions - * Add `xxxSafe()` functions to encapsulate encoding/decoding in `KmmResult` - * Return `KmmResult` for conversions between different key representations ( i.e. `CryptoPublicKey`, `CoseKey` and `JsonWebKey`) +### 2.4.0 +* Add Support for EC Point compression +* Add Support for full Cose-Key Spec +* Correct Multibase Encoding +* Change `DID:KEY` encoding to Base58_BTC to comply with draft +* Add Multibase Encoder/Decoder +* Add UVarInt datatype (63 bit max) +* Remove MultibaseHelper +* Finally make `CoseKey`'s EC Point compression play nicely with kotlinx.serialization +* Rename `CoseKey.fromKeyId` to `CoseKey.fromDid` +* Rename `JsonWebKey.fromKeyId` to `JsonWebKey.fromDid` + +### 2.3.0 +* Change `CryptoPublicKey.toJsonWebKey()` return type from `KmmResult` to `JsonWebKey` +* Add `CryptoSignature.parseFromJca` function +* Refactor `CryptoPublicKey.keyID` to `CryptoPublicKey.didEncoded` to better reflect what it actually is +* Rename `CryptoPublicKey.fromKeyId` to `CryptoPublicKey.fromDid` + +### 2.2.1 +* Update conventions + * Rename CBOR annotations + * Target Java 17 ### 2.2.0 * Dependency Updates @@ -250,48 +267,37 @@ the Tag class just cannot be directly accessed from Swift and ObjC any more. * Remove `SignatureValueLength` parameters from JWS & COSE Algorithm Enum class * Remove deprecated functions * Rename `Jws` classes - * New `CryptoAlgorithm` class - * New `CryptoSignature` class for easy Asn1 - RawByteArray conversion + * New `CryptoAlgorithm` class + * New `CryptoSignature` class for easy Asn1 - RawByteArray conversion * Rename function in file `JcaExtensions.kt` from `.toPublicKey` to `.toJcaPublicKey` to reflect connection to JVM * Remove VcLib-specific constants -#### 2.2.1 -* Update conventions - * Rename CBOR annotations - * Target Java 17 +### 2.1.0 +* Kotlin 1.9.20 +* COSE Support +* Full RSA and HMAC Support +* New interface `Asn1OctetString` to unify both ASN.1 OCTET STREAM classes +* Fix broken `content` property of `Asn1EncapsulatingOctetString` +* Refactor `.derEncoded` property of `Asn1Encodable` interface to function `.encodeToDer()` +* Consistent exception handling behaviour + * Throw new type `Asn1Exception` for ASN.1-related errors + * Throw `IllegalArgumentException` for input-related errors + * Add `xxxOrNull()` functions for all encoding/decoding/parsing functions + * Add `xxxSafe()` functions to encapsulate encoding/decoding in `KmmResult` + * Return `KmmResult` for conversions between different key representations ( i.e. `CryptoPublicKey`, `CoseKey` and `JsonWebKey`) -### 2.3.0 -* Change `CryptoPublicKey.toJsonWebKey()` return type from `KmmResult` to `JsonWebKey` -* Add `CryptoSignature.parseFromJca` function -* Refactor `CryptoPublicKey.keyID` to `CryptoPublicKey.didEncoded` to better reflect what it actually is -* Rename `CryptoPublicKey.fromKeyId` to `CryptoPublicKey.fromDid` -### 2.4.0 -* Add Support for EC Point compression -* Add Support for full Cose-Key Spec -* Correct Multibase Encoding -* Change `DID:KEY` encoding to Base58_BTC to comply with draft -* Add Multibase Encoder/Decoder -* Add UVarInt datatype (63 bit max) -* Remove MultibaseHelper -* Finally make `CoseKey`'s EC Point compression play nicely with kotlinx.serialization -* Rename `CoseKey.fromKeyId` to `CoseKey.fromDid` -* Rename `JsonWebKey.fromKeyId` to `JsonWebKey.fromDid` +### 2.0.0 +* JWS Support +* Bugfixes and streamlining all over the place +* Proper BIT STRING +* BitSet (100% Kotlin BitSet implementation) +* Recursively parsing (and encapsulating) ASN.1 structures in OCTET Strings +* Initial pretty-printing of ASN.1 Strucutres +* Massive ASN.1 builder DSL streamlining +* More convenient explicit tagging -### 2.5.0 -* Parse more certificates from `x5c` in JWS headers -* Kotlin 1.9.23 thanks to updated conventions -* Generate `KnownOIDs` using [KotlinPoet](https://square.github.io/kotlinpoet/) -* Work around KT-65315 thanks to updated conventions -* BigNum as API dependency and iOS export (seems nonsensical, - but is somehow required when using this inside a compose multiplatform app) -* Rename `BERTags.NULL` to `BERTags.ASN1_NULL` to fix broken ObjC export +## 1.0 -### 2.6.0 - * Pull in `JsonWebKeySet` from `vclib` - * Implement JWK Set Url (`jku`) in JWS headers - * Implement Attestation JWT (`jwt`) in JWS headers - * Implement Confirmation keys (`cnf`) in JWT - * Implement `CborWebToken` (RFC 8392) - * Boolean ASN.1 decoding helper function - * Certificate to/from JCA certificate conversion functions +### 1.0.0 +First public release diff --git a/README.md b/README.md index 2d56c3e8..d8f314ab 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
- - - Signum – Kotlin Multiplatform Crypto/PKI Library and ASN1 Parser + Encoder + + + Signum – Kotlin Multiplatform Crypto/PKI Library and ASN1 Parser + Encoder @@ -37,7 +37,7 @@ types and functionality related to crypto and PKI applications: * Certification Request (CSR) * ObjectIdentifier Class with human-readable notation (e.g. 1.2.9.6245.3.72.13.4.7.6) * Generic ASN.1 abstractions to operate on and create arbitrary ASN.1 Data -* JWS-related data structures (JSON Web Keys, JWT, etc…) +* JOSE-related data structures (JSON Web Keys, JWT, etc…) * COSE-related data structures (COSE Keys, CWT, etc…) * Serializability of all ASN.1 classes for debugging **AND ONLY FOR DEBUGGING!!!** *Seriously, do not try to deserialize ASN.1 classes through kotlinx.serialization! Use `decodeFromDer()` and its companions!* * 100% pure Kotlin BitSet @@ -48,26 +48,26 @@ This last bit means that **you can work with X509 Certificates, public keys, CSRs and arbitrary ASN.1 structures on iOS.** The very first bit means that you can verify signatures on the JVM, Android and on iOS. -**Do check out the full API docs [here](https://a-sit-plus.github.io/signum/)**! +### Do check out the full manual with examples and API docs [here](https://a-sit-plus.github.io/signum/)! +This README provides just an overview. +The full manual is more comprehensive, has separate sections for each module, and provides a full API documentation. -## Usage +## Using it in your Projects This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets the JVM, Android and iOS. +Tt. consists of four modules, each of which is published on maven central: -This library consists of four modules, each of which is published on maven central: +| Name | Info | +|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Indispensable | **Indispensable** base module containing the cryptographic data structures, algorithm identifiers, the ASN.1 parser, OIDs, X.509 certificate, … | +| Indispensable Josef | **Indispensable Josef** JOSE add-on module containing JWS/E/T-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | +| Indispensable Cosef | **Indispensable Cosef** COSE add-on module containing all COSE/CWT-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | +| Supreme | **Supreme** KMP crypto provider implementing hardware-backed signature creation and verification across mobile platforms (Android KeyStore / iOS Secure Enclave) and JCA compatibility (on the JVM). | -| Name | Info | -|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Indispensable | **Indispensable** base module containing the cryptographic data structures, algorithm identifiers, the ASN.1 parser, OIDs, X.509 certificate, … | -| Indispensable Josef | **Indispensable Josef** JOSE add-on module containing JWS/E/T-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | -| Indispensable Cosef | **Indispensable Cosef** COSE add-on module containing all COSE/CWT-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | -| Supreme | *(Preview)* **Supreme** KMP crypto provider implementing signature verification across platforms. Signature creation using platform-native functionality across JVM, Android (read: HW-Backed AndroidKeyStore) and iOS (read KeyChain + Secure Enclave) are WIP. | - -This separation keeps dependencies to a minimum, i.e. it enables including only JWT-related functionality, if COSE is irrelevant. - -### Using it in your Projects +This separation keeps dependencies to a minimum, i.e. it enables including only JOSE-related functionality, if COSE is irrelevant. +More importantly, in a JVM, iOS, or Android-only project, it allows for processing cryptographic material without imposing the inclusion of a crypto provider. Simply declare the desired dependency to get going: @@ -87,14 +87,9 @@ implementation("at.asitplus.signum:indispensable-cosef:$version") implementation("at.asitplus.signum:supreme:0.2.0") ``` -
- -_Relevant classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all -implement `Asn1Encodable` and their respective companions implement `Asn1Decodable`. -Which means that you can do things like parsing and examining certificates, creating CSRs, or transferring key -material._ - -
+## _Supreme_ Demo Reel +The _Supreme_ KMP crypto provider works differently from JCA. Configuration is type-safe, more expressive and you'll +end up less code. **Nothing throws! Do not discard the results returned from any operation!** ### Signature Creation @@ -132,7 +127,7 @@ PlatformSigningProvider.createSigningKey(alias = "Swordfish") { ec { // you don't even need to specify the curve (P256 is the default) but we'll do it for demonstration purposes curve = ECCurve.SECP_256_R_1 - // you could specify the supported digests explicity - if you do not, the curve's native digest (for P256, this is SHA256) is supported + // you could specify the supported digests explicitly - if you do not, the curve's native digest (for P256, this is SHA256) is supported } // see https://a-sit-plus.github.io/signum/supreme/at.asitplus.signum.supreme.sign/-platform-signing-key-configuration-base/-secure-hardware-configuration/index.html hardware { @@ -208,7 +203,7 @@ println("Certificate looks trustworthy: $isValid") #### Platform Verifiers -Not every platform supports every algorithm parameter. For example, iOS does not support raw ECDSA verification (of pre-hashed data). +Not every platform supports every algorithm parameter. For example, iOS does not support raw ECDSA verification (of pre-hashed data) for curve P-521. If you use `.verifierFor`, and this happens, the library will transparently substitute a pure-Kotlin implementation. If this is not desired, you can specifically enforce a platform verifier by using `.platformVerifierFor`. @@ -229,8 +224,16 @@ val isValid = verifier.verify(plaintext, signature).isSuccess println("Is it trustworthy? $isValid") ``` +## ASN.1 Demo Reel + +Classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all +implement `Asn1Encodable` and their respective companions implement `Asn1Decodable`. +Which means that you can do things like parsing and examining certificates, creating CSRs, or transferring key +material. + ### Certificate Parsing + ```kotlin val cert = X509Certificate.decodeFromDer(certBytes) diff --git a/build.gradle.kts b/build.gradle.kts index fa0544d4..ab2f9e57 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,22 +17,7 @@ apply(plugin = "org.jetbrains.dokka") tasks.getByName("dokkaHtmlMultiModule") { (this as DokkaMultiModuleTask) outputDirectory.set(File("${buildDir}/dokka")) - includes.from("README.md") moduleName.set("Signum") - doLast { - files( - "core-dark.png", - "core-light.png", - "cosef-dark.png", - "cosef-light.png", - "supreme-dark.png", - "supreme-light.png", - "josef-dark.png", - "josef-light.png", - "signum-light-large.png", - "signum-dark-large.png", - ).files.forEach { it.copyTo(File("build/dokka/${it.name}"), overwrite = true) } - } } allprojects { @@ -43,3 +28,21 @@ allprojects { mavenLocal() } } + +tasks.register("copyChangelog") { + into(rootDir.resolve("docs/docs")) + from("CHANGELOG.md") +} + +tasks.register("mkDocsPrepare") { + dependsOn("dokkaHtmlMultiModule") + dependsOn("copyChangelog") + into(rootDir.resolve("docs/docs/dokka")) + from("${buildDir}/dokka") +} + +tasks.register("mkDocsBuild") { + dependsOn(tasks.named("mkDocsPrepare")) + workingDir("${rootDir}/docs") + commandLine("mkdocs", "build", "--clean", "--strict") +} \ No newline at end of file diff --git a/docs/docs/assets/core-dark-large.png b/docs/docs/assets/core-dark-large.png new file mode 100644 index 00000000..e868bc4f Binary files /dev/null and b/docs/docs/assets/core-dark-large.png differ diff --git a/core-dark.png b/docs/docs/assets/core-dark.png similarity index 100% rename from core-dark.png rename to docs/docs/assets/core-dark.png diff --git a/docs/docs/assets/core-light-large.png b/docs/docs/assets/core-light-large.png new file mode 100644 index 00000000..39b7ba7f Binary files /dev/null and b/docs/docs/assets/core-light-large.png differ diff --git a/core-light.png b/docs/docs/assets/core-light.png similarity index 100% rename from core-light.png rename to docs/docs/assets/core-light.png diff --git a/docs/docs/assets/cosef-dark-large.png b/docs/docs/assets/cosef-dark-large.png new file mode 100644 index 00000000..7d62b180 Binary files /dev/null and b/docs/docs/assets/cosef-dark-large.png differ diff --git a/cosef-dark.png b/docs/docs/assets/cosef-dark.png similarity index 100% rename from cosef-dark.png rename to docs/docs/assets/cosef-dark.png diff --git a/docs/docs/assets/cosef-light-large.png b/docs/docs/assets/cosef-light-large.png new file mode 100644 index 00000000..d947841d Binary files /dev/null and b/docs/docs/assets/cosef-light-large.png differ diff --git a/cosef-light.png b/docs/docs/assets/cosef-light.png similarity index 100% rename from cosef-light.png rename to docs/docs/assets/cosef-light.png diff --git a/docs/docs/assets/josef-dark-large.png b/docs/docs/assets/josef-dark-large.png new file mode 100644 index 00000000..49db64e0 Binary files /dev/null and b/docs/docs/assets/josef-dark-large.png differ diff --git a/josef-dark.png b/docs/docs/assets/josef-dark.png similarity index 100% rename from josef-dark.png rename to docs/docs/assets/josef-dark.png diff --git a/docs/docs/assets/josef-light-large.png b/docs/docs/assets/josef-light-large.png new file mode 100644 index 00000000..0be6708c Binary files /dev/null and b/docs/docs/assets/josef-light-large.png differ diff --git a/josef-light.png b/docs/docs/assets/josef-light.png similarity index 100% rename from josef-light.png rename to docs/docs/assets/josef-light.png diff --git a/docs/docs/assets/schloss.svg b/docs/docs/assets/schloss.svg new file mode 100644 index 00000000..eeffa8bc --- /dev/null +++ b/docs/docs/assets/schloss.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/signum-dark-large.png b/docs/docs/assets/signum-dark-large.png similarity index 100% rename from signum-dark-large.png rename to docs/docs/assets/signum-dark-large.png diff --git a/signum-light-large.png b/docs/docs/assets/signum-light-large.png similarity index 100% rename from signum-light-large.png rename to docs/docs/assets/signum-light-large.png diff --git a/docs/docs/assets/supreme-dark-large.png b/docs/docs/assets/supreme-dark-large.png new file mode 100644 index 00000000..88070a31 Binary files /dev/null and b/docs/docs/assets/supreme-dark-large.png differ diff --git a/supreme-dark.png b/docs/docs/assets/supreme-dark.png similarity index 100% rename from supreme-dark.png rename to docs/docs/assets/supreme-dark.png diff --git a/docs/docs/assets/supreme-light-large.png b/docs/docs/assets/supreme-light-large.png new file mode 100644 index 00000000..73c74141 Binary files /dev/null and b/docs/docs/assets/supreme-light-large.png differ diff --git a/supreme-light.png b/docs/docs/assets/supreme-light.png similarity index 100% rename from supreme-light.png rename to docs/docs/assets/supreme-light.png diff --git a/docs/docs/features.md b/docs/docs/features.md new file mode 100644 index 00000000..028184ed --- /dev/null +++ b/docs/docs/features.md @@ -0,0 +1,87 @@ +--- +hide: + - navigation +--- + +# Signum Feature Matrix + +This page contains feature matrices, providing a detailed summary of what is and isn't supported. + +## Operations + +The following table provides an overview about the current status of supported and unsupported cryptographic functionality. +More details about the supported algorithms is provided in the next section. + +| Operation | JVM | Android | iOS | +|:----------------------------|:---------------------:|:-------:|:---------------:| +| ASN.1 Encoding + Decoding | ✔ | ✔ | ✔ | +| Signature Creation | ✔ | ✔ | ✔ | +| Signature Verification | ✔ | ✔ | ✔ | +| Digest Calculation | ✔ | ✔ | ✔ | +| Attestation | ❋ | ✔ | ✔* | +| Biometric Auth | ✗ | ✔ | ✔ | +| Hardware-Backed Key Storage | through dedicated HSM | ✔ | P-256 keys only | +| Key Agreement | ✗ | ✗ | ✗ | +| Asymmetric Encryption | ✗ | ✗ | ✗ | +| Symmetric Encryption | ✗ | ✗ | ✗ | +| MAC | ✗ | ✗ | ✗ | + +Hardware-backed key agreement, asymmetric and symmetric encryption are WIP and will be supported in an upcoming release. +This is more than a mere lip service, since we (A-SIT Plus GmbH) need this functionality urgently ourselves and are already working on it. + +### ❋ JVM Attestation +The JVM supports a custom attestation format, which can convey attestation +information inside an X.509 certificate. +By default, no semantics are attached to it. It can, therefore be used in any way desired, although this is +highly context-specific. +For example, if a hardware security module is plugged into the JVM crypto provider (e.g. using PKCS11) and this HSM +supports attestation, the JVM-specific attestation format can carry this information. WIP! +If you have suggestions, experience or a concrete use-case where you need this, check the footer and let us know! + +### ✔* iOS Attestation +iOS supports App attestation, but no direct key attestation. The Supreme crypto provider emulates key attestation +through app attestation, by _asserting_ the creation of a fresh public/private key pair inside the secure enclave +through application-layer logic encapsulated by the Supreme crypto provider. +Additional details are described in the [Attestation](supreme.md#attestation) section of the _Supreme_ manual. + +## Supported Algorithms + +The following matrix lists all supported algorithms and details. +Since everything is supported on all platforms equally, +a separate platform listing is omitted. + +| Primitive | Details | +|--------------------|--------------------------------------------------------------------------------------| +| Signature Creation | RSA/ECDSA with SHA2-family hash functions + raw signatures on pre-hashed data | +| RSA Key Sizes | 512 (useful for faster tests) up to 4096 (larger keys may not work on all platforms) | +| RSA Padding | PKCS1 and PSS (with sensible defaults) | +| Elliptic Curves | NIST Curves (P-256, P-384, P-521) | +| Digests | SHA-1 and SHA-2 family (SHA-256, SHA-384, SHA-512) | + +On the JVM and on Android, supporting more algorithms is rather easy, since Bouncy Castle works on both platforms +and can be used to provide more algorithms than natively supported. However, we aim for tight platform integration, +especially wrt. hardware-backed key storage and in-hardware computation of cryptographic operations. +We have therefore limited ourselves to what is natively supported on all platforms and most relevant in practice. + +## High-Level ASN.1 Abstractions + +The _Indispensable_ module comes with a fully-features ASN.1 engine including a builder DSL. +In addition to low-level, generic abstractions, it also provides higher-level datatypes with enriched +semantics: + +| Abstraction | Remarks | +|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| X.509 Certificate | Only supported algorithms can be parsed as certificate.
Certificates containing other algorithm can be parsed as generic ASN.1 structure. Parser is too lenient in some aspects. | +| X.509 Certificate Extension | Almost no predefined extensions. Need to be manually created. | +| Relative Distinguished Names | Rather barebones with little to no validation. | +| Alternative Names | Only basic structural validation. | +| PKCS10 CSR | Almost certainly a bit too lenient. | +| PKCS10 CSR Attributes | No predefined attributes. Need to be manually created. | +| X.509 Signature Algorithm | Only supported algorithms. | +| Public Keys | Only supported types. | +| ASN.1 Integer | Supports `Int`, `UInt`, `Long`, `ULong` and `BigInteger`. | +| ASN.1 Time | Maps from/to kotlinx-datetime `Instant`. Automatic choice of `GENERALIZED` and `UTC` time | +| ASN.1 String | All types supported, with little to no validation, however. | +| ASN.1 Object Identifier | Only `1` and `2` subtrees supported. `KnownOIDs` is generated from _dumpasn1_. | +| ASN.1 Octet String | Primitive octet strings and encapsulating complex structures natively supported for encoding and parsing. | +| ASN.1 Bit String | Relies on custom `BitSet` implementation, but also supports encoding raw bytes. | diff --git a/docs/docs/fonts/FantasqueSansMono-Bold.svg b/docs/docs/fonts/FantasqueSansMono-Bold.svg new file mode 100644 index 00000000..e05e4583 --- /dev/null +++ b/docs/docs/fonts/FantasqueSansMono-Bold.svg @@ -0,0 +1,2792 @@ + + + + + +Created by FontForge 20190526 at Sat Nov 16 19:56:45 2019 + By Jany +Created by Jany Belluz with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/fonts/FantasqueSansMono-Bold.woff b/docs/docs/fonts/FantasqueSansMono-Bold.woff new file mode 100644 index 00000000..9173a575 Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Bold.woff differ diff --git a/docs/docs/fonts/FantasqueSansMono-Bold.woff2 b/docs/docs/fonts/FantasqueSansMono-Bold.woff2 new file mode 100644 index 00000000..3159612f Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Bold.woff2 differ diff --git a/docs/docs/fonts/FantasqueSansMono-BoldItalic.svg b/docs/docs/fonts/FantasqueSansMono-BoldItalic.svg new file mode 100644 index 00000000..7da128e3 --- /dev/null +++ b/docs/docs/fonts/FantasqueSansMono-BoldItalic.svg @@ -0,0 +1,3125 @@ + + + + + +Created by FontForge 20190526 at Sat Nov 16 19:56:41 2019 + By Jany +Created by Jany Belluz with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff b/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff new file mode 100644 index 00000000..083e069a Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff differ diff --git a/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff2 b/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff2 new file mode 100644 index 00000000..3517ca81 Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-BoldItalic.woff2 differ diff --git a/docs/docs/fonts/FantasqueSansMono-Italic.svg b/docs/docs/fonts/FantasqueSansMono-Italic.svg new file mode 100644 index 00000000..156043c0 --- /dev/null +++ b/docs/docs/fonts/FantasqueSansMono-Italic.svg @@ -0,0 +1,2961 @@ + + + + + +Created by FontForge 20190526 at Sat Nov 16 19:56:38 2019 + By Jany +Created by Jany Belluz with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/fonts/FantasqueSansMono-Italic.woff b/docs/docs/fonts/FantasqueSansMono-Italic.woff new file mode 100644 index 00000000..9c2415d1 Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Italic.woff differ diff --git a/docs/docs/fonts/FantasqueSansMono-Italic.woff2 b/docs/docs/fonts/FantasqueSansMono-Italic.woff2 new file mode 100644 index 00000000..c113b432 Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Italic.woff2 differ diff --git a/docs/docs/fonts/FantasqueSansMono-Regular.svg b/docs/docs/fonts/FantasqueSansMono-Regular.svg new file mode 100644 index 00000000..be0b9672 --- /dev/null +++ b/docs/docs/fonts/FantasqueSansMono-Regular.svg @@ -0,0 +1,2804 @@ + + + + + +Created by FontForge 20190526 at Sat Nov 16 19:56:48 2019 + By Jany +Created by Jany Belluz with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/fonts/FantasqueSansMono-Regular.ttf b/docs/docs/fonts/FantasqueSansMono-Regular.ttf new file mode 100644 index 00000000..fa6be4d8 Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Regular.ttf differ diff --git a/docs/docs/fonts/FantasqueSansMono-Regular.woff b/docs/docs/fonts/FantasqueSansMono-Regular.woff new file mode 100644 index 00000000..5713fc2b Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Regular.woff differ diff --git a/docs/docs/fonts/FantasqueSansMono-Regular.woff2 b/docs/docs/fonts/FantasqueSansMono-Regular.woff2 new file mode 100644 index 00000000..07172c7e Binary files /dev/null and b/docs/docs/fonts/FantasqueSansMono-Regular.woff2 differ diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 00000000..a2c4f7fa --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,278 @@ +![Signum](assets/signum-dark-large.png#only-light) +![Signum](assets/signum-light-large.png#only-dark) + +[![Maven Central (indispensable)](https://img.shields.io/maven-central/v/at.asitplus.signum/indispensable?label=maven-central%20%28indispensable%29)](https://mvnrepository.com/artifact/at.asitplus.signum/) +[![Maven Central (Supreme)](https://img.shields.io/maven-central/v/at.asitplus.signum/supreme?label=maven-central%20%28Supreme%29)](https://mvnrepository.com/artifact/at.asitplus.signum/supreme) + +# Signum – Kotlin Multiplatform Crypto/PKI Library and ASN1 Parser + Encoder + +This [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library provides platform-independent data +types and functionality related to crypto and PKI applications: + +* **Multiplatform ECDSA and RSA Signer and Verifier** → Check out the included [CMP demo App](https://github.com/a-sit-plus/signum/tree/main/demoapp) to see it in + action + * **Supports Attestation on iOS and Android** + * **Biometric Authentication on Android and iOS without Callbacks or Activity Passing** (✨Magic!✨) +* Public Keys (RSA and EC) +* Algorithm Identifiers (Signatures, Hashing) +* X509 Certificate Class (create, encode, decode) +* Certification Request (CSR) +* ObjectIdentifier Class with human-readable notation (e.g. 1.2.9.6245.3.72.13.4.7.6) +* Generic ASN.1 abstractions to operate on and create arbitrary ASN.1 Data +* JOSE-related data structures (JSON Web Keys, JWT, etc…) +* COSE-related data structures (COSE Keys, CWT, etc…) +* Serializability of all ASN.1 classes for debugging **and only for debugging!!!** *Seriously, do not try to deserialize + ASN.1 classes through kotlinx.serialization! Use `decodeFromDer()` and its companions!* +* 100% pure Kotlin BitSet +* Exposes Multibase Encoder/Decoder as an API dependency + including [Matthew Nelson's smashing Base16, Base32, and Base64 encoders](https://github.com/05nelsonm/encoding) +* **ASN.1 Parser and Encoder including a DSL to generate ASN.1 structures** + +This last bit means that +**you can work with X509 Certificates, public keys, CSRs and arbitrary ASN.1 structures on iOS.** +The very first bit means that you can create and verify signatures on the JVM, Android and on iOS. + +**We also provide comprehensive API docs [here](dokka/index.html)**! + +## Using it in your Projects + +This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets +the JVM, Android and iOS. + +This library consists of four modules, each of which is published on maven central: + +| Name | Info | +|:-----------------------------------------------------------------------------------------------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ![indispensable](assets/core-dark.png#only-light) ![indispensable](assets/core-light.png#only-dark) | **Indispensable** base module containing the cryptographic data structures, algorithm identifiers, the ASN.1 parser, OIDs, X.509 certificate, … | +| ![indispensable-josef](assets/josef-dark.png#only-light) ![indispensable-josef](assets/josef-light.png#only-dark) | **Indispensable Josef** JOSE add-on module containing JWS/E/T-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | +| ![indispensable-cosef](assets/cosef-dark.png#only-light) ![indispensable-cosef](assets/cosef-light.png#only-dark) | **Indispensable Cosef** COSE add-on module containing all COSE/CWT-specific data structures and extensions to convert from/to types contained in the base module. Includes all required kotlinx-serialization magic to allow for spec-compliant de-/serialization. | +| ![Supreme](assets/supreme-dark.png#only-light) ![Supreme](assets/supreme-light.png#only-dark) | **Supreme** KMP crypto provider implementing hardware-backed signature creation and verification across mobile platforms (Android KeyStore / iOS Secure Enclave) and JCA compatibility (on the JVM). | + +This separation keeps dependencies to a minimum, i.e. it enables including only JOSE-related functionality, if COSE is irrelevant. +More importantly, in a JVM, iOS, or Android-only project, it allows for processing cryptographic material without imposing the inclusion of a crypto provider. + +Simply declare the desired dependency to get going: + +```kotlin +implementation("at.asitplus.signum:indispensable:$version") +``` + +```kotlin +implementation("at.asitplus.signum:indispensable-josef:$version") +``` + +```kotlin +implementation("at.asitplus.signum:indispensable-cosef:$version") +``` + +```kotlin +implementation("at.asitplus.signum:supreme:$supreme_version") +``` + +## Demo Reel + +This section provides a quick overview to show how this library works. +Since this is only a peek. more detailed information can be found in the corresponding sections dedicated to individual +features. + +### Signature Creation (Supreme) + +To create a signature, obtain a `Signer` instance. +You can do this using `Signer.Ephemeral` to create a signer for a throwaway keypair: + +```kotlin +val signer = Signer.Ephemeral {}.getOrThrow() +val plaintext = "You have this.".encodeToByteArray() +val signature = signer.sign(plaintext).signature +println("Signed using ${signer.signatureAlgorithm}: $signature") +``` + +If you want to create multiple signatures using the same ephemeral key, you can obtain an `EphemeralKey` instance, then +create signers from it: + +```kotlin +val key = EphemeralKey { rsa {} }.getOrThrow() +val sha256Signer = key.getSigner { rsa { digest = Digests.SHA256 } }.getOrThrow() +val sha384Signer = key.getSigner { rsa { digest = Digests.SHA384 } }.getOrThrow() +``` + +The instances can be configured using the configuration DSL. +Any unspecified parameters use sensible, secure defaults. + +### Signature Verification (Supreme) + +To verify a signature, obtain a `Verifier` instance using `verifierFor(k: PublicKey)`, either directly on a +`SignatureAlgorithm`, or on one of the specialized algorithms (`X509SignatureAlgorithm`, `CoseAlgorithm`, ...). +A variety of constants, resembling the well-known JCA names, are also available in `SignatureAlgorithm`'s companion. + +As an example, here's how to verify a basic signature using a public key: + +```kotlin +val publicKey: CryptoPublicKey.EC = TODO("You have this and trust it.") +val plaintext = "You want to trust this.".encodeToByteArray() +val signature: CryptoSignature = TODO("This was sent alongside the plaintext.") +val verifier = SignatureAlgorithm.ECDSAwithSHA256.verifierFor(publicKey).getOrThrow() +val isValid = verifier.verify(plaintext, signature).isSuccess +println("Looks good? $isValid") +``` + +### ASN.1 Parsing and Encoding + +Relevant classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all +implement `Asn1Encodable` and their respective companions implement `Asn1Decodable`. +Which means that you can do things like parsing and examining certificates, creating CSRs, or transferring key +material. +Parsing and re-encoding an X.509 certificate works as follows: + +```kotlin +val cert = X509Certificate.decodeFromDer(certBytes) + +when (val pk = cert.publicKey) { + is CryptoPublicKey.EC -> println( + "Certificate with serial no. ${ + cert.tbsCertificate.serialNumber + } contains an EC public key using curve ${pk.curve}" + ) + + is CryptoPublicKey.RSA -> println( + "Certificate with serial no. ${ + cert.tbsCertificate.serialNumber + } contains a ${pk.bits.number} bit RSA public key" + ) +} + +println("Re-encoding it produces the same bytes? ${cert.encodeToDer() contentEquals certBytes}") +``` + +Which produces the following output: + + Certificate with serial no. 19821EDCA68C59CF contains an EC public key using curve SECP_256_R_1 + Re-encoding it produces the same bytes? true + +### ASN.1 Builder DSL + +While predefined structures are essential for working with cryptographic material in a PKI context, +full control is sometimes required. +Signum directly support this with an ASN.1 builder DSL, including explicit and implicit tagging: + +```kotlin +Asn1.Sequence { + +ExplicitlyTagged(1uL) { + +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false) + } + +Asn1.Set { + +Asn1.Sequence { + +Asn1.SetOf { + +PrintableString("World") + +PrintableString("Hello") + } + +Asn1.Set { + +PrintableString("World") + +PrintableString("Hello") + +Utf8String("!!!") + } + + } + } + +Asn1.Null() + + +ObjectIdentifier("1.2.603.624.97") + + +(Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE)) + +PrintableString("Bar") + + // ↓ faux primitive ↓ + +(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED)) + + +Asn1.Set { + +Asn1.Int(3) + +Asn1.Int(-65789876543L) + +Asn1.Bool(false) + +Asn1.Bool(true) + } + +Asn1.Sequence { + +Asn1.Null() + +Asn1String.Numeric("12345") + +UtcTime(Clock.System.now()) + } +} withImplicitTag (1337uL withClass TagClass.APPLICATION) +``` + +This produces the following ASN.1 structure: + +``` +Application 1337 (9 elem) + + [1] (1 elem) + BOOLEAN false + SET (1 elem) + SEQUENCE (2 elem) + SET (2 elem) + PrintableString World + PrintableString Hello + SET (3 elem) + UTF8String !!! + PrintableString World + PrintableString Hello + NULL + OBJECT IDENTIFIER 1.2.603.624.97 + Private 51966 (3 byte) Foo + PrintableString Bar + [94] (3 byte) 02012A + SET (4 elem) + BOOLEAN false + BOOLEAN true + INTEGER 3 + INTEGER (36 bit) -65789876543 + SEQUENCE (3 elem) + NULL + NumericString 12345 + UTCTime 2024-09-16 11:53:51 UTC +``` + +### COSE and JOSE + +The modules _Indispensable Josef_ and _Indispensable Cosef_ provide data structures to work within JOSE and COSE +domains, respectively. +Since these are essentially data classes, there's really not much magic to using them. +The main reason those modules exist, is to keep the core _Indispensable_ module small, so it can be used without pulling +in unnecessary functionality. +COSE and JOSE data types come with mapping functionality to core (_Indispensable_) data types, +such as `CryptoPublicKey` and are guaranteed to parse and serialize correctly. + +#### COSE Parsing (Indidpensable Cosef) +As a quick self-contained example, deserializing the following `CoseSigned` structure works as expected: +```kotlin +val input = "d28443a10126a10442313154546869732069732074686520636f6e74656e" + + "742e58408eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a" + + "91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b09" + + "16e5a4c345cacb36" +val cose = CoseSigned.deserialize(input.uppercase().decodeToByteArray(Base16Strict)) + .also { println(it.getOrNull()) } +``` + +The output confirms that parsing was successful: + + CoseSigned(protectedHeader=CoseHeader(algorithm=ES256, criticalHeaders=null, contentType=null, kid=null, iv=null, partialIv=null, coseKey=null, certificateChain=null), unprotectedHeader=CoseHeader(algorithm=null, criticalHeaders=null, contentType=null, kid=3131, iv=null, partialIv=null, coseKey=null, certificateChain=null), payload=546869732069732074686520636F6E74656E742E, signature=8EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36) + +#### JWK creation (Indispensable Josef) +JsonWebKeys can be manually created (just as COSE keys) and converted to `CryptoPublicKey`, so we can pass it to a _Supreme_ verifier: + +```kotlin +val parsedN = ("0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2" + + "aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCi" + + "FV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65Y" + + "GjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n" + + "91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_x" + + "BniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw").decodeToByteArray(Base64UrlStrict) +val parsedE = "AQAB".decodeToByteArray(Base64UrlStrict) +val key = JsonWebKey(type = JwkType.RSA, n = parsedN, e = parsedE) + +key.jwkThumbprint //this is "urn:ietf:params:oauth:jwk-thumbprint:sha256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" +key.toCryptoPublicKey().getOrThrow() //<- this we can pass to a Supreme verifier +``` + +## Further Reading +Every module has dedicated documentation pages, and we provide full API docs. +Also checkout the feature matrix to get an overview of what is and isn't supported. + diff --git a/docs/docs/indispensable-cosef.md b/docs/docs/indispensable-cosef.md new file mode 100644 index 00000000..1e94f628 --- /dev/null +++ b/docs/docs/indispensable-cosef.md @@ -0,0 +1,29 @@ +![indispensable-cosef](assets/cosef-dark-large.png#only-light) ![indispensable-cosef](assets/cosef-light-large.png#only-dark) + +[![Maven Central](https://img.shields.io/maven-central/v/at.asitplus.signum/indispensable-cosef?label=maven-central)](https://mvnrepository.com/artifact/at.asitplus.signum.indispensable-cosef/) + +# Indispensable COSE Data Structures + +This [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library provides platform-independent COSE data +types and utility functions. It comes with predefined JOSE algorithm identifiers and guaranteed correct +serialization, leveraging kotlinx.serialization. +There's really not much more to it; it's data structures with self-describing names that interop with +_Indispensable_ data classes such as `SignatureAlgorithm`, `CryptoSignature`, and `CryptoPublicKey`. + +Classes like `CborWebToken` come with `serialize()` and `deserialize()` functions, that take care of everything. +The preconfigured serializer ensuring compliant serialization of all COSE-related data structures is called `coseCompliantSerializer`. + +!!! tip + **Do check out the full API docs [here](dokka/indispensable-cosef/index.html)** to get an overview about all COSE-specific data types included! + + +## Using it in your Projects + +This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets +the JVM, Android and iOS. + +Simply declare the desired dependency to get going: + +```kotlin +implementation("at.asitplus.signum:indispensable-cosef:$version") +``` diff --git a/docs/docs/indispensable-josef.md b/docs/docs/indispensable-josef.md new file mode 100644 index 00000000..e287cc69 --- /dev/null +++ b/docs/docs/indispensable-josef.md @@ -0,0 +1,28 @@ +![indispensable-josef](assets/josef-dark-large.png#only-light) ![indispensable-josef](assets/josef-light-large.png#only-dark) + +[![Maven Central](https://img.shields.io/maven-central/v/at.asitplus.signum/indispensable-josef?label=maven-central)](https://mvnrepository.com/artifact/at.asitplus.signum.indispensable-josef/) + +# Indispensable JOSE Data Structures + +This [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library provides platform-independent JOSE data +types and utility functions. It comes with predefined JOSE algorithm identifiers and guaranteed correct +serialization, leveraging kotlinx.serialization. +There's really not much more to it; it's data structures with self-describing names that interop with +_Indispensable_ data classes such as `SignatureAlgorithm`, `CryptoSignature`, and `CryptoPublicKey`. + +Classes like `JsonWebToken` come with `serialize()` and `deserialize()` functions, since their encoded representation is not a valid JSON string. +The preconfigured serializer ensuring compliant serialization of all JOSE-related data structures is called `joseCompliantSerializer`. + +!!! tip + **Do check out the full API docs [here](dokka/indispensable-josef/index.html)** to get an overview about all JOSE-specific data types included! + +## Using it in your Projects + +This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets +the JVM, Android and iOS. + +Simply declare the desired dependency to get going: + +```kotlin +implementation("at.asitplus.signum:indispensable-josef:$version") +``` diff --git a/docs/docs/indispensable.md b/docs/docs/indispensable.md new file mode 100644 index 00000000..6424c978 --- /dev/null +++ b/docs/docs/indispensable.md @@ -0,0 +1,430 @@ +![Indispensable](assets/core-dark-large.png#only-light) +![Signum](assets/core-light-large.png#only-dark) + +[![Maven Central](https://img.shields.io/maven-central/v/at.asitplus.signum/indispensable?label=maven-central)](https://mvnrepository.com/artifact/at.asitplus.signum.indispensable/) + +# Indispensable Core Data Structures and Functions for Cryptographic Material + +This [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library provides platform-independent data +types and functionality related to crypto and PKI applications: + +* EC Math + * EC Point Class + * EC Curve Class + * Mathematical operations + * Bit Length + * Point Compression +* Public Keys (RSA and EC) +* Algorithm Identifiers (Signatures, Hashing) +* X509 Certificate Class (create, encode, decode) + * Extensions + * Alternative Names + * Distinguished Names +* Certification Request (CSR) + * CSR Attributes +* ObjectIdentifier Class with human-readable notation (e.g. 1.2.9.6245.3.72.13.4.7.6) +* 100% pure Kotlin BitSet +* Exposes Multibase Encoder/Decoder as an API dependency + including [Matthew Nelson's smashing Base16, Base32, and Base64 encoders](https://github.com/05nelsonm/encoding) +* Generic ASN.1 abstractions to operate on and create arbitrary ASN.1 Data +* Serializability of all ASN.1 classes for debugging **AND ONLY FOR DEBUGGING!!!** *Seriously, do not try to deserialize + ASN.1 classes through kotlinx.serialization! Use `decodeFromDer()` and its cousins!* +* **ASN.1 Parser and Encoder including a DSL to generate ASN.1 structures** + +This last bit means that +you can work with X509 Certificates, public keys, CSRs and arbitrary ASN.1 structures on iOS. + +!!! tip + **Do check out the full API docs [here](dokka/indispensable/index.html)**! + +## Using it in your Projects + +This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets +the JVM, Android and iOS. + +Simply declare the desired dependency to get going: + +```kotlin +implementation("at.asitplus.signum:indispensable:$version") +``` + +## Structure and Class Overview +As the name _Indispensable_ implies, this is the base module for all KMP crypto operations. +It includes types, abstractions, and functionality considered absolutely essential to even entertain the thought +of working with and on cryptographic data. + +### Package Organisation + +#### Fundamental Cryptographic Data Structures +The main package housing all data classes is `at.asitplus.signum.indispensable`. +It contains essentials such as: + +* `CryptoPublicKey` representing a public key. Currently, we support RSA and EC public keys on NIST curves. +* `Digest` containing an enumeration of supported +* `ECCurve` representing an EC Curve +* `ECPoint` representing a point on an elliptic curve +* `CryptoSignatre` representing a cryptographic signature including descriptive information regarding the algorithms and signature data +* `SignatureAlgorithm` containing an enumeration of supported signature algorithms + * `X509SignatureAlgorithm` enumeration of supported X.509 signature algorithms (maps to and from `SignatureAlgorithm`) +* `Attestation` representing a container to convey attestation statements + * `AndroidKeystoreAttestation` contains the certificate chain from Google's root certificate down to the attested key + * `IosLegacyHomebrewAttesation` contains an attestation and an assertion, conforming to the emulated key attestation scheme +currently supported by warden. + * `IosHomebrewAttestation` contains the new iOS attestation format introduces in Supreme 0.2.0 (see the [Attestation](supreme.md#attestation) section of the _Supreme_ manual for details). + * `SelfAttestation` is used on the JVM. It has no specific semantics, but could be used, if an attestation-supporting HSM is used on the JVM. WIP! + +#### PKI-Related data Structures +The `pki` package contains data classes relevant in the PKI context: + +* `X509Certificate` does what you think it does + * `X509CertificateExtension` contains a convenience abstraction of X.509 certificate extensions + * `AlternativeNames` contains definitions of subject/issuer alternative names + * `RelativeDistinguishedName` contains definitions of RDNs (Common Name, City, …) +* `Pkcs10CertificateRequest` contains a CSR abstraction + * `Pcs10CertificateRequestAttributes` contains a CSR attribute extension + +#### ASN.1 + +The `asn1` package contains a 100% pure Kotlin (read: no platform dependencies) ASN.1 engine and data types: + +* `Asn1Elements.kt` contains all ANS.1 element types + * `Asn1Element` is an abstract, generic ASN.1 element. Has a tag and content. Can be DER-encoded + * `Asn1Element.Tag` representing an ASN.1 tag. Contains user-friendly representations of: + * Tag number + * `CONSTRUCTED` bit + * Tag Class + * A set of predefined tag constants that are often encountered such as `INTEGER`, `OCTET STRING`, `BIT STRING`, etc… + * `Asn1Primitive` is an ASN.1 element containing primitive data (string, byte strings, numbers, null, …) + * `Asn1Structure` is a `CONSTRUCTED` ASN.1 type, containing zero or more ASN.1 child elements + * `Asn1Sequence` has sequence semantics (order-preserving!) + * `Asn1SequenceOf` has sequence semantics but allows only child nodes of the same tag + * `Asn1Set` has set semantics, i.e. sorts all child nodes by tag in accordance with DER + * `Asn1SetOf` has set semantics but allows only child nodes of the same tag + +In addition, some convenience types are also present: + +* `Asn1ExplicitlyTagged`, which is essentially a sequence, but with a user-defined `CONTEXT_SPECIFIC` tag +* `Asn1BitString`, wich is an ASN.1 primitive containing bit strings, which are not necessarily byte-aligned. + Heavily relies on the included `BitSet` type to work its magic. +* `Asn1OctetString`, wich is often encountered in one of two flavours: + * `Asn1PrimitiveOctetString` containing raw bytes + * `Asn1EncapsulatingOctetString` containing any number of children. This is a structure, without the `CONSTRUCTED` bit set, using tag number `4`. +* `Asn1CustomStructure` representing a structure with a custom tag, that does not align with any predefined tag. + Can be constructed to auto-sort children to conform with DER set semantics. +* `ObjectIdentifier` represents an ASN.1 OID +* `Asn1String` contains different String types (printable, UTF-8, numeric, …) +* `Asn1Time` maps from/to kotlinx-datetime `Instant`s and supports both UTC time and generalized time + +The `asn1.encoding` package contains the ASN.1 builder DSL, as well as encoding and decoding functions +-- both for whole ASN.1 elements, as wells as for encoding/decoding primitive data types to/from DER-conforming byte arrays. +Most prominently, it comes with ASN.1 unsigned varint and minimum-length encoding of signed numbers. + +## Conversion from/to platform types + +Obviously, a world outside this library's data structures exists. +The following functions provide interop functionality with platform types. + +### JVM/Android + +* `SignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstance()` gets a pre-configured JCA instance for this algorithm +* `SignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm +* `SpecializedSignatureAlgorithm.getJCASignatureInstancePreHashed()` gets a pre-configured JCA instance for pre-hashed data for this algorithm + +* `Digest.jcaPSSParams` returns a sane default `PSSParameterSpec` for computing PSS signatures +* `Digest.jcaName` returns the JCA name of the digest +* `Digest?.jcaAlgorithmComponent` digest part of the digest part of the with JCA algorithm identifier (which differs fom the above) + +* `ECCurve.jcaName` returns the curve's name used by JCA +* `ECCurve.byJcaName()` returns the curve matching the provided JCA curve name + +* `CryptoPublicKey.getJcaPublicKey()` returns the JCA-representation of the public key +* `CryptoPublicKey.EC.getJcaPublicKey()` returns the JCA-representation of the public key (convenience helper) +* `CryptoPublicKey.RSA.getJcaPublicKey()` returns the JCA-representation of the public key (convenience helper) +* `CryptoPublicKey.fromJcaPublicKey` creates a `CryptoPublicKey` from a JCA Public Key +* `CryptoPublicKey.EC.fromJcaPublicKey` creates a `CryptoPublicKey.EC` from a JCA EC Public Key +* `CryptoPublicKey.RSA.fromJcaPublicKey` creates a `CryptoPublicKey.RSA` from a JCA RSA Public Key + +* `CryptoSignature.jcaSignatureBytes` returns the JCA-native encoded representation of a signature +* `CryptoSignature.parseFromJca()` returns a signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.EC.parseFromJca()` returns an EC signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.RSAorHMAC.parseFromJca()` returns an RSA signature object form a JCA-native encoded representation of a signature +* `CryptoSignature.EC.parseFromJcaP1363` parses a signature produced by the JCA digestWithECDSAinP1363Format algorithm. +* `X509Certificate.toJcaCertificate()` converts the certificate to a JCA-native `X509Certificate` +* `java.security.cert.X509Certificate.toKmpCertificate()` converts a JCA-native certificate to a Signum `X509Certificate` + +### iOS + +* `CryptoPublicKey.iosEncoded` encodes a public key as iOS does +* `CryptoPublicKey.fromiosEncoded()` decodes a public key that was encoded in iOS + +* `SignatureAlgorithm.secKeyAlgorithm` returns an algorithm identifier constant usable with CommonCrypto +* `SpecializedSignatureAlgorithm.secKeyAlgorithm` returns an algorithm identifier constant usable with CommonCrypto +* `SpecializedSignatureAlgorithm.secKeyAlgorithm` returns an algorithm identifier constant usable with CommonCrypto +* `SignatureAlgorithm.secKeyAlgorithmPreHashed` returns an algorithm identifier constant usable with CommonCrypto (for pre-hashed data) +* `SpecializedSignatureAlgorithm.secKeyAlgorithmPreHashed` returns an algorithm identifier constant usable with CommonCrypto (for pre-hashed data) + +* `CryptoSignature.iosEncoded` encodes a signature object as iOS would natively do + + +## ASN.1 Engine + +Relevant classes like `CryptoPublicKey`, `X509Certificate`, `Pkcs10CertificationRequest`, etc. all +implement `Asn1Encodable` and their respective companions implement `Asn1Decodable`. +This is an essential pattern, making the ASN.1 engine work the way it does. +We have opted against using kotlinx.serialization for maximum flexibility and more convenient debugging. +The following section provides more details on the various patterns used for ASN.1 encoding and decoding. + +### Generic Patterns +Recalling the classes in the `asn1` package described before already hints how ASN.1 elements are constructed. +In effect, it is just a nesting of those classes. +This works well for parsing and encoding but lacks higher-level semantics (in contrast to `X509CErtificate`, for example). + +As mentioned before, classes like `CryptoPublicKey`, `X509Certificate`, and `ObjectIdentifier` all implement `Asn1Encodable` +while their companions implement `Asn1Decodable`. +These interfaces essentially provide a mapping between custom types and low-level TLV structures that can directly be encoded, conforming to DER. +This also means, a direct serialization of such custom types is valuable for debugging, but not for encoding. +**Hence, decoding a kotlinx.serialization output of those classes is unsupported.** + + +### Decoding +Decoding functions come in two categories: high-level functions, wich are used to map ASN.1 elements to types with enriched semantics +(such as certificates, public keys, etc.) and low-level ones, operating on the encoded values of TLV structures (i.e. decoding the _V_ in TLV). + +#### High-Level + +`Asn1Decodable` provides the following functions for decoding data: + +* `doDecode()`, which is the only function that needs to be implemented by high-level types implementing `Asn1Encodable`. + To provide a concrete example: This function needs to contain all parsing/decoding logic to construct a `CryptoPublicKey` from an `Asn1Sequence`. +* `verifyTag()` already implements optional tag assertion. The default implementation of `decodeFromTlv()` (see below) calls this before invoking `doDecode()`. +* `decodeFromTlv()` takes an ASN.1 element and optional tag to assert, and returns a high-level type. Throws! +* `decodeFromTlvSafe()` does not throw, but returns a KmmResult, encapsulating the result of `decodeFromTlv()` +* `decodeFromTlvorNull()` does not throw, but returns null when decoding fails +* `decodeFromDer()` takes DER-encoded bytes, parses them into an ASN.1 element and calls `decodeFromTlv()`. Throws! +* `decodeFromDerSafe()` takes DER-encoded bytes. Does not throw, but returns a KmmResult, encapsulating the result of `decodeFromDer()` +* `decodeFromDerOrNull()` takes DER-encoded bytes. Does not throw, but returns null on decoding errors. + +In addition, the companion of `Asn1Element` exposes the following functions: + +* `parse()` parses a single ASN.1 element from the input and throws on error, or when additional input is left after parsing. + This is helpful, to ensure that any given input contains a single, top-level ASN.1 element. +* `parseAll()` consumes all input and returns a list of parsed ASN.1 elements. Throws on error. +* `parseFirst()` comes in two flavours, both of which parse only a single, top-level ASN.1 element from the passed input + * Variant 1 takes a `ByteIterator` and advances it until after the first parsed element. + * Variant 2 takes a `ByteArray` and returns the first parses alement, as well as the remaining bytes (as `Pair`) +* `decodeFromDerHexString()` strips all whitespace before trying to decode an ASN.1 element from the provided hex string. +This function throws various exceptions on illegal input. Has the same semantics as `parse()`. + +All of these return one or more `Asn1Element`s, which can then be passed to `decodeFromTlv()` if desired. +Low-level decoding functions deal with the actual decoding of payloads in TLV structures. + +#### Low-Level + +Some Low-level decoding functions are implemented as extension functions in `Asn1Primitive` for convenience (since CONSTRUCTED elements contain child nodes, but no raw data). +The base decoding function is called `decode()` and has the following signature: +```kotlin +fun Asn1Primitive.decode(assertTag: ULong, transform: (content: ByteArray) -> T): T +``` +An alternative exists, taking a `Tag` instead of an `Ulong`. in both cases a tag to assert and a user-defined transformation function is expected, which operates on +the content of the ASN.1 primitive. Moreover, npn-throwing `decodeOrNull` variant is present. +In addition, the following self-describing shorthands are defined: + +* `Asn1Primitive.decodeToBoolean()` throws on error +* `Asn1Primitive.decodeToBooleanOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToInt()` throws on error +* `Asn1Primitive.decodeToIntOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToLong()` throws on error +* `Asn1Primitive.decodeToLongOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToUInt()` throws on error +* `Asn1Primitive.decodeToUIntOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToULong()` throws on error +* `Asn1Primitive.decodeToULongOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToBigInteger()` throws on error +* `Asn1Primitive.decodeToBigIntegerOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToString()` throws on error +* `Asn1Primitive.decodeToStringOrNull()` returns `null` on error + +* `Asn1Primitive.decodeToInstant()` throws on error +* `Asn1Primitive.decodeToInstantOrNull()` returns `null` on error + +* `Asn1Primitive.readNull()` validates that the ASN.1 primitive is indeed an ASN.1 NULL. throws on error +* `Asn1Primitive.readNullOrNull()` validates that the ASN.1 primitive is indeed an ASN.1 NULL. returns `null` on error + +In addition, an `asAsn1String()` conversion function exists that checks an ANS.1 primitive's tag and returns the correct `Asn1String` subtype (UTF-8, NUMERIC, BMP, …). +Manually working on DER-encoded payloads is also supported through the following extensions (each taking a `ByteArray` as input): + +* `Int.decodeFromAsn1ContentBytes()` +* `UInt.decodeFromAsn1ContentBytes()` +* `Long.decodeFromAsn1ContentBytes()` +* `ULong.decodeFromAsn1ContentBytes()` +* `BigInteger.decodeFromAsn1ContentBytes()` +* `Boolean.decodeFromAsn1ContentBytes()` +* `String.decodeFromAsn1ContentBytes()` +* `Instant.decodeGeneralizedTimeFromAsn1ContentBytes()` +* `Instant.decodeUtcTimeFromAsn1ContentBytes()` + +All of these functions throw an `Asn1Exception` when decoding fails. + + +### Encoding +Similarly to decoding function, encoding function also come as high-level and low-level ones. +The general idea is the same: `Asn1Encodable` should be implemented by any custom type that needs encoding to ANS.1, +while low-level encoding functions create the raw bytes contained in an `Asn1Primtive`. + +#### High-Level +`Asn1Encodable` defines the following functions: + +* `encodeToTlv()` is the only function that need to be implemented. It defines how user-defined types are converted to an ASN.1 element. Throws on error. +* `encodeToTlvOrNull()` is a non-throwing variant of the above, returning `null` on error. +* `encodeToTlvOrSafe()` encapsulates the encoding result into a `KmmResult`. +* `encodeToDer()` invokes `encodeToTlv().derEncoded` to produce a `ByteArray` conforming to DER. Throws on error. +* `encodeToDerOrNull()` is a non-throwing variant of the above, returning `null` on error. +* `encodeToDerSafe()` encapsulates the encoding result into a `KmmResult`. + +`Asn1Element` and its subclasses come with the lazily-evaluated property `derEncoded` which produces a `ByteArray` conforming to DER. + +#### Low-Level +Low-level encoding functions come in two flavours: On the one hand, functions to produce correctly tagged ASN.1 primitives exist. +These essentially delegate to the other kind of low-level encoding function, producing the content bytes of an `Asn1Primitive`. +Both kind of encoding functions follow a simple naming convention: + +* `encodeToAsn1Primitive()` produces an ASN.1 primitive corresponding to the input. +This is implemented for `Int`, `UInt`, `Long`, `ULong`, `BigInteger`, `Boolean`, and `String` +* `encodeToAsn1ContentBytes()` producing the content bytes of an `Asn1Primitive`. +This is implemented for `Int`, `UInt`, `Long`, `ULong`, `BigInteger`, and `Boolean`. As for strings: An UTF-8 string is just its bytes. + +In addition, some more specialized encoding functions exist for cases that are not as straight-forward: + +* `ByteArray.encodeToAsn1OctetStringPrimitive()` produces an ASN.1 OCTET STRING containing the source bytes. +* `ByteArray.encodeToAsn1BitStringPrimitive()` produces an ASN.1 BIT STRING, prepending the source bytes with a single `0x00` byte. +* `ByteArray.encodeToAsn1BitStringContentBytes()` produces a `ByteArray` containing the source bytes, prepended with a single `0x00` byte. +* `Instant.encodeToAsn1UtcTimePrimitive()` produces an ASN.1 UTC TIME primitive +* `Instant.encodeToAsn1GeneralizedTimePrimitive()` produces an ASN.1 GENERALIZED TIME primitive + +### Custom Tagging + +This library comes with extensive tagging support and an expressive `Asn1Element.Tag` class. +ASN.1 knows EXPLICIT and IMPLICIT tags. +The former is simply a structure with SEQUENCE SEMANTICS and a user-defined CONSTRUCTED, CONTEXT_SPECIFIC tag, while the latter replaces an ASN.1 element's tag. + +#### Explicit +To explicitly tag any number of elements, simply invoke `Asn1.ExplicitlyTagged`, set the desired tag and add the desired elements (see ASN.1 Builder DSL) +To create an explicit tag (to compare it to a parsed, explicitly tagged element, for example), just pass tag number (and optionally) tag class to `Asn1.ExplicitTag`. + +#### Implicit + +Implicit tagging is implemented differently. Any element can be implicitly tagged, after it was constructed, by invoking the +`withImplicitTag` infix function on it. There's, of course, also an option to override the tag class. +Creating an implicitly tagged UTF-8 String using the ASN.1 builder DSL with a custom tag class works as follows: + +```kotlin +Asn1.Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE) +``` + +It is also possible to unset the CONSTRUCTED bit from any ASN.1 structure or Tag by invoking the infix function `without` as follows: +```kotlin +Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED) +``` + +!!! warning + It is perfectly possible to use abuse implicit tagging in ways that produces UNIVERSAL tags that are reserved for well-defined types. + If you really think you must create a faux ASN.1 NULL from an X.509 certificate go ahead, we dare you! + Just blame the mess you created only on yourself and nobody else! + + +### ASN.1 Builder DSL +So far, custom high-level types and manually constructing low-level types was discussed. +When actually constructing ASN.1 structures, a far more streamlined and intuitive approach exists. +Signum's Indispensable module comes with a powerful, expressive ASN.1 builder DSL, including shorthand functions +covering CONSTRUCTED types and primitives. +Everything is grouped under a namespace object called `Asn1`. It not only streamlines the creation of complex ASN.1 +structures, but also provides maximum flexibility. The following snippet showcases how it can be used in practice: + +```kotlin +Asn1.Sequence { + +ExplicitlyTagged(1uL) { + +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false) + } + +Asn1.Set { + +Asn1.Sequence { + +Asn1.SetOf { + +PrintableString("World") + +PrintableString("Hello") + } + +Asn1.Set { + +PrintableString("World") + +PrintableString("Hello") + +Utf8String("!!!") + } + + } + } + +Asn1.Null() + + +ObjectIdentifier("1.2.603.624.97") + + +(Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE)) + +PrintableString("Bar") + + // ↓ faux primitive ↓ + +(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED)) + + +Asn1.Set { + +Asn1.Int(3) + +Asn1.Int(-65789876543L) + +Asn1.Bool(false) + +Asn1.Bool(true) + } + +Asn1.Sequence { + +Asn1.Null() + +Asn1String.Numeric("12345") + +UtcTime(Clock.System.now()) + } +} withImplicitTag (1337uL withClass TagClass.APPLICATION) +``` + +This produces the following ASN.1 structure: + +``` +Application 1337 (9 elem) + + [1] (1 elem) + BOOLEAN false + SET (1 elem) + SEQUENCE (2 elem) + SET (2 elem) + PrintableString World + PrintableString Hello + SET (3 elem) + UTF8String !!! + PrintableString World + PrintableString Hello + NULL + OBJECT IDENTIFIER 1.2.603.624.97 + Private 51966 (3 byte) Foo + PrintableString Bar + [94] (3 byte) 02012A + SET (4 elem) + BOOLEAN false + BOOLEAN true + INTEGER 3 + INTEGER (36 bit) -65789876543 + SEQUENCE (3 elem) + NULL + NumericString 12345 + UTCTime 2024-09-16 11:53:51 UTC +``` + +You can, of course, also create primitives, by directly invoking builder functions, like `Asn1.Int()` and use the resulting +ASN.1 primitive as-is. +!!! tip + The builder also takes any `Asn1Encodable`, so you can also add an `X509Certificate`, or a `CryptoPublicKey` using + the same concise syntax. + **Do checkout the [API docs](dokka/indispensable/at.asitplus.signum.indispensable.asn1.encoding/-asn1/index.html) for a full list of builder functions!** \ No newline at end of file diff --git a/docs/docs/stylesheets/extra.css b/docs/docs/stylesheets/extra.css new file mode 100644 index 00000000..fb144e99 --- /dev/null +++ b/docs/docs/stylesheets/extra.css @@ -0,0 +1,74 @@ +[data-md-color-scheme="asp"] { + --md-primary-fg-color: #005b79; + --md-typeset-color: #1F2328; + --md-typeset-a-color: #009ed1; + --md-accent-fg-color: #005b79; + --md-default-bg-color: #fff; + --md-code-bg-color: #F6F8FA; + --md-default-fg-color--light: #1F2328; + --md-primary-fg-color--dark: #002A38; +} + +[data-md-color-scheme="asp"] img[src$="#only-dark"], +[data-md-color-scheme="asp"] img[src$="#gh-dark-mode-only"] { + display: none; /* Hide dark images in light mode */ +} + +[data-md-color-scheme="asp-dark"] { + --md-primary-fg-color: #005b79; + --md-typeset-color: #f0f6fc; + --md-typeset-a-color: #009dcf; + --md-accent-fg-color: #00c1ff; + --md-default-bg-color: #0D1117; + --md-default-fg-color: var(--md-typeset-color); + --md-default-fg-color--light: white; + --md-code-fg-color: #f0f6fc; + --md-code-bg-color: #1E242A; + --md-primary-fg-color--dark: #002A38; + --md-code-hl-variable-color: #fff; + --md-code-hl-operator-color: #f0f6fc; + --md-code-hl-string-color: rgb(47, 177, 112); + --md-code-hl-keyword-color: rgb(103, 145, 224); + --md-code-hl-number-color: rgb(230, 105, 91); + --md-code-hl-comment-color: rgba(226, 228, 233, 0.56); + --md-code-hl-punctuation-color: rgba(226, 228, 233, 0.56); + --md-code-hl-name-color: rgba(213, 216, 226, 0.82); + --md-typeset-table-color: rgba(213, 216, 226, 0.2); + --md-admonition-fg-color: var(--md-typeset-color); + --md-admonition-bg-color: var(--md-default-bg-color); +} + +[data-md-color-scheme="asp-dark"] img[src$="#only-light"], +[data-md-color-scheme="asp-dark"] img[src$="#gh-light-mode-only"] { + display: none; /* Hide light images in dark mode */ +} + + +@font-face { + font-family: 'Fantasque'; + src: url('../fonts/FantasqueSansMono-Regular.woff2') format('woff2'), + url('../fonts/FantasqueSansMono-Regular.woff') format('woff'), /* Firefox >= 3.6, any other modern browser */ + url('../fonts/FantasqueSansMono-Regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/FantasqueSansMono-Regular.svg#FantasqueSansMono-Regular') format('svg'); /* Chrome < 4, Legacy iOS */ + font-weight: 400; + font-style: normal; +} + +:root { + --md-code-font: "Fantasque"; +} + +.md-typeset { + font-size: .8rem; +} + +.md-typeset code { + font-size: .8rem; + font-feature-settings: "kern", "liga"; +} + +@media screen and (max-width: 76.2344em) { + .md-nav--primary .md-nav__title { + height: 3.9rem; + } +} \ No newline at end of file diff --git a/docs/docs/supreme.md b/docs/docs/supreme.md new file mode 100644 index 00000000..b3d65e0d --- /dev/null +++ b/docs/docs/supreme.md @@ -0,0 +1,317 @@ +![Signum Supreme](assets/supreme-dark-large.png#only-light) ![Signum Supreme](assets/supreme-light-large.png#only-dark) + +[![Maven Central](https://img.shields.io/maven-central/v/at.asitplus.signum/supreme?label=maven-central)](https://mvnrepository.com/artifact/at.asitplus.signum/supreme) + +# **Supreme** KMP Crypto Provider + +This [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) library provides platform-independent data +types and functionality related to crypto and PKI applications: + +* **Multiplatform ECDSA and RSA Signer and Verifier** → Check out the included [CMP demo App](https://github.com/a-sit-plus/signum/tree/main/demoapp) to see it in + action +* Supports Attestation on iOS and Android +* Biometric Authentication on Android and iOS without Callbacks or Activity Passing** (✨Magic!✨) +* Support Attestation on Android and iOS + +!!! tip + **Do check out the full API docs [here](dokka/supreme/index.html)**! + +## Using it in your Projects + +This library was built for [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). Currently, it targets +the JVM, Android and iOS. + +Simply declare the desired dependency to get going: + +```kotlin +implementation("at.asitplus.signum:supreme:$supreme_version") +``` + +## Key Design Principles +The Supreme KMP crypto provider works differently than JCA. It uses a `Provider` to manage private key material and create `Signer` instances, +and a `Verifier`, that is instantiated on a `SignatureAlgorithm`, taking a `CryptoPublicKey` as parameter. +In addition, creating ephemeral keys is a dedicated operation, decoupled from a `Provider`. +The actual implementation of cryptographic functionality is delegated to platform-native implementations. + +Moreover, the Supreme KMP crypto provider heavily relies on a type-safe DSL for configuration. +This type-safety goes so far as to expose platform-specific configuration options only in platform-specific sources, even when +the actual calls to some DSL-configurable type reads the same as in common code. + +!!! warning + **Do not ignore the results returned by any operation!** + We heavily rely on `KmmResult` to communicate the success or failure of operations. Nothing ever throws! + + +## Provider Initialization +Currently, we provide only one provider, the `SigningProvider`, which is used to manage signing keys and create signer +instances. Due to limitations of Kotlin, two discrete implementations of the provider exist: one for mobile targets, and +one for the JVM. Their initialization differs. + +### iOS and Android +On mobile targets (Android and iOS), simply reference the `PlatformSigningProvider` object, and you're good to go! +This provider is backed by the _AndroidKeyStore_ and the _KeyChain_/_Secure Enclave_ and requires no configuration. + +### JVM + +On the JVM, you need to instantiate the `JKSProvider` back it with a JCA `KeyStore`. +This can either be an already initialized, loaded one, or you can pass a path to a keystore file: + + + + + + + + + + + +
File-Basedwith pre-loaded KeyStore
+ +```kotlin +JKSProvider { + file { path = keystorePath } +} +``` + + + +```kotlin +JKSProvider { + withBackingObject { store = keyStore } +} +``` + +
+ +Usually, passing pre-initialized keystore is enough to cover even custom `KeyStore` implementations depending on +a specific `SecurityProvider`. +In cases where even more flexibility is needed, it is possible to use `withCustomAccessor{}` and pass a custom +KeyStore-accessor, implementing the `JKSAccessor` interface. + +In addition, the `JKSProvider` can be initialized without any backing keystore to create only ephemeral keys, if no +options are passed. + + +## Key Management +The provider enables creating, loading and deleting signing keys. + +### Key Generation +A key's properties cannot be modified after its creation. +Fundamental key-generation options, such as key type, are available on all targets and in common code. + +The common options include key type and specifics to the key type. +As EC and RSA keys are the only supported ones, this amounts to the following configuration options: + + + + + + + + + + + + +
ECRSA
+ +```kotlin +prov.createSigningKey(alias = "sig") { + ec { + curve = ECCurve.SECP_256_R_1 + digests = setOf(Digest.SHA256) + } +} +``` + + + +```kotlin +prov.createSigningKey(alias = "sig") { + rsa { + bits = 4096 + digests = setOf(Digest.SHA256) + paddings = setOf(RSAPAdding.PSS) + } +} +``` + +
+ +For EC keys, the digest is optional and if none is set, it defaults to the curve's native digest. +For RSA keys, the set of digests defaults to SHA-256 and the padding defaults to PSS. +It is also possible to override the public exponent, although not all platform respect this override. + +#### iOS and Android +Both iOS and Android support attestation, hardware-backed key storage and authentication to use a key. +Since all of this is, at least in part, hardware-dependent, the `PlatformSigningProvider` supports an additional +`hardware` configuration block for key generation. +The following snippet is a comprehensive example showcasing this feature set: + +```kotlin +val serverChallenge: ByteArray = TODO("This was unpredictably chosen by your server.") +PlatformSigningProvider.createSigningKey(alias = "Swordfish") { + ec { + // as supported by iOS and Android in hardware + curve = ECCurve.SECP_256_R_1 + } + hardware { + // you could use PREFERRED if you want the operation to succeed (without hardware backing) on devices that do not support it + backing = REQUIRED + attestation { challenge = serverChallenge } + protection { + timeout = 5.seconds + factors { + biometry = true + deviceLock = false + } + } + } +} +``` + +If multiple protections factors are chosen, any one of them can be used to unlock the key. +Biometry could be face unlock or fingerprint unlock, depending on the device and how it is configured. +If no timeout is specified, the key requires authentication on every use. + +In case an attestation challenge is specified, an attestation proof is generated alongside the key. +On iOS, this requires an Internet connection! See also [Attestation](#attestation). + +!!! warning + iOS only supports P-256 keys in hardware! + Yes, this means hardware-backed RSA keys are altogether unsupported on iOS! + + +#### JVM +The JVM supports no additional configuration options, since it supports none of the above features. + +### Key Loading +To load a key, simply call `provider.getSignerForKey(alias) {…}`. +Depending on how the key was created, it may be necessary or just useful to pass additional options. +Most prominently, you may want to display a custom unlock prompt on mobile targets, if the key +is protected by biometry: + +```kotlin +provider.getSignerForKey("Swordfish") { + unlockPrompt { + message = "Authenticate key usage" + subtitle = "We require your authentication to sign data" //Android-only + cancelText = "Cancel" + } +} +``` + +This configuration will be used for every sign operation as well. +More often than not, though, you'll want to setup an `unlockPrompt` as part of the signing operation +(see [Signature Creation](#signature-creation)). +On the JVM (using the `JKSProvider`), another toplevel configuration property is present: `privateKeyPassword`, +which is used to unlock the private key, in case it is password-protected + +In addition, EC and RSA-specific configuration options are available, to specify a digest and/or padding. +To configure such algorithm-specific options, invoke the `ec{}` or `rsa{}` block accordingly. + +### Key Deletion +Simply call `provider.deleteSigningKey(alias)` to delete a key. +If the operation succeeds, a key was indeed deleted. +If not, it usually means that a non-existent alias was specified. + +## Signature Creation +Regardless of whether a key was freshly created or a pre-existing key way loaded. The result of either operation +is a `Signer`, which can be used as desired. +To sign, simply pass data to sign. +On iOS and Android, it is possible to perform additional optional configuration, such as +setting up an `unlockPrompt`: + +```kotlin +signer.sign(data) { + unlockPrompt { + message = "Authenticate key usage" + subtitle = "We require your authentication to sign data" //Android-only + cancelText = "Cancel" + } +} +``` + +## Signature Verification + +To verify a signature, obtain a `Verifier` instance using `verifierFor(k: PublicKey)`, either directly on a +`SignatureAlgorithm`, or on one of the specialized algorithms (`X509SignatureAlgorithm`, `CoseAlgorithm`, ...). +A variety of constants, resembling the well-known JCA names, are also available in `SignatureAlgorithm`'s companion. + +As an example, here's how to verify a basic signature using a public key: + +```kotlin +val publicKey: CryptoPublicKey.EC = TODO("You have this and trust it.") +val plaintext = "You want to trust this.".encodeToByteArray() +val signature: CryptoSignature = TODO("This was sent alongside the plaintext.") +val verifier = SignatureAlgorithm.ECDSAwithSHA256.verifierFor(publicKey).getOrThrow() +val isValid = verifier.verify(plaintext, signature).isSuccess +println("Looks good? $isValid") +``` + +!!! tip + + Not every platform supports every algorithm parameter. For example, iOS does not support raw ECDSA verification (of pre-hashed data) for curve P-521. + If you use `.verifierFor`, and this happens, the library will transparently substitute a pure-Kotlin implementation. + If this is not desired, you can specifically enforce a platform verifier by using `.platformVerifierFor`. + That way, the library will only ever act as a proxy to platform APIs (JCA, CryptoKit, etc.), and will not use its own implementations. + +You can also further configure the verifier, for example to specify the `provider` to use on the JVM. +To do this, pass a DSL configuration lambda to `verifierFor`/`platformVerifierFor`. + +There really is not much more to it. This pattern works the same on all platforms. +Details on how to parse cryptographic material can be found in the [section on decoding](indispensable.md#decoding) in +of the Indispensable module description. + + +## Ephemeral Keys and Ephemeral Signers +Ephemeral keys and ephemeral signers are not backed by a provider, but are still delegated to platform functionality. +They are just not persisted and work the same across platforms. + +To obtain an ephemeral signer, call `Signer.Ephemeral{}` and pass EC or RSA-specific configuration options as you would when creating a key using +the `SigningProvider`. +Alternatively, you can create an ephemeral key using `EphemeralKey{}`(and again, pass algorithm-specific configuration options). +To obtain a signer from this ephemeral key, call `getSigner{}` on it. This, similarly to provider-backed keys, takes +algorithm-specific configuration options, such as a specific hash algorithm or padding, in case more than one was +specified when creating the ephemeral key. + +## Digest Calculation +The Supreme KMP crypto provider introduces a `digest()` extension function on the `Digest` class. +For a list of supported algorithms, check out the [feature matrix](features.md#supported-algorithms). + +## Attestation +!!! info + All attestation types are serializable for transfer and are part of the _Indispensable_ module, so they are usable + on JVM-only back-ends, that may not wish to include the _Supreme_ KM crypto provider. + [_WARDEN_](https://github.com/a-sit-plus/warden) does not yet directly support this format, but will in the next release. + As of now, the encoded certificate chain of the `AndroidKeytoreAttestation` and an array containing `attestation` + followed by `assertion` from the `IosLegacyHomebrewAttestation` are supported WARDEN. + +The Android KeyStore offers key attestation certificates for hardware-backed keys. +These certificates are exposed by the signer's `.attestation` property. + +For iOS, Apple does not provide this capability, but rather supports app attestation. +We therefore piggy-back onto iOS app attestation to provide a home-brew "key attestation" scheme. +The guarantees are different: you are trusting the OS, not the actual secure hardware; +and you are trusting that our library properly interfaces with the OS. +On a technical level, it works as follows: + +!!! note inline end + This section assumes in-depth knowledge of how an Apple attestation statement is created and validated, + as described in the [Apple Developer Documentation on AppAttest](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server). + +We make use of the fact that verification of `clientHashData` is purely up to the back-end. +Hence, we create an attestation key, immediately afterwards create a P-256 key inside the secure enclave, and compute +`clientHashData` over both the nonce obtained from the back-end **and** the public key bytes of the freshly created, SE-protected +EC key. +The iOS attestation type hence includes an attestation statement, the challenge, and the public key, so that the back-end +can easily verify the attestation result based on Apple's AppAttest service and the public key bytes, hence emulating +key attestation. Strictly speaking, this is a violation of the process described by Apple, but cryptographically, it is +perfectly sound! + +The JVM also "supports" a custom attestation format. By default, it is rather nonsensical. +However, if you plug an HSM that supports attestation to the JCA, you can make use of it. + +The [feature matrix](features.md) also contains remarks on attestation, while +details on the attestation format can be found in the corresponding [API documentation](dokka/indispensable/at.asitplus.signum.indispensable/-attestation/index.html). \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 00000000..794a6706 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,80 @@ +site_name: Signum +site_url: https://a-sit-plus.github.io/signum/ + +theme: + name: material + logo: assets/signum-light-large.png + favicon: assets/schloss.svg + custom_dir: overrides + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + icon: + repo: fontawesome/brands/github + + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + font: + text: Roboto + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: asp + toggle: + icon: material/brightness-7 + name: Switch to dark mode + font: + text: Roboto + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: asp-dark + toggle: + icon: material/brightness-4 + name: Switch to system preference + font: + text: Roboto + + +extra_css: + - stylesheets/extra.css + +repo_url: https://github.com/a-sit-plus/signum +repo_name: a-sit-plus/signum + +copyright: Copyright © 2024 A-SIT Plus GmbH + +extra: + social: + - icon: fontawesome/solid/earth-americas + link: https://a-sit-plus.github.io + - icon: fontawesome/solid/paper-plane + link: mailto: + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - admonition + - pymdownx.details + +nav: + - Manual: + - Overview: index.md + - CHANGELOG.md + - Modules: + - Indispensable: indispensable.md + - Indispensable Josef: indispensable-josef.md + - Indispensable Cosef: indispensable-cosef.md + - Signum Supreme: supreme.md + - Feature Matrix: features.md + - API Docs: dokka/index.html \ No newline at end of file diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..b8c6315a --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,99 @@ +{% extends "base.html" %} + +{% block header %} + +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} +{% set class = class ~ " md-header--shadow md-header--lifted" %} +{% elif "navigation.tabs" not in features %} +{% set class = class ~ " md-header--shadow" %} +{% endif %} + + +
+ + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
+{% endblock %} diff --git a/docs/overrides/partials/nav.html b/docs/overrides/partials/nav.html new file mode 100644 index 00000000..da9a1cdc --- /dev/null +++ b/docs/overrides/partials/nav.html @@ -0,0 +1,47 @@ + +{% import "partials/nav-item.html" as item with context %} + + +{% set class = "md-nav md-nav--primary" %} +{% if "navigation.tabs" in features %} +{% set class = class ~ " md-nav--lifted" %} +{% endif %} +{% if "toc.integrate" in features %} +{% set class = class ~ " md-nav--integrated" %} +{% endif %} + + + \ No newline at end of file