Skip to content

Commit 5c7e679

Browse files
authored
feat: Signer settings and CAWG signing (#17)
1 parent c34508e commit 5c7e679

File tree

8 files changed

+452
-5
lines changed

8 files changed

+452
-5
lines changed

library/src/androidTest/kotlin/org/contentauth/c2pa/AndroidSignerTests.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
This file is licensed to you under the Apache License, Version 2.0
33
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
44
(http://opensource.org/licenses/MIT), at your option.
@@ -103,4 +103,16 @@ class AndroidSignerTests : SignerTests() {
103103
val result = testStrongBoxAvailability()
104104
assertTrue(result.success, "StrongBox Availability test failed: ${result.message}")
105105
}
106+
107+
@Test
108+
fun runTestSignerFromSettingsToml() = runBlocking {
109+
val result = testSignerFromSettingsToml()
110+
assertTrue(result.success, "Signer From Settings (TOML) test failed: ${result.message}")
111+
}
112+
113+
@Test
114+
fun runTestSignerFromSettingsJson() = runBlocking {
115+
val result = testSignerFromSettingsJson()
116+
assertTrue(result.success, "Signer From Settings (JSON) test failed: ${result.message}")
117+
}
106118
}

library/src/androidTest/kotlin/org/contentauth/c2pa/ResourceTestHelper.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
This file is licensed to you under the Apache License, Version 2.0
33
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
44
(http://opensource.org/licenses/MIT), at your option.
@@ -22,6 +22,8 @@ object ResourceTestHelper {
2222
TestBase.loadSharedResourceAsBytes("$resourceName.jpg")
2323
?: TestBase.loadSharedResourceAsBytes("$resourceName.pem")
2424
?: TestBase.loadSharedResourceAsBytes("$resourceName.key")
25+
?: TestBase.loadSharedResourceAsBytes("$resourceName.toml")
26+
?: TestBase.loadSharedResourceAsBytes("$resourceName.json")
2527

2628
return sharedResource ?: throw IllegalArgumentException("Resource not found: $resourceName")
2729
}
@@ -31,6 +33,8 @@ object ResourceTestHelper {
3133
TestBase.loadSharedResourceAsString("$resourceName.jpg")
3234
?: TestBase.loadSharedResourceAsString("$resourceName.pem")
3335
?: TestBase.loadSharedResourceAsString("$resourceName.key")
36+
?: TestBase.loadSharedResourceAsString("$resourceName.toml")
37+
?: TestBase.loadSharedResourceAsString("$resourceName.json")
3438

3539
return sharedResource ?: throw IllegalArgumentException("Resource not found: $resourceName")
3640
}

library/src/main/jni/c2pa_jni.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,16 @@ JNIEXPORT jbyteArray JNICALL Java_org_contentauth_c2pa_Builder_signDataHashedEmb
10201020
}
10211021

10221022
// Signer native methods
1023+
JNIEXPORT jlong JNICALL Java_org_contentauth_c2pa_Signer_nativeFromSettings(JNIEnv *env, jclass clazz) {
1024+
struct C2paSigner *signer = c2pa_signer_from_settings();
1025+
1026+
if (signer == NULL) {
1027+
return 0;
1028+
}
1029+
1030+
return (jlong)(uintptr_t)signer;
1031+
}
1032+
10231033
JNIEXPORT jlong JNICALL Java_org_contentauth_c2pa_Signer_nativeFromInfo(JNIEnv *env, jclass clazz, jstring algorithm, jstring certificatePEM, jstring privateKeyPEM, jstring tsaURL) {
10241034
if (algorithm == NULL || certificatePEM == NULL || privateKeyPEM == NULL) {
10251035
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"),

library/src/main/kotlin/org/contentauth/c2pa/C2PA.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
This file is licensed to you under the Apache License, Version 2.0
33
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
44
(http://opensource.org/licenses/MIT), at your option.
@@ -40,6 +40,13 @@ object C2PA {
4040
@JvmStatic
4141
private external fun loadSettingsNative(settings: String, format: String): Int
4242

43+
/**
44+
* Load settings from a string.
45+
* Returns the result code from the native call (0 for success).
46+
*/
47+
@JvmStatic
48+
fun loadSettingsResult(settings: String, format: String): Int = loadSettingsNative(settings, format)
49+
4350
/**
4451
* Load settings from a string
4552
*/

library/src/main/kotlin/org/contentauth/c2pa/Signer.kt

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
This file is licensed to you under the Apache License, Version 2.0
33
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
44
(http://opensource.org/licenses/MIT), at your option.
@@ -54,6 +54,132 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
5454
if (handle == 0L) null else Signer(handle)
5555
}
5656

57+
/**
58+
* Creates a signer from JSON settings configuration.
59+
*
60+
* This method creates a signer from a JSON settings object that can include certificate
61+
* paths, private keys, algorithm selection, and other configuration options. This is useful
62+
* for loading signer configuration from external sources, configuration files, or for CAWG
63+
* (Creator Assertions Working Group) signers.
64+
*
65+
* @param settingsJson A JSON string containing signer configuration.
66+
* @return A new [Signer] instance configured according to the settings.
67+
* @throws C2PAError if the settings are invalid or the signer cannot be created.
68+
*
69+
* Example JSON for a CAWG signer:
70+
* ```json
71+
* {
72+
* "version": 1,
73+
* "signer": {
74+
* "local": {
75+
* "alg": "es256",
76+
* "sign_cert": "-----BEGIN CERTIFICATE-----\n...",
77+
* "private_key": "-----BEGIN PRIVATE KEY-----\n...",
78+
* "tsa_url": "http://timestamp.digicert.com"
79+
* }
80+
* },
81+
* "cawg_x509_signer": {
82+
* "local": {
83+
* "alg": "es256",
84+
* "sign_cert": "-----BEGIN CERTIFICATE-----\n...",
85+
* "private_key": "-----BEGIN PRIVATE KEY-----\n...",
86+
* "tsa_url": "http://timestamp.digicert.com",
87+
* "referenced_assertions": ["cawg.training-mining"]
88+
* }
89+
* }
90+
* }
91+
* ```
92+
*/
93+
@JvmStatic
94+
@Throws(C2PAError::class)
95+
fun fromSettingsJson(settingsJson: String): Signer =
96+
fromSettings(settingsJson, "json")
97+
98+
/**
99+
* Creates a signer from TOML settings configuration.
100+
*
101+
* This method creates a signer from a TOML settings string. TOML format supports additional
102+
* features like CAWG (Creator Assertions Working Group) X.509 signers that generate identity
103+
* assertions.
104+
*
105+
* @param settingsToml A TOML string containing signer configuration.
106+
* @return A new [Signer] instance configured according to the settings.
107+
* @throws C2PAError if the settings are invalid or the signer cannot be created.
108+
*
109+
* Example TOML for a CAWG signer:
110+
* ```toml
111+
* version = 1
112+
*
113+
* [signer.local]
114+
* alg = "es256"
115+
* sign_cert = """-----BEGIN CERTIFICATE-----
116+
* ...certificate chain...
117+
* -----END CERTIFICATE-----
118+
* """
119+
* private_key = """-----BEGIN PRIVATE KEY-----
120+
* ...private key...
121+
* -----END PRIVATE KEY-----
122+
* """
123+
* tsa_url = "http://timestamp.digicert.com"
124+
*
125+
* [cawg_x509_signer.local]
126+
* alg = "es256"
127+
* sign_cert = """-----BEGIN CERTIFICATE-----
128+
* ...certificate chain...
129+
* -----END CERTIFICATE-----
130+
* """
131+
* private_key = """-----BEGIN PRIVATE KEY-----
132+
* ...private key...
133+
* -----END PRIVATE KEY-----
134+
* """
135+
* tsa_url = "http://timestamp.digicert.com"
136+
* referenced_assertions = ["cawg.training-mining"]
137+
* ```
138+
*/
139+
@JvmStatic
140+
@Throws(C2PAError::class)
141+
fun fromSettingsToml(settingsToml: String): Signer =
142+
fromSettings(settingsToml, "toml")
143+
144+
/**
145+
* Creates a signer from settings configuration in the specified format.
146+
*
147+
* @param settings The settings string in the specified format.
148+
* @param format The format of the settings string ("json" or "toml").
149+
* @return A new [Signer] instance configured according to the settings.
150+
* @throws C2PAError if the settings are invalid or the signer cannot be created.
151+
*/
152+
@JvmStatic
153+
@Throws(C2PAError::class)
154+
private fun fromSettings(settings: String, format: String): Signer =
155+
executeC2PAOperation("Failed to create signer from settings") {
156+
val loadResult = C2PA.loadSettingsResult(settings, format)
157+
if (loadResult != 0) {
158+
throw C2PAError.Api(C2PA.getError() ?: "Failed to load settings")
159+
}
160+
val handle = nativeFromSettings()
161+
if (handle == 0L) null else Signer(handle)
162+
}
163+
164+
/**
165+
* Loads global C2PA settings without creating a signer.
166+
*
167+
* This method loads settings that will be used by subsequent signing operations. Use this
168+
* to load CAWG identity assertion settings separately from the main signer.
169+
*
170+
* @param settings The settings string in the specified format.
171+
* @param format The format of the settings string ("json" or "toml").
172+
* @throws C2PAError if the settings are invalid.
173+
*/
174+
@JvmStatic
175+
@Throws(C2PAError::class)
176+
fun loadSettings(settings: String, format: String) {
177+
val result = C2PA.loadSettingsResult(settings, format)
178+
if (result != 0) {
179+
throw C2PAError.Api(C2PA.getError() ?: "Failed to load settings")
180+
}
181+
}
182+
57183
/** Create signer with custom signing callback */
58184
@JvmStatic
59185
@Throws(C2PAError::class)
@@ -92,6 +218,9 @@ class Signer internal constructor(internal var ptr: Long) : Closeable {
92218
tsaURL: String?,
93219
callback: SignCallback,
94220
): Long
221+
222+
@JvmStatic
223+
private external fun nativeFromSettings(): Long
95224
}
96225

97226
/** Get the reserve size for this signer */

0 commit comments

Comments
 (0)