Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Support GPG signing #691

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
65 changes: 60 additions & 5 deletions GitUpKit/Core/GCRepository+Bare.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#endif

#import "GCPrivate.h"
#import "GPGKeys.h"

@implementation GCRepository (Bare)

Expand Down Expand Up @@ -364,20 +365,57 @@ - (GCCommit*)createCommitFromTree:(git_tree*)tree
message:(NSString*)message
error:(NSError**)error {
GCCommit* commit = nil;
git_commit* newCommit = NULL;
git_signature* signature = NULL;
const char *gpgSignature = NULL;

git_oid oid;

GCConfigOption* shouldSignOption = [self readConfigOptionForVariable:@"commit.gpgsign" error:nil];
BOOL shouldSign = [shouldSignOption.value isEqualToString:@"true"];

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_signature_default, &signature, self.private);
CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create, &oid, self.private, NULL, author ? author : signature, signature, NULL, GCCleanedUpCommitMessage(message).bytes, tree, count, parents);
git_commit* newCommit = NULL;

git_buf commitBuffer = GIT_BUF_INIT;
CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create_buffer, &commitBuffer, self.private, author ? author : signature, signature, NULL, GCCleanedUpCommitMessage(message).bytes, tree, count, parents);

if (shouldSign) {
GCConfigOption* signingKeyOption = [self readConfigOptionForVariable:@"user.signingkey" error:nil];
gpgSignature = [self gpgSig:commitBuffer.ptr keyId:signingKeyOption.value];
}

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create_with_signature, &oid, self.private, commitBuffer.ptr, gpgSignature, NULL);

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_lookup, &newCommit, self.private, &oid);
commit = [[GCCommit alloc] initWithRepository:self commit:newCommit];

cleanup:
git_buf_dispose(&commitBuffer);
git_signature_free(signature);
return commit;
}

-(const char*)gpgSig:(const char*)body keyId:(NSString*)keyId {
GPGKey *key = nil;

if (keyId.length > 0) {
key = [GPGKey secretKeyForId:keyId];
}

if (key == nil) {
key = [[GPGKey allSecretKeys] firstObject];
}

if (key == nil) {
return NULL;
}

NSString* plainToSign = [[NSString alloc] initWithCString:body encoding:NSUTF8StringEncoding];
NSString* signature = [key signSignature:plainToSign];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasderraugh I would like to trigger a separate discussion regarding UI blocking calls.
I'm focusing on signing commits functionality in current PR. However, even with the partial support I already observe significant UI regression – the main queue gets blocked for a 0.2-0.3 second when password for the gpg key already stored the keychain. It even can be an infinite period of time when GPG Keychain presents screen for GPG Key Password input.
Do you have any UI/UX related ideas on how we can play around this issue?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasderraugh Any ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, didn't realize there was an outstanding question. Let me think about what we could do here and I'll get back to you soon.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasderraugh any updates? 🙂


return [signature UTF8String];
}

- (GCCommit*)createCommitFromIndex:(git_index*)index
withParents:(const git_commit**)parents
count:(NSUInteger)count
Expand Down Expand Up @@ -437,22 +475,39 @@ - (GCCommit*)createCommitFromCommit:(git_commit*)commit
error:(NSError**)error {
git_commit* newCommit = NULL;
git_signature* signature = NULL;
const char *gpgSignature = NULL;

git_oid oid;

GCConfigOption* shouldSignOption = [self readConfigOptionForVariable:@"commit.gpgsign" error:nil];
BOOL shouldSign = [shouldSignOption.value isEqualToString:@"true"];

if (updateCommitter) {
CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_signature_default, &signature, self.private);
}

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create_from_callback, &oid, self.private, NULL,
git_buf commitBuffer = GIT_BUF_INIT;
CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create_buffer_for_parents_cb, &commitBuffer, self.private,
git_commit_author(commit),
updateCommitter ? signature : git_commit_committer(commit),
message ? NULL : git_commit_message_encoding(commit), message ? GCCleanedUpCommitMessage(message).bytes : git_commit_message(commit),
message ? NULL : git_commit_message_encoding(commit),
message ? GCCleanedUpCommitMessage(message).bytes : git_commit_message(commit),
git_tree_id(tree),
parents ? _CommitParentCallback_Parents : _CommitParentCallback_Commit, parents ? (__bridge void*)parents : (void*)commit);
parents ? _CommitParentCallback_Parents : _CommitParentCallback_Commit, parents ? (__bridge void*)parents : (void*)commit,
true);

if (shouldSign) {
GCConfigOption* signingKeyOption = [self readConfigOptionForVariable:@"user.signingkey" error:nil];
gpgSignature = [self gpgSig:commitBuffer.ptr keyId:signingKeyOption.value];
}

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_create_with_signature, &oid, self.private, commitBuffer.ptr, gpgSignature, NULL);

CALL_LIBGIT2_FUNCTION_GOTO(cleanup, git_commit_lookup, &newCommit, self.private, &oid);
XLOG_DEBUG_CHECK(!git_oid_equal(git_commit_id(newCommit), git_commit_id(commit)));

cleanup:
git_buf_dispose(&commitBuffer);
git_signature_free(signature);
return newCommit ? [[GCCommit alloc] initWithRepository:self commit:newCommit] : nil;
}
Expand Down
24 changes: 24 additions & 0 deletions GitUpKit/Core/GPGContext+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (C) 2015-2022 Pierre-Olivier Latour <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#import "GPGContext.h"

NS_ASSUME_NONNULL_BEGIN

@interface GPGContext()
@property (nonatomic, assign) gpgme_ctx_t gpgContext;
@end

NS_ASSUME_NONNULL_END
24 changes: 24 additions & 0 deletions GitUpKit/Core/GPGContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (C) 2015-2022 Pierre-Olivier Latour <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#import <Foundation/Foundation.h>
#include <gpgme.h>

NS_ASSUME_NONNULL_BEGIN

@interface GPGContext : NSObject
@end

NS_ASSUME_NONNULL_END
41 changes: 41 additions & 0 deletions GitUpKit/Core/GPGContext.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (C) 2015-2022 Pierre-Olivier Latour <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#import "GPGContext.h"
#import "GPGContext+Private.h"
#import "XLFacilityMacros.h"

@implementation GPGContext
-(instancetype)init {
self = [super init];
if (self) {
static dispatch_once_t initializeThreadInfo;
dispatch_once(&initializeThreadInfo, ^{
gpgme_check_version(NULL);
});

gpgme_error_t initError = gpgme_new(&_gpgContext);
if (initError) {
XLOG_ERROR(@"Failed to initialize GPGME context");
return nil;
}
}
return self;
}

-(void)dealloc {
gpgme_release(_gpgContext);
}
@end
33 changes: 33 additions & 0 deletions GitUpKit/Core/GPGKeys.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2015-2022 Pierre-Olivier Latour <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#import <Foundation/Foundation.h>
#include <gpgme.h>

NS_ASSUME_NONNULL_BEGIN

@interface GPGKey : NSObject
@property (readonly) NSString* email;
@property (readonly) NSString* name;
@property (readonly) NSString* keyId;

+(NSArray<GPGKey *> *)allSecretKeys;
+(nullable instancetype)secretKeyForId:(NSString*)keyId;

-(NSString*)signSignature:(NSString*)document;

@end

NS_ASSUME_NONNULL_END
Loading