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 Expand Up @@ -103,4 +103,16 @@ class AndroidSignerTests : SignerTests() {
val result = testStrongBoxAvailability()
assertTrue(result.success, "StrongBox Availability test failed: ${result.message}")
}

@Test
fun runTestSignerFromSettingsToml() = runBlocking {
val result = testSignerFromSettingsToml()
assertTrue(result.success, "Signer From Settings (TOML) test failed: ${result.message}")
}

@Test
fun runTestSignerFromSettingsJson() = runBlocking {
val result = testSignerFromSettingsJson()
assertTrue(result.success, "Signer From Settings (JSON) test failed: ${result.message}")
}
}
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 @@ -22,6 +22,8 @@ object ResourceTestHelper {
TestBase.loadSharedResourceAsBytes("$resourceName.jpg")
?: TestBase.loadSharedResourceAsBytes("$resourceName.pem")
?: TestBase.loadSharedResourceAsBytes("$resourceName.key")
?: TestBase.loadSharedResourceAsBytes("$resourceName.toml")
?: TestBase.loadSharedResourceAsBytes("$resourceName.json")

return sharedResource ?: throw IllegalArgumentException("Resource not found: $resourceName")
}
Expand All @@ -31,6 +33,8 @@ object ResourceTestHelper {
TestBase.loadSharedResourceAsString("$resourceName.jpg")
?: TestBase.loadSharedResourceAsString("$resourceName.pem")
?: TestBase.loadSharedResourceAsString("$resourceName.key")
?: TestBase.loadSharedResourceAsString("$resourceName.toml")
?: TestBase.loadSharedResourceAsString("$resourceName.json")

return sharedResource ?: throw IllegalArgumentException("Resource not found: $resourceName")
}
Expand Down
10 changes: 10 additions & 0 deletions library/src/main/jni/c2pa_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,16 @@ JNIEXPORT jbyteArray JNICALL Java_org_contentauth_c2pa_Builder_signDataHashedEmb
}

// Signer native methods
JNIEXPORT jlong JNICALL Java_org_contentauth_c2pa_Signer_nativeFromSettings(JNIEnv *env, jclass clazz) {
struct C2paSigner *signer = c2pa_signer_from_settings();

if (signer == NULL) {
return 0;
}

return (jlong)(uintptr_t)signer;
}

JNIEXPORT jlong JNICALL Java_org_contentauth_c2pa_Signer_nativeFromInfo(JNIEnv *env, jclass clazz, jstring algorithm, jstring certificatePEM, jstring privateKeyPEM, jstring tsaURL) {
if (algorithm == NULL || certificatePEM == NULL || privateKeyPEM == NULL) {
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"),
Expand Down
9 changes: 8 additions & 1 deletion library/src/main/kotlin/org/contentauth/c2pa/C2PA.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 @@ -40,6 +40,13 @@ object C2PA {
@JvmStatic
private external fun loadSettingsNative(settings: String, format: String): Int

/**
* Load settings from a string.
* Returns the result code from the native call (0 for success).
*/
@JvmStatic
fun loadSettingsResult(settings: String, format: String): Int = loadSettingsNative(settings, format)

/**
* Load settings from a string
*/
Expand Down
131 changes: 130 additions & 1 deletion library/src/main/kotlin/org/contentauth/c2pa/Signer.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 @@ -54,6 +54,132 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
if (handle == 0L) null else Signer(handle)
}

/**
* Creates a signer from JSON settings configuration.
*
* This method creates a signer from a JSON settings object that can include certificate
* paths, private keys, algorithm selection, and other configuration options. This is useful
* for loading signer configuration from external sources, configuration files, or for CAWG
* (Creator Assertions Working Group) signers.
*
* @param settingsJson A JSON string containing signer configuration.
* @return A new [Signer] instance configured according to the settings.
* @throws C2PAError if the settings are invalid or the signer cannot be created.
*
* Example JSON for a CAWG signer:
* ```json
* {
* "version": 1,
* "signer": {
* "local": {
* "alg": "es256",
* "sign_cert": "-----BEGIN CERTIFICATE-----\n...",
* "private_key": "-----BEGIN PRIVATE KEY-----\n...",
* "tsa_url": "http://timestamp.digicert.com"
* }
* },
* "cawg_x509_signer": {
* "local": {
* "alg": "es256",
* "sign_cert": "-----BEGIN CERTIFICATE-----\n...",
* "private_key": "-----BEGIN PRIVATE KEY-----\n...",
* "tsa_url": "http://timestamp.digicert.com",
* "referenced_assertions": ["cawg.training-mining"]
* }
* }
* }
* ```
*/
@JvmStatic
@Throws(C2PAError::class)
fun fromSettingsJson(settingsJson: String): Signer =
fromSettings(settingsJson, "json")

/**
* Creates a signer from TOML settings configuration.
*
* This method creates a signer from a TOML settings string. TOML format supports additional
* features like CAWG (Creator Assertions Working Group) X.509 signers that generate identity
* assertions.
*
* @param settingsToml A TOML string containing signer configuration.
* @return A new [Signer] instance configured according to the settings.
* @throws C2PAError if the settings are invalid or the signer cannot be created.
*
* Example TOML for a CAWG signer:
* ```toml
* version = 1
*
* [signer.local]
* alg = "es256"
* sign_cert = """-----BEGIN CERTIFICATE-----
* ...certificate chain...
* -----END CERTIFICATE-----
* """
* private_key = """-----BEGIN PRIVATE KEY-----
* ...private key...
* -----END PRIVATE KEY-----
* """
* tsa_url = "http://timestamp.digicert.com"
*
* [cawg_x509_signer.local]
* alg = "es256"
* sign_cert = """-----BEGIN CERTIFICATE-----
* ...certificate chain...
* -----END CERTIFICATE-----
* """
* private_key = """-----BEGIN PRIVATE KEY-----
* ...private key...
* -----END PRIVATE KEY-----
* """
* tsa_url = "http://timestamp.digicert.com"
* referenced_assertions = ["cawg.training-mining"]
* ```
*/
@JvmStatic
@Throws(C2PAError::class)
fun fromSettingsToml(settingsToml: String): Signer =
fromSettings(settingsToml, "toml")

/**
* Creates a signer from settings configuration in the specified format.
*
* @param settings The settings string in the specified format.
* @param format The format of the settings string ("json" or "toml").
* @return A new [Signer] instance configured according to the settings.
* @throws C2PAError if the settings are invalid or the signer cannot be created.
*/
@JvmStatic
@Throws(C2PAError::class)
private fun fromSettings(settings: String, format: String): Signer =
executeC2PAOperation("Failed to create signer from settings") {
val loadResult = C2PA.loadSettingsResult(settings, format)
if (loadResult != 0) {
throw C2PAError.Api(C2PA.getError() ?: "Failed to load settings")
}
val handle = nativeFromSettings()
if (handle == 0L) null else Signer(handle)
}

/**
* Loads global C2PA settings without creating a signer.
*
* This method loads settings that will be used by subsequent signing operations. Use this
* to load CAWG identity assertion settings separately from the main signer.
*
* @param settings The settings string in the specified format.
* @param format The format of the settings string ("json" or "toml").
* @throws C2PAError if the settings are invalid.
*/
@JvmStatic
@Throws(C2PAError::class)
fun loadSettings(settings: String, format: String) {
val result = C2PA.loadSettingsResult(settings, format)
if (result != 0) {
throw C2PAError.Api(C2PA.getError() ?: "Failed to load settings")
}
}

/** Create signer with custom signing callback */
@JvmStatic
@Throws(C2PAError::class)
Expand Down Expand Up @@ -92,6 +218,9 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
tsaURL: String?,
callback: SignCallback,
): Long

@JvmStatic
private external fun nativeFromSettings(): Long
}

/** Get the reserve size for this signer */
Expand Down
Loading