Skip to content

Instantly share code, notes, and snippets.

@0xilis
Created November 21, 2023 17:18
Show Gist options
  • Save 0xilis/be2ddaf88e512a676ef169136f79d660 to your computer and use it in GitHub Desktop.
Save 0xilis/be2ddaf88e512a676ef169136f79d660 to your computer and use it in GitHub Desktop.
iCloudSignShortcut.m
/*
* Snoolie K / 0xilis
* 21 November 2023 (EST)
* iCloud Sign Shortcuts Example
*/
#import <UIKit/UIKit.h>
@interface WFFileRepresentation : NSObject
@property (readonly, nonatomic) NSData *data;
+(id)fileWithData:(id)arg1 ofType:(id)arg2 proposedFilename:(id)arg3;
+(id)fileWithURL:(id)arg1 options:(long long)arg2;
+(id)fileWithURL:(id)arg1 options:(long long)arg2 ofType:(id)arg3;
+(id)proposedFilenameForFile:(id)arg1 ofType:(id)arg2;
+(id)fileWithURL:(id)arg1 options:(long long)arg2 ofType:(id)arg3 proposedFilename:(id)arg4;
@end
@interface WFWorkflowFileDescriptor : NSObject
@property (readonly, nonatomic) WFFileRepresentation *file;
-(id)initWithFile:(id)file name:(id)name;
@end
@interface WFWorkflowFile : NSObject
-(id)recordRepresentationWithError:(NSError*)err;
-(id)initWithDescriptor:(id)fileDesc error:(NSError*)err;
-(id)initWithFileData:(id)fileData name:(id)name error:(NSError*)err;
@end
@interface WFGallerySessionManager : NSObject
+ (id)sharedManager;
-(void)uploadWorkflow:(id)arg1 withName:(id)arg2 shortDescription:(id)arg3 longDescription:(id)arg4 private:(BOOL)arg5 completionHandler:(id)arg6;
@end
#define UNSIGNED_SHORTCUT_PATH "/Users/0xilis/Downloads/0/GitGub/WorkflowKit-Ent-Testing/unsignedShortcut.shortcut"
#define SIGNED_SHORTCUT_OUTPUT_PATH "/Users/0xilis/Downloads/0/GitGub/WorkflowKit-Ent-Testing/SIGNED_RESULT.shortcut"
/* Notice: recordRepresentationWithError: method requires com.apple.developer.icloud-services entitlement to list CloudKit. Other stuff after may also require entitlements, I'm not sure :P */
int main(void) {
/* load WorkflowKit framework */
NSBundle *container = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/WorkflowKit.framework"];
if ([container load]) {
/* get classes */
Class WFWorkflowFileDescriptorClass = objc_getClass("WFWorkflowFileDescriptor");
Class WFWorkflowFileClass = objc_getClass("WFWorkflowFile");
Class WFGallerySessionManagerClass = objc_getClass("WFGallerySessionManager");
Class WFFileRepresentationClass = objc_getClass("WFFileRepresentation");
/* get WFWorkflowRecord from file */
WFFileRepresentation *fileRep = [WFFileRepresentationClass fileWithURL:[NSURL fileURLWithPath:@UNSIGNED_SHORTCUT_PATH] options:nil];
WFWorkflowFileDescriptor *fileDesc = [[WFWorkflowFileDescriptorClass alloc] initWithFile:fileRep name:@"SnoolieShortcut"];
WFWorkflowFile *wFile = [[WFWorkflowFileClass alloc] initWithDescriptor:fileDesc error:nil];
WFWorkflowRecord *workflowRecord = [wFile recordRepresentationWithError:nil]; /* requires cloudkit entitlement */
/* now actually sign shortcut */
/*
* iOS 15 has two classes in relating to iCloud signing:
* WFiCloudShortcutFileExporter (the one actually used in the macOS CLI tool, outputs signed file)
* WFShortcutiCloudLinkExporter (outputs iCloud URL)
* However, both of these are just wrappers around WFGallerySessionManager
* since its methods exist in iOS 13/14, it can easily be backported to those versions
* here, I call uploadWorkflow: just like the normal implementation, however
* the normal implementation of WFiCloudShortcutFileExporter parses the file URL
* to get the shortcut identifier, then calls getWorkflowForIdentifier
* I'm not sure what this method does exactly, but it is on iOS 13, however I was worried it
* might not return signing information on iOS 13, so to be safe, instead I parse the iCloud
* URL that is returned, and use the iCloud API to retrive the signed shortcut file.
* Also, note I only tested this when being injected into the Shortcuts process :P.
*/
WFGallerySessionManager *sharedManager = [WFGallerySessionManagerClass sharedManager];
WFFileRepresentation *fileRep = [WFFileRepresentationClass fileWithURL:[NSURL fileURLWithPath:@UNSIGNED_SHORTCUT_PATH] options:nil];
[sharedManager uploadWorkflow:workflowRecord withName:@"Signed Shortcut Test" shortDescription:nil longDescription:nil private:YES completionHandler:^(NSURL *iCloudURLToShortcut, id exportError) {
if (!exportError) {
if (iCloudURLToShortcut) {
/*
* iCloudURLToShortcut will be https://www.icloud.com/shortcuts/(shortcutid)
* We need to get https://www.icloud.com/shortcuts/api/records/(shortcutid)
* This will have the URL to the signed shortcut file.
*/
NSString *apiURLString = [iCloudURLToShortcut.absoluteString stringByReplacingOccurrencesOfString:@"/shortcuts/" withString:@"/shortcuts/api/records/"];
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:apiURLString]];
NSError *jsonError = nil;
NSDictionary *apiResponse = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonError];
if (!jsonError) {
/* you probably should check for nil with these keys instead of just blindly trusting they all exist but eh, this is just a quick PoC */
NSString *signedShortcutURL = apiResponse[@"fields"][@"signedShortcut"][@"value"][@"downloadURL"];
NSURL *dataGetURL = [[NSURL alloc] initWithString:[signedShortcutURL stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
NSData *signedShortcutData = [NSData dataWithContentsOfURL:dataGetURL];
[signedShortcutData writeToFile:@SIGNED_SHORTCUT_OUTPUT_PATH atomically:YES];
} else {
/* handle json error */
NSLog(@""ShortcutsCLI-iOS(jsonError): %@",jsonError);
}
} else {
/* both exportError and iCloudURLToShortcut are nil?? */
NSLog(@"ShortcutsCLI-iOS Error: nil but no err?");
}
} else {
/* Handle errors with iCloud uploading */
NSLog(@"ShortcutsCLI-iOS Error: %@",exportError);
}
}];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment