Skip to content

Commit ac3577b

Browse files
authored
feat(CodeSigningPlugin): multiple public keys support (#1382)
1 parent 997816e commit ac3577b

17 files changed

Lines changed: 310 additions & 29 deletions

File tree

.changeset/gold-jokes-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@callstack/repack": major
3+
---
4+
5+
Add support for passing a per-script `publicKey` from `ScriptManager` resolvers so signed bundles can be verified with a runtime-provided public key instead of only the app-embedded `RepackPublicKey`

packages/repack/android/src/main/java/com/callstack/repack/CodeSigningUtils.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,18 @@ class CodeSigningUtils {
4545
private fun parsePublicKey(stringPublicKey: String): PublicKey? {
4646
val formattedPublicKey = stringPublicKey.replace("-----BEGIN PUBLIC KEY-----", "")
4747
.replace("-----END PUBLIC KEY-----", "")
48-
.replace(System.getProperty("line.separator")!!, "")
48+
.replace("\\s".toRegex(), "")
4949

50-
val byteKey: ByteArray = Base64.decode(formattedPublicKey.toByteArray(), Base64.DEFAULT)
51-
val x509Key = X509EncodedKeySpec(byteKey)
52-
val kf = KeyFactory.getInstance("RSA")
50+
if (formattedPublicKey.isBlank()) {
51+
return null
52+
}
5353

54-
return kf.generatePublic(x509Key)
54+
return runCatching {
55+
val byteKey: ByteArray = Base64.decode(formattedPublicKey.toByteArray(), Base64.DEFAULT)
56+
val x509Key = X509EncodedKeySpec(byteKey)
57+
val kf = KeyFactory.getInstance("RSA")
58+
kf.generatePublic(x509Key)
59+
}.getOrNull()
5560
}
5661

5762
private fun verifyAndDecodeToken(
@@ -83,15 +88,17 @@ class CodeSigningUtils {
8388
return null
8489
}
8590

86-
fun verifyBundle(context: Context, token: String?, fileContent: ByteArray?) {
91+
fun verifyBundle(
92+
context: Context, token: String?, fileContent: ByteArray?, stringPublicKey: String?
93+
) {
8794
if (token == null) {
8895
throw Exception("The bundle verification failed because no token for the bundle was found.")
8996
}
9097

91-
val stringPublicKey = getPublicKeyFromStringsIfExist(context)
98+
val resolvedPublicKey = stringPublicKey ?: getPublicKeyFromStringsIfExist(context)
9299
?: throw Exception("The bundle verification failed because PublicKey was not found in the bundle. Make sure you've added the PublicKey to the res/values/strings.xml under RepackPublicKey key.")
93100

94-
val publicKey = parsePublicKey(stringPublicKey)
101+
val publicKey = parsePublicKey(resolvedPublicKey)
95102
?: throw Exception("The bundle verification failed because the PublicKey is invalid.")
96103

97104
val claims: Map<String, Any?> = verifyAndDecodeToken(token, publicKey)

packages/repack/android/src/main/java/com/callstack/repack/FileSystemScriptLoader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class FileSystemScriptLoader(private val reactContext: ReactContext, private val
1212
}
1313

1414
if (config.verifyScriptSignature == "strict" || (config.verifyScriptSignature == "lax" && token != null)) {
15-
CodeSigningUtils.verifyBundle(reactContext, token, bundle)
15+
CodeSigningUtils.verifyBundle(reactContext, token, bundle, config.publicKey)
1616
}
1717

1818
return bundle

packages/repack/android/src/main/java/com/callstack/repack/RemoteScriptLoader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class RemoteScriptLoader(val reactContext: ReactContext, private val nativeLoade
5656
} ?: Pair(null, null)
5757

5858
if (config.verifyScriptSignature == "strict" || (config.verifyScriptSignature == "lax" && token != null)) {
59-
CodeSigningUtils.verifyBundle(reactContext, token, bundle)
59+
CodeSigningUtils.verifyBundle(reactContext, token, bundle, config.publicKey)
6060
}
6161

6262
if (bundle == null) {

packages/repack/android/src/main/java/com/callstack/repack/ScriptConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ data class ScriptConfig(
1919
val timeout: Int,
2020
val headers: Headers,
2121
val verifyScriptSignature: String,
22+
val publicKey: String?,
2223
val uniqueId: String,
2324
val sourceUrl: String
2425
) {
@@ -33,6 +34,7 @@ data class ScriptConfig(
3334
val headersMap = value.getMap("headers")
3435
val timeout = value.getInt("timeout")
3536
val verifyScriptSignature = requireNotNull(value.getString("verifyScriptSignature"))
37+
val publicKey = value.getString("publicKey")
3638
val uniqueId = requireNotNull(value.getString("uniqueId"))
3739

3840
val initialUrl = URL(urlString)
@@ -79,6 +81,7 @@ data class ScriptConfig(
7981
timeout,
8082
headers.build(),
8183
verifyScriptSignature,
84+
publicKey,
8285
uniqueId,
8386
sourceUrl
8487
)

packages/repack/ios/CodeSigningUtils.swift

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,33 @@ public class CodeSigningUtils: NSObject {
3838
}
3939

4040
let signatureB64 = convertBase64URLtoBase64(jwtSignature)
41-
let signature = Signature(data: Data(base64Encoded: signatureB64)!)
41+
guard let signatureData = Data(base64Encoded: signatureB64) else {
42+
throw CodeSigningError.tokenInvalid
43+
}
44+
let signature = Signature(data: signatureData)
4245

43-
guard let pk = try? PublicKey(pemEncoded: publicKey) else {
46+
guard let pk = try? PublicKey(
47+
pemEncoded: publicKey.trimmingCharacters(in: .whitespacesAndNewlines)
48+
) else {
4449
throw CodeSigningError.publicKeyInvalid
4550
}
4651

4752
// use b64-encoded header and payload for signature verification
4853
let tokenWithoutSignature = token.components(separatedBy: ".").dropLast().joined(separator: ".")
49-
let clearMessage = try? ClearMessage(string: tokenWithoutSignature, using: .utf8)
50-
51-
let isSuccesfullyVerified = try? clearMessage?.verify(with: pk, signature: signature, digestType: .sha256)
54+
guard let clearMessage = try? ClearMessage(string: tokenWithoutSignature, using: .utf8) else {
55+
throw CodeSigningError.tokenInvalid
56+
}
5257

53-
if isSuccesfullyVerified! {
54-
return jwt
55-
} else {
58+
do {
59+
let isSuccesfullyVerified = try clearMessage.verify(with: pk, signature: signature, digestType: .sha256)
60+
if isSuccesfullyVerified {
61+
return jwt
62+
} else {
63+
throw CodeSigningError.tokenVerificationFailed
64+
}
65+
} catch let error as CodeSigningError {
66+
throw error
67+
} catch {
5668
throw CodeSigningError.tokenVerificationFailed
5769
}
5870
}
@@ -80,12 +92,12 @@ public class CodeSigningUtils: NSObject {
8092
}
8193

8294
@objc
83-
public static func verifyBundle(token: String?, fileContent: NSData?) throws {
95+
public static func verifyBundle(token: String?, fileContent: NSData?, publicKey: String?) throws {
8496
guard let token = token else {
8597
throw CodeSigningError.tokenNotFound
8698
}
8799

88-
guard let publicKey = getPublicKey() else {
100+
guard let publicKey = publicKey ?? getPublicKey() else {
89101
throw CodeSigningError.publicKeyNotFound
90102
}
91103

packages/repack/ios/ScriptConfig.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN
1919
@property (nonatomic, readonly, nullable) NSDictionary *headers;
2020
@property (nonatomic, readonly) NSNumber *timeout;
2121
@property (nonatomic, readonly) NSString *verifyScriptSignature;
22+
@property (nonatomic, readonly, nullable) NSString *publicKey;
2223
@property (nonatomic, readonly) NSString *uniqueId;
2324
@property (nonatomic, readonly) NSString *sourceUrl;
2425

@@ -39,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
3940
withBody:(nullable NSData *)body
4041
withTimeout:(NSNumber *)timeout
4142
withVerifyScriptSignature:(NSString *)verifyScriptSignature
43+
withPublicKey:(nullable NSString *)publicKey
4244
withUniqueId:(NSString *)uniqueId
4345
withSourceUrl:(NSString *)sourceUrl;
4446

packages/repack/ios/ScriptConfig.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ @implementation ScriptConfig
1313
@synthesize headers = _headers;
1414
@synthesize timeout = _timeout;
1515
@synthesize verifyScriptSignature = _verifyScriptSignature;
16+
@synthesize publicKey = _publicKey;
1617
@synthesize uniqueId = _uniqueId;
1718
@synthesize sourceUrl = _sourceUrl;
1819

@@ -42,6 +43,7 @@ + (ScriptConfig *)fromConfig:(JS::NativeScriptManager::NormalizedScriptLocator &
4243
withBody:[config.body() dataUsingEncoding:NSUTF8StringEncoding]
4344
withTimeout:[NSNumber numberWithDouble:config.timeout()]
4445
withVerifyScriptSignature:config.verifyScriptSignature()
46+
withPublicKey:config.publicKey()
4547
withUniqueId:config.uniqueId()
4648
withSourceUrl:sourceUrl];
4749
}
@@ -67,6 +69,7 @@ + (ScriptConfig *)fromConfig:(NSDictionary *)config withScriptId:(nonnull NSStri
6769
withBody:[config[@"body"] dataUsingEncoding:NSUTF8StringEncoding]
6870
withTimeout:config[@"timeout"]
6971
withVerifyScriptSignature:config[@"verifyScriptSignature"]
72+
withPublicKey:config[@"publicKey"]
7073
withUniqueId:config[@"uniqueId"]
7174
withSourceUrl:sourceUrl];
7275
}
@@ -90,6 +93,7 @@ - (ScriptConfig *)initWithScript:(NSString *)scriptId
9093
withBody:(nullable NSData *)body
9194
withTimeout:(nonnull NSNumber *)timeout
9295
withVerifyScriptSignature:(NSString *)verifyScriptSignature
96+
withPublicKey:(nullable NSString *)publicKey
9397
withUniqueId:(NSString *)uniqueId
9498
withSourceUrl:(nonnull NSString *)sourceUrl
9599
{
@@ -103,6 +107,7 @@ - (ScriptConfig *)initWithScript:(NSString *)scriptId
103107
_headers = headers;
104108
_timeout = timeout;
105109
_verifyScriptSignature = verifyScriptSignature;
110+
_publicKey = publicKey;
106111
_uniqueId = uniqueId;
107112
_sourceUrl = sourceUrl;
108113
return self;

packages/repack/ios/ScriptManager.mm

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,10 @@ - (void)downloadAndCache:(ScriptConfig *)config completionHandler:(void (^)(NSEr
259259
if ([config.verifyScriptSignature isEqualToString:@"strict"] ||
260260
([config.verifyScriptSignature isEqualToString:@"lax"] && token != nil)) {
261261
NSError *codeSigningError = nil;
262-
[CodeSigningUtils verifyBundleWithToken:token fileContent:bundle error:&codeSigningError];
262+
[CodeSigningUtils verifyBundleWithToken:token
263+
fileContent:bundle
264+
publicKey:config.publicKey
265+
error:&codeSigningError];
263266
if (codeSigningError != nil) {
264267
callback(codeSigningError);
265268
return;
@@ -315,7 +318,10 @@ - (void)executeFromFilesystem:(ScriptConfig *)config
315318
if ([config.verifyScriptSignature isEqualToString:@"strict"] ||
316319
([config.verifyScriptSignature isEqualToString:@"lax"] && token != nil)) {
317320
NSError *codeSigningError = nil;
318-
[CodeSigningUtils verifyBundleWithToken:token fileContent:bundle error:&codeSigningError];
321+
[CodeSigningUtils verifyBundleWithToken:token
322+
fileContent:bundle
323+
publicKey:config.publicKey
324+
error:&codeSigningError];
319325
if (codeSigningError != nil) {
320326
reject(CodeExecutionFailure, codeSigningError.localizedDescription, nil);
321327
return;

packages/repack/src/modules/ScriptManager/NativeScriptManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface NormalizedScriptLocator {
2323
headers: { [key: string]: string } | undefined;
2424
body: string | undefined;
2525
verifyScriptSignature: NormalizedScriptLocatorSignatureVerificationMode;
26+
publicKey?: string;
2627
}
2728

2829
export interface Spec extends TurboModule {

0 commit comments

Comments
 (0)