Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -9,6 +9,7 @@ ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import android.content.Context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import android.content.Context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -9,6 +9,7 @@ ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import android.content.Context
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -9,6 +9,7 @@ ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import android.content.Context
Expand Down
12 changes: 10 additions & 2 deletions library/src/main/kotlin/org/contentauth/c2pa/C2PAError.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -13,21 +13,29 @@ each license.
package org.contentauth.c2pa

/**
* Error model for C2PA operations
* Sealed error hierarchy for C2PA operations.
*
* All errors thrown by the C2PA library are subtypes of this class, allowing
* callers to handle them with exhaustive `when` expressions.
*/
sealed class C2PAError : Exception() {

/** An error returned by the native C2PA API with a descriptive message. */
data class Api(override val message: String) : C2PAError() {
override fun toString() = "C2PA-API error: $message"
}

/** A null pointer was unexpectedly returned from the native layer. */
object NilPointer : C2PAError() {
override fun toString() = "Unexpected NULL pointer"
}

/** A UTF-8 conversion error occurred when reading native string data. */
object Utf8 : C2PAError() {
override fun toString() = "Invalid UTF-8 from C2PA"
}

/** A negative status code was returned from a native operation. */
data class Negative(val value: Long) : C2PAError() {
override fun toString() = "C2PA negative status $value"
}
Expand Down
4 changes: 2 additions & 2 deletions library/src/main/kotlin/org/contentauth/c2pa/Helpers.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down Expand Up @@ -91,5 +91,5 @@ fun derToRawSignature(derSignature: ByteArray, componentLength: Int): ByteArray
return rPadded + sPadded
}

/** C2PA version fetched once */
/** The version string of the native C2PA library, lazily fetched once on first access. */
val c2paVersion: String by lazy { C2PA.version() }
22 changes: 15 additions & 7 deletions library/src/main/kotlin/org/contentauth/c2pa/KeyStoreSigner.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down Expand Up @@ -61,6 +61,7 @@ object KeyStoreSigner {
* @param tsaURL Optional timestamp authority URL
* @return A configured Signer instance
*/
@JvmStatic
fun createSigner(
algorithm: SigningAlgorithm,
certificateChainPEM: String,
Expand Down Expand Up @@ -100,6 +101,7 @@ object KeyStoreSigner {
* @param promptDescription Description for the biometric prompt
* @return A configured Signer instance after successful authentication
*/
@JvmStatic
suspend fun createBiometricSigner(
activity: FragmentActivity,
keyAlias: String,
Expand Down Expand Up @@ -162,6 +164,7 @@ object KeyStoreSigner {
*
* @return true if hardware-backed KeyStore is available
*/
@JvmStatic
fun isHardwareBackedKeystoreAvailable(): Boolean = try {
KeyStore.getInstance("AndroidKeyStore").load(null)
true
Expand All @@ -175,10 +178,13 @@ object KeyStoreSigner {
* @param keyAlias The alias of the key to check
* @return true if the key is hardware-backed
*/
fun isKeyHardwareBacked(keyAlias: String): Boolean {
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val privateKey = keyStore.getKey(keyAlias, null) as? PrivateKey ?: return false
@JvmStatic
fun isKeyHardwareBacked(keyAlias: String): Boolean = try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val privateKey = keyStore.getKey(keyAlias, null) as? PrivateKey
if (privateKey == null) {
false
} else {
val keyInfo =
KeyFactory.getInstance(privateKey.algorithm, "AndroidKeyStore")
.getKeySpec(privateKey, KeyInfo::class.java)
Expand All @@ -188,9 +194,9 @@ object KeyStoreSigner {
@Suppress("DEPRECATION")
keyInfo.isInsideSecureHardware
}
} catch (e: Exception) {
false
}
} catch (e: Exception) {
false
}

/**
Expand All @@ -199,6 +205,7 @@ object KeyStoreSigner {
* @param keyAlias The alias of the key to check
* @return true if the key exists
*/
@JvmStatic
fun keyExists(keyAlias: String): Boolean = try {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
Expand All @@ -213,6 +220,7 @@ object KeyStoreSigner {
* @param keyAlias The alias of the key to delete
* @return true if the key was deleted or didn't exist
*/
@JvmStatic
fun deleteKey(keyAlias: String): Boolean = try {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
Expand Down
40 changes: 37 additions & 3 deletions library/src/main/kotlin/org/contentauth/c2pa/Signer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
loadC2PALibraries()
}

/** Create signer from certificates and private key */
/**
* Creates a signer from PEM-encoded certificates and a private key.
*
* This is the simplest way to create a signer when you have the certificate chain
* and private key available as PEM strings.
*
* @param certsPEM The certificate chain in PEM format
* @param privateKeyPEM The private key in PEM format
* @param algorithm The [SigningAlgorithm] to use (e.g., ES256, ES384)
* @param tsaURL Optional timestamp authority URL for trusted timestamping
* @return A configured [Signer] instance
* @throws C2PAError.Api if the certificates or key are invalid
*/
@JvmStatic
@Throws(C2PAError::class)
fun fromKeys(
Expand All @@ -40,7 +52,13 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
return fromInfo(info)
}

/** Create signer from SignerInfo */
/**
* Creates a signer from a [SignerInfo] configuration object.
*
* @param info The [SignerInfo] containing algorithm, certificates, key, and TSA URL
* @return A configured [Signer] instance
* @throws C2PAError.Api if the signer cannot be created from the provided info
*/
@JvmStatic
@Throws(C2PAError::class)
fun fromInfo(info: SignerInfo): Signer = executeC2PAOperation("Failed to create signer") {
Expand Down Expand Up @@ -180,7 +198,23 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
}
}

/** Create signer with custom signing callback */
/**
* Creates a signer with a custom signing callback.
*
* Use this when the signing operation is handled externally, such as with
* hardware security modules (StrongBox, Android Keystore) or remote signing
* services. The callback receives raw bytes to sign and must return the signature.
*
* @param algorithm The [SigningAlgorithm] to use
* @param certificateChainPEM The certificate chain in PEM format
* @param tsaURL Optional timestamp authority URL for trusted timestamping
* @param sign Callback that receives data bytes and returns the signature bytes
* @return A configured [Signer] instance
* @throws C2PAError.Api if the callback signer cannot be created
*
* @see StrongBoxSigner
* @see KeyStoreSigner
*/
@JvmStatic
@Throws(C2PAError::class)
fun withCallback(
Expand Down
12 changes: 10 additions & 2 deletions library/src/main/kotlin/org/contentauth/c2pa/SignerInfo.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -13,7 +13,15 @@ each license.
package org.contentauth.c2pa

/**
* Signer information
* Configuration for creating a [Signer] from PEM-encoded credentials.
*
* @property algorithm The [SigningAlgorithm] to use (e.g., ES256, PS256)
* @property certificatePEM The certificate chain in PEM format
* @property privateKeyPEM The private key in PEM format
* @property tsaURL Optional timestamp authority URL for trusted timestamping
*
* @see Signer.fromInfo
* @see Signer.fromKeys
*/
data class SignerInfo(
val algorithm: SigningAlgorithm,
Expand Down
25 changes: 22 additions & 3 deletions library/src/main/kotlin/org/contentauth/c2pa/SigningAlgorithm.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,46 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.

Unless required by applicable law or agreed to in writing, this software is
distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Signing algorithms
* Supported signing algorithms for C2PA manifests.
*
* Each value corresponds to a COSE algorithm identifier used in the C2PA specification.
*/
@Serializable
enum class SigningAlgorithm {
@SerialName("es256")
ES256,

@SerialName("es384")
ES384,

@SerialName("es512")
ES512,

@SerialName("ps256")
PS256,

@SerialName("ps384")
PS384,

@SerialName("ps512")
PS512,

@SerialName("ed25519")
ED25519,
;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down Expand Up @@ -74,6 +74,7 @@ object StrongBoxSigner {
* @param tsaURL Optional timestamp authority URL
* @return A configured Signer instance
*/
@JvmStatic
fun createSigner(
algorithm: SigningAlgorithm,
certificateChainPEM: String,
Expand Down Expand Up @@ -105,6 +106,7 @@ object StrongBoxSigner {
* @param config Configuration for the key
* @return The created private key
*/
@JvmStatic
fun createKey(config: Config): PrivateKey =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore").run {
initialize(
Expand Down Expand Up @@ -135,6 +137,7 @@ object StrongBoxSigner {
* @param keyTag The alias of the key to delete
* @return true if the key was deleted or didn't exist
*/
@JvmStatic
fun deleteKey(keyTag: String): Boolean = try {
KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
Expand All @@ -151,6 +154,7 @@ object StrongBoxSigner {
* @param keyTag The alias of the key to check
* @return true if the key exists
*/
@JvmStatic
fun keyExists(keyTag: String): Boolean = try {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
Expand All @@ -165,6 +169,7 @@ object StrongBoxSigner {
* @param context Android context
* @return true if StrongBox is available
*/
@JvmStatic
fun isAvailable(context: Context): Boolean =
context.packageManager.hasSystemFeature("android.hardware.strongbox_keystore")

Expand All @@ -174,6 +179,7 @@ object StrongBoxSigner {
* @param keyTag The alias of the key to check
* @return true if the key is StrongBox-backed
*/
@JvmStatic
@RequiresApi(Build.VERSION_CODES.S)
fun isKeyStrongBoxBacked(keyTag: String): Boolean {
return try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand Down Expand Up @@ -54,9 +54,9 @@ class C2PAConfigurationController {
val configuration =
C2PAConfiguration(
algorithm = "es256",
timestamp_url = "http://timestamp.digicert.com",
signing_url = signingURL,
certificate_chain = encodedCertChain,
timestampUrl = "http://timestamp.digicert.com",
signingUrl = signingURL,
certificateChain = encodedCertChain,
)

call.respond(HttpStatusCode.OK, configuration)
Expand Down
Loading
Loading