Skip to content

Commit

Permalink
feat: add support for passkeys
Browse files Browse the repository at this point in the history
  • Loading branch information
coroiu committed Jul 25, 2024
1 parent d06a529 commit 45c9202
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 12 deletions.
45 changes: 34 additions & 11 deletions apps/desktop/desktop_native/objc/src/native/commands/sync.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#import <AuthenticationServices/ASCredentialIdentityStoreState.h>
#import <AuthenticationServices/ASCredentialServiceIdentifier.h>
#import <AuthenticationServices/ASPasswordCredentialIdentity.h>
#import <AuthenticationServices/ASPasskeyCredentialIdentity.h>
#import "../utils.h"
#import "../interop.h"
#import "sync.h"

Expand All @@ -11,26 +13,47 @@ void runSync(void* context, NSDictionary *params) {
NSArray *credentials = params[@"credentials"];

// Map credentials to ASPasswordCredential objects
NSMutableArray *passwordCredentials = [NSMutableArray arrayWithCapacity:credentials.count];
NSMutableArray *mappedCredentials = [NSMutableArray arrayWithCapacity:credentials.count];
for (NSDictionary *credential in credentials) {
NSString *cipherId = credential[@"cipherId"];
NSString *uri = credential[@"uri"];
NSString *username = credential[@"username"];
NSString *type = credential[@"type"];

ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc]
initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL];
ASPasswordCredentialIdentity *credential = [[ASPasswordCredentialIdentity alloc]
initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId];
if ([type isEqualToString:@"password"]) {
NSString *cipherId = credential[@"cipherId"];
NSString *uri = credential[@"uri"];
NSString *username = credential[@"username"];

[passwordCredentials addObject:credential];
ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc]
initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL];
ASPasswordCredentialIdentity *credential = [[ASPasswordCredentialIdentity alloc]
initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId];

[mappedCredentials addObject:credential];
}

if ([type isEqualToString:@"fido2"]) {
NSString *cipherId = credential[@"cipherId"];
NSString *rpId = credential[@"rpId"];
NSString *userName = credential[@"userName"];
NSData *credentialId = decodeBase64URL(credential[@"credentialId"]);
NSData *userHandle = decodeBase64URL(credential[@"userHandle"]);

ASPasskeyCredentialIdentity *credential = [[ASPasskeyCredentialIdentity alloc]
initWithRelyingPartyIdentifier:rpId
userName:userName
credentialID:credentialId
userHandle:userHandle
recordIdentifier:cipherId];

[mappedCredentials addObject:credential];
}
}

[ASCredentialIdentityStore.sharedStore replaceCredentialIdentityEntries:passwordCredentials
[ASCredentialIdentityStore.sharedStore replaceCredentialIdentityEntries:mappedCredentials
completion:^(BOOL success, NSError * _Nullable error) {
if (error) {
return _return(context, _error_er(error));
}

_return(context, _success(@{@"added": @([passwordCredentials count])}));
_return(context, _success(@{@"added": @([mappedCredentials count])}));
}];
}
2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/objc/src/native/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
NSDictionary *parseJson(NSString *jsonString, NSError *error);
NSString *serializeJson(NSDictionary *dictionary, NSError *error);

NSData *decodeBase64URL(NSString *base64URLString);

#endif
10 changes: 10 additions & 0 deletions apps/desktop/desktop_native/objc/src/native/utils.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

NSData *decodeBase64URL(NSString *base64URLString) {
NSString *base64String = [base64URLString stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"];

NSData *nsdataFromBase64String = [[NSData alloc]
initWithBase64EncodedString:base64String options:0];

return nsdataFromBase64String;
}
Binary file not shown.
10 changes: 9 additions & 1 deletion apps/desktop/src/autofill/services/desktop-autofill.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Subject, mergeMap, takeUntil } from "rxjs";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";

Check warning on line 9 in apps/desktop/src/autofill/services/desktop-autofill.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/desktop/src/autofill/services/desktop-autofill.service.ts#L4-L9

Added lines #L4 - L9 were not covered by tests
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
Expand Down Expand Up @@ -55,7 +56,7 @@ export class DesktopAutofillService implements OnDestroy {
return;

Check warning on line 56 in apps/desktop/src/autofill/services/desktop-autofill.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/desktop/src/autofill/services/desktop-autofill.service.ts#L56

Added line #L56 was not covered by tests
}

const fido2Credentials: NativeAutofillFido2Credential[] = [];
let fido2Credentials: NativeAutofillFido2Credential[];
let passwordCredentials: NativeAutofillPasswordCredential[];

if (status.value.support.password) {
Expand All @@ -76,6 +77,13 @@ export class DesktopAutofillService implements OnDestroy {
}));
}

if (status.value.support.fido2) {
fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({

Check warning on line 81 in apps/desktop/src/autofill/services/desktop-autofill.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/desktop/src/autofill/services/desktop-autofill.service.ts#L81

Added line #L81 was not covered by tests
type: "fido2",
...credential,
}));
}

const syncResult = await ipc.autofill.runCommand<NativeAutofillSyncCommand>({

Check warning on line 87 in apps/desktop/src/autofill/services/desktop-autofill.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/desktop/src/autofill/services/desktop-autofill.service.ts#L87

Added line #L87 was not covered by tests
command: "sync",
params: {
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/platform/main/autofill/sync.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export type NativeAutofillCredential =

export type NativeAutofillFido2Credential = {
type: "fido2";
cipherId: string;
rpId: string;
userName: string;
/** Should be Base64URL-encoded binary data */
credentialId: string;
/** Should be Base64URL-encoded binary data */
userHandle: string;
};

export type NativeAutofillPasswordCredential = {
Expand Down
26 changes: 26 additions & 0 deletions libs/common/src/platform/services/fido2/fido2-autofill-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// TODO: Add tests for this method

import { CipherType } from "../../../vault/enums";

Check warning on line 3 in libs/common/src/platform/services/fido2/fido2-autofill-utils.ts

View check run for this annotation

Codecov / codecov/patch

libs/common/src/platform/services/fido2/fido2-autofill-utils.ts#L3

Added line #L3 was not covered by tests
import { CipherView } from "../../../vault/models/view/cipher.view";
import { Fido2CredentialAutofillView } from "../../../vault/models/view/fido2-credential-autofill.view";

// TODO: Move into Fido2AuthenticatorService
export async function getCredentialsForAutofill(

Check warning on line 8 in libs/common/src/platform/services/fido2/fido2-autofill-utils.ts

View check run for this annotation

Codecov / codecov/patch

libs/common/src/platform/services/fido2/fido2-autofill-utils.ts#L8

Added line #L8 was not covered by tests
ciphers: CipherView[],
): Promise<Fido2CredentialAutofillView[]> {
return ciphers

Check warning on line 11 in libs/common/src/platform/services/fido2/fido2-autofill-utils.ts

View check run for this annotation

Codecov / codecov/patch

libs/common/src/platform/services/fido2/fido2-autofill-utils.ts#L11

Added line #L11 was not covered by tests
.filter(
(cipher) =>
!cipher.isDeleted && cipher.type === CipherType.Login && cipher.login.hasFido2Credentials,
)
.map((cipher) => {
const credential = cipher.login.fido2Credentials[0];
return {

Check warning on line 18 in libs/common/src/platform/services/fido2/fido2-autofill-utils.ts

View check run for this annotation

Codecov / codecov/patch

libs/common/src/platform/services/fido2/fido2-autofill-utils.ts#L17-L18

Added lines #L17 - L18 were not covered by tests
cipherId: cipher.id,
credentialId: credential.credentialId,
rpId: credential.rpId,
userHandle: credential.userHandle,
userName: credential.userName,
};
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class Fido2CredentialAutofillView {

Check warning on line 1 in libs/common/src/vault/models/view/fido2-credential-autofill.view.ts

View check run for this annotation

Codecov / codecov/patch

libs/common/src/vault/models/view/fido2-credential-autofill.view.ts#L1

Added line #L1 was not covered by tests
cipherId: string;
credentialId: string;
rpId: string;
userHandle: string;
userName: string;
}

0 comments on commit 45c9202

Please sign in to comment.