Skip to content

Commit

Permalink
Apple: Final support for ECIES V3.3
Browse files Browse the repository at this point in the history
  • Loading branch information
hvge committed Aug 28, 2024
1 parent d0191fc commit a67f0af
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 49 deletions.
162 changes: 134 additions & 28 deletions proj-xcode/PowerAuth2/PowerAuthSDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,60 @@ - (NSData*) biometryRelatedKeyWithAuthentication:(nonnull PowerAuthKeychainAuthe
#endif
}


- (PowerAuthCoreSignatureUnlockKeys*) signatureKeysForAuthentication:(nonnull PowerAuthAuthentication*)authentication
error:(NSError **)error
{
// Validate authentication object usage
[authentication validateUsage:NO];

// Generate signature key encryption keys
NSData *possessionKey = nil;
NSData *biometryKey = nil;
if (authentication.usePossession) {
if (authentication.overridenPossessionKey) {
possessionKey = authentication.overridenPossessionKey;
} else {
possessionKey = [self deviceRelatedKey];
}
}
if (authentication.useBiometry) {
if (authentication.overridenBiometryKey) {
// application specified a custom biometry key
biometryKey = authentication.overridenBiometryKey;
} else {
// default biometry key should be fetched
biometryKey = [self biometryRelatedKeyWithAuthentication:authentication.keychainAuthentication error:error];
if (!biometryKey) {
return nil;
}
}
}

// Prepare signature unlock keys structure
PowerAuthCoreSignatureUnlockKeys *keys = [[PowerAuthCoreSignatureUnlockKeys alloc] init];
keys.possessionUnlockKey = possessionKey;
keys.biometryUnlockKey = biometryKey;
keys.userPassword = authentication.password;
if (error) { *error = nil; }
return keys;
}

- (PowerAuthCoreSignatureFactor) determineSignatureFactorForAuthentication:(PowerAuthAuthentication*)authentication
{
PowerAuthCoreSignatureFactor factor = 0;
if (authentication.usePossession) {
factor |= PowerAuthCoreSignatureFactor_Possession;
}
if (authentication.password != nil) {
factor |= PowerAuthCoreSignatureFactor_Knowledge;
}
if (authentication.useBiometry) {
factor |= PowerAuthCoreSignatureFactor_Biometry;
}
return factor;
}

- (id<PowerAuthOperationTask>) fetchEncryptedVaultUnlockKey:(PowerAuthAuthentication*)authentication
reason:(PA2VaultUnlockReason)reason
callback:(void(^)(NSString * encryptedEncryptionKey, NSError *error))callback
Expand Down Expand Up @@ -552,27 +606,33 @@ - (void) cancelAllPendingTasks
requestData.platform = [PowerAuthSystem platform];
requestData.deviceInfo = [PowerAuthSystem deviceInfo];

PowerAuthCoreEciesEncryptor * decryptor = [[_sessionInterface writeTaskWithSession:^id _Nullable(PowerAuthCoreSession * _Nonnull session) {
// Start an activation
error = [_sessionInterface writeTaskWithSession:^NSError*(PowerAuthCoreSession * session) {
return [self prepareActivation:activation
forRequest:request
requestData:requestData
session:session];
}] extractResult:&error];

if (!decryptor) {
}];
if (error) {
callback(nil, error);
return nil;
}

// The create activation endpoint needs a custom object processing where we encrypt the inner data
// with a different encryptor. We have to do this in the HTTP client's queue to guarantee that time
// service is already synchronized.
PA2RestApiEndpoint * endpoint = [PA2RestApiEndpoint createActivationWithCustomStep:^NSError *{
// service is already synchronized and tempoerary key is acquired.
PA2RestApiEndpoint * endpoint = [PA2RestApiEndpoint createActivationWithCustomStep:^NSError*(PA2RestApiEndpoint * endpoint) {
// Encrypt payload and put it directly to the request object.
NSError * localError = nil;
request.activationData = [PA2ObjectSerialization encryptObject:requestData
encryptor:decryptor
error:&localError];
PowerAuthCoreEciesEncryptor * decryptor = [self encryptorWithId:PA2EncryptorId_ActivationPayload error:&localError];;
if (decryptor && !localError) {
request.activationData = [PA2ObjectSerialization encryptObject:requestData
encryptor:decryptor
error:&localError];
}
if (!localError) {
endpoint.customData = decryptor;
}
return localError;
}];

Expand All @@ -586,7 +646,7 @@ - (void) cancelAllPendingTasks
if (status == PowerAuthRestApiResponseStatus_OK) {
// Validate response from the server
return [self validateActivationResponse:response
decryptor:decryptor
decryptor:endpoint.customData
session:session];
}
[session resetSession:NO];
Expand Down Expand Up @@ -765,10 +825,10 @@ - (NSString*) activationFingerprint
The method requires request & request data and if everything's right, then request.activationData
is prepared and metods returns a new decryptor, required for response decryption.
*/
- (PA2Result<PowerAuthCoreEciesEncryptor*>*) prepareActivation:(PowerAuthActivation*)activation
forRequest:(PA2CreateActivationRequest*)request
requestData:(PA2CreateActivationRequestData*)requestData
session:(PowerAuthCoreSession*)session
- (NSError*) prepareActivation:(PowerAuthActivation*)activation
forRequest:(PA2CreateActivationRequest*)request
requestData:(PA2CreateActivationRequestData*)requestData
session:(PowerAuthCoreSession*)session
{
BOOL resetState = YES;
NSError * localError = nil;
Expand All @@ -783,27 +843,19 @@ - (NSString*) activationFingerprint
if (resultStep1) {
// Keep device's public key in requestData
requestData.devicePublicKey = resultStep1.devicePublicKey;

// Now we need to ecrypt request data with the Layer2 encryptor.
PowerAuthCoreEciesEncryptor * privateEncryptor = [self encryptorWithId:PA2EncryptorId_ActivationPayload error:&localError];
if (!localError) {
// Everything looks OS, so finally, try notify other apps that this instance started the activation.
localError = [_sessionInterface startExternalPendingOperation:PowerAuthExternalPendingOperationType_Activation];
if (!localError) {
return [PA2Result success:privateEncryptor];
}
}
// Everything looks OS, so finally, try notify other apps that this instance started the activation.
localError = [_sessionInterface startExternalPendingOperation:PowerAuthExternalPendingOperationType_Activation];
} else {
localError = PA2MakeError(PowerAuthErrorCode_InvalidActivationData, nil);
}
} else {
resetState = NO; // Don't reset state, there's already existing or pendign activation
localError = PA2MakeError(PowerAuthErrorCode_InvalidActivationState, nil);
}
if (resetState) {
if (localError && resetState) {
[session resetSession:NO];
}
return [PA2Result failure:localError];
return localError;
}

/**
Expand Down Expand Up @@ -1039,6 +1091,45 @@ - (NSString*) offlineSignatureWithAuthentication:(PowerAuthAuthentication*)authe
}


/**
This private method implements both online & offline signature calculations. Unlike the public interfaces, method accepts
PA2HTTPRequestData object as a source for data for signing and returns structured PA2HTTPRequestDataSignature object.
*/
- (PowerAuthCoreHTTPRequestDataSignature*) signHttpRequestData:(PowerAuthCoreHTTPRequestData*)requestData
authentication:(PowerAuthAuthentication*)authentication
error:(NSError**)error
{
[self checkForValidSetup];

return [[_sessionInterface writeTaskWithSession:^PA2Result<PowerAuthCoreHTTPRequestDataSignature*>* (PowerAuthCoreSession * session) {
// Check if there is an activation present
if (!session.hasValidActivation) {
return [PA2Result failure:PA2MakeError(PowerAuthErrorCode_MissingActivation, nil)];
}

// Determine authentication factor type
PowerAuthCoreSignatureFactor factor = [self determineSignatureFactorForAuthentication:authentication];
if (factor == 0) {
return [PA2Result failure:PA2MakeError(PowerAuthErrorCode_WrongParameter, nil)];
}

// Generate signature key encryption keys
NSError * localError = nil;
PowerAuthCoreSignatureUnlockKeys *keys = [self signatureKeysForAuthentication:authentication error:&localError];
if (keys == nil) { // Unable to fetch Touch ID related record - maybe user or iOS canacelled the operation?
return [PA2Result failure:localError];
}

// Compute signature for provided values and return result.
PowerAuthCoreHTTPRequestDataSignature * signature = [session signHttpRequestData:requestData keys:keys factor:factor];
if (signature == nil) {
return [PA2Result failure:PA2MakeError(PowerAuthErrorCode_SignatureError, nil)];
}
return [PA2Result success:signature];

}] extractResult:error];
}

- (BOOL) verifyServerSignedData:(nonnull NSData*)data
signature:(nonnull NSString*)signature
masterKey:(BOOL)masterKey
Expand All @@ -1049,6 +1140,7 @@ - (BOOL) verifyServerSignedData:(nonnull NSData*)data
signedData.signingDataKey = masterKey ? PowerAuthCoreSigningDataKey_ECDSA_MasterServerKey : PowerAuthCoreSigningDataKey_ECDSA_PersonalizedKey;
signedData.data = data;
signedData.signatureBase64 = signature;
signedData.signatureFormat = PowerAuthCoreSignatureFormat_ECDSA_DER;
return [session verifyServerSignedData: signedData];
}];
}
Expand Down Expand Up @@ -1388,6 +1480,7 @@ - (NSData*) generateInvalidBiometricKey

- (id<PowerAuthOperationTask>) signDataWithDevicePrivateKey:(PowerAuthAuthentication*)authentication
data:(NSData*)data
format:(PowerAuthCoreSignatureFormat)format
callback:(void(^)(NSData *signature, NSError *error))callback
{
return [self fetchEncryptedVaultUnlockKey:authentication reason:PA2VaultUnlockReason_SIGN_WITH_DEVICE_PRIVATE_KEY callback:^(NSString *encryptedEncryptionKey, NSError *error) {
Expand All @@ -1399,7 +1492,8 @@ - (NSData*) generateInvalidBiometricKey
signature = [_sessionInterface readTaskWithSession:^id (PowerAuthCoreSession * session) {
return [session signDataWithDevicePrivateKey:encryptedEncryptionKey
keys:keys
data:data];
data:data
format:format];
}];
// Propagate error
if (!signature) {
Expand All @@ -1409,6 +1503,17 @@ - (NSData*) generateInvalidBiometricKey
// Call back to application
callback(signature, error);
}];

}

- (id<PowerAuthOperationTask>) signDataWithDevicePrivateKey:(PowerAuthAuthentication*)authentication
data:(NSData*)data
callback:(void(^)(NSData *signature, NSError *error))callback
{
return [self signDataWithDevicePrivateKey:authentication
data:data
format:PowerAuthCoreSignatureFormat_ECDSA_DER
callback:callback];
}

- (id<PowerAuthOperationTask>) signJwtWithDevicePrivateKey:(PowerAuthAuthentication*)authentication
Expand All @@ -1424,6 +1529,7 @@ - (NSData*) generateInvalidBiometricKey
// Calculate signature
return [self signDataWithDevicePrivateKey:authentication
data:[signedData dataUsingEncoding:NSASCIIStringEncoding]
format:PowerAuthCoreSignatureFormat_ECDSA_JOSE
callback:^(NSData * signature, NSError * error) {
// Handle error
if (error) {
Expand Down
1 change: 1 addition & 0 deletions proj-xcode/PowerAuth2/private/PA2EncryptedRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

- (id) initWithCryptogram:(PowerAuthCoreEciesCryptogram*)cryptogram;

@property (nonatomic, strong) NSString * temporaryKeyId;
@property (nonatomic, strong) NSString * ephemeralPublicKey;
@property (nonatomic, strong) NSString * encryptedData;
@property (nonatomic, strong) NSString * mac;
Expand Down
3 changes: 3 additions & 0 deletions proj-xcode/PowerAuth2/private/PA2EncryptedRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ - (id) initWithCryptogram:(PowerAuthCoreEciesCryptogram*)cryptogram
- (NSDictionary*) toDictionary
{
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity:3];
if (_temporaryKeyId) {
dict[@"temporaryKeyId"] = _temporaryKeyId;
}
if (_ephemeralPublicKey) {
dict[@"ephemeralPublicKey"] = _ephemeralPublicKey;
}
Expand Down
4 changes: 2 additions & 2 deletions proj-xcode/PowerAuth2/private/PA2GetTemporaryKeyRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ @implementation PA2GetTemporaryKeyRequest
- (NSDictionary<NSString *,NSObject *> *)toDictionary
{
if (_activationId) {
return @{ @"appKey" : _appKey, @"activationId": _activationId, @"challenge" : _challenge };
return @{ @"applicationKey" : _appKey, @"activationId": _activationId, @"challenge" : _challenge };
} else {
return @{ @"appKey" : _appKey, @"challenge" : _challenge };
return @{ @"applicationKey" : _appKey, @"challenge" : _challenge };
}
}

Expand Down
8 changes: 4 additions & 4 deletions proj-xcode/PowerAuth2/private/PA2GetTemporaryKeyResponse.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ - (instancetype) initWithDictionary:(NSDictionary<NSString *,NSObject *> *)dicti
{
self = [super init];
if (self) {
_appKey = PA2ObjectAs(dictionary[@"appKey"], NSString);
_appKey = PA2ObjectAs(dictionary[@"applicationKey"], NSString);
_activationId = PA2ObjectAs(dictionary[@"activationId"], NSString);
_challenge = PA2ObjectAs(dictionary[@"challenge"], NSString);
_keyId = PA2ObjectAs(dictionary[@"keyId"], NSString);
_publicKey = PA2ObjectAs(dictionary[@"publicKey"], NSString);
_expiration = [PA2ObjectAs(dictionary[@"expiration"], NSNumber) unsignedLongLongValue];
_serverTime = [PA2ObjectAs(dictionary[@"serverTime"], NSNumber) unsignedLongLongValue];
_keyId = PA2ObjectAs(dictionary[@"sub"], NSString);
_expiration = [PA2ObjectAs(dictionary[@"exp_ms"], NSNumber) unsignedLongLongValue];
_serverTime = [PA2ObjectAs(dictionary[@"iat_ms"], NSNumber) unsignedLongLongValue];
}
return self;
}
Expand Down
8 changes: 5 additions & 3 deletions proj-xcode/PowerAuth2/private/PA2GetTemporaryKeyTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ - (instancetype) initWithHttpClient:(PA2HttpClient*)httpClient
if (self) {
_client = httpClient;
_sessionProvider = sessionProvider;
_applicationKey = applicationKey;
_deviceRelatedKey = deviceRelatedKey;
_encryptorScope = encryptorScope;
_delegate = delegate;
Expand Down Expand Up @@ -162,16 +163,17 @@ - (PA2GetTemporaryKeyResponse*) processResponseJwt:(PA2JwtObject*)responseJwt er
return nil;
}
if (![jwtHeaderObj.typ isEqualToString:@"JWT"]) {
*error = PA2MakeError(PowerAuthErrorCode_NetworkError, @"Unsupported JWT response");
*error = PA2MakeError(PowerAuthErrorCode_NetworkError, @"Unsupported JWT type in response");
}
if (![jwtHeaderObj.alg isEqualToString:@"ES256"]) {
*error = PA2MakeError(PowerAuthErrorCode_NetworkError, @"Unsupported JWT algorithm in response");
return nil;
}
PowerAuthCoreSignedData * signedData = [[PowerAuthCoreSignedData alloc] init];
signedData.data = [[[jwtHeader stringByAppendingString:@"."] stringByAppendingString:jwtPayload] dataUsingEncoding:NSASCIIStringEncoding];
signedData.signature = [[NSData alloc] initWithJwtEncodedString:jwtSignature];
signedData.signingDataKey = _isApplicationScope ? PowerAuthCoreSigningDataKey_ECDSA_MasterServerKey : PowerAuthCoreSigningDataKey_ECDSA_PersonalizedKey;
signedData.signatureFormat = PowerAuthCoreSignatureFormat_ECDSA_JOSE;
signedData.data = [[NSString stringWithFormat:@"%@.%@", jwtHeader, jwtPayload] dataUsingEncoding:NSUTF8StringEncoding];
signedData.signature = [[NSData alloc] initWithJwtEncodedString:jwtSignature];
BOOL valid = [_sessionProvider readBoolTaskWithSession:^BOOL(PowerAuthCoreSession * session) {
return [session verifyServerSignedData:signedData];
}];
Expand Down
4 changes: 2 additions & 2 deletions proj-xcode/PowerAuth2/private/PA2HttpRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ - (NSMutableURLRequest*) buildRequestWithHelper:(id<PA2PrivateCryptoHelper>)help
}

// Execute custom step before the request is serialized.
NSError * (^beforeSerialization)(void) = _endpoint.beforeRequestSerialization;
NSError * (^beforeSerialization)(PA2RestApiEndpoint*) = _endpoint.beforeRequestSerialization;
if (beforeSerialization) {
NSError * customStepError = beforeSerialization();
NSError * customStepError = beforeSerialization(_endpoint);
if (customStepError) {
if (error) *error = customStepError;
return nil;
Expand Down
2 changes: 1 addition & 1 deletion proj-xcode/PowerAuth2/private/PA2JwtObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ - (instancetype) initJwtWithAlg:(NSString *)alg
- (instancetype) initWithDictionary:(NSDictionary<NSString *,NSObject *> *)dictionary
{
return [self initWithTyp:PA2ObjectAs(dictionary[@"typ"], NSString)
withAlg:PA2ObjectAs(dictionary[@"typ"], NSString)];
withAlg:PA2ObjectAs(dictionary[@"alg"], NSString)];
}

- (NSDictionary<NSString *,NSObject *> *)toDictionary
Expand Down
4 changes: 3 additions & 1 deletion proj-xcode/PowerAuth2/private/PA2ObjectSerialization.m
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ + (PA2EncryptedRequest*) encryptObject:(id<PA2Encodable>)object
return nil;
}
// Finally, construct a request body from cryptogram
return [[PA2EncryptedRequest alloc] initWithCryptogram:cryptogram];
PA2EncryptedRequest * encryptedRequest = [[PA2EncryptedRequest alloc] initWithCryptogram:cryptogram];
encryptedRequest.temporaryKeyId = encryptor.associatedMetaData.temporaryKeyId;
return encryptedRequest;
}


Expand Down
7 changes: 5 additions & 2 deletions proj-xcode/PowerAuth2/private/PA2RestApiEndpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@
@property (nonatomic, assign, readonly) BOOL requireSynchronizedTime;

/// Contains block that will be executed on networking queue, before the request is serialized.
@property (nonatomic, strong, readonly) NSError *(^beforeRequestSerialization)(void);
@property (nonatomic, strong, readonly) NSError *(^beforeRequestSerialization)(PA2RestApiEndpoint * endpoint);

/// Arbitrary data produced in custom serialization steps.
@property (nonatomic, strong) id customData;

#pragma mark - Endpoint construction

+ (instancetype) createActivationWithCustomStep:(NSError*(^)(void))customStep;
+ (instancetype) createActivationWithCustomStep:(NSError*(^)(PA2RestApiEndpoint * endpoint))customStep;
+ (instancetype) getActivationStatus;
+ (instancetype) removeActivation;

Expand Down
2 changes: 1 addition & 1 deletion proj-xcode/PowerAuth2/private/PA2RestApiEndpoint.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ @implementation PA2RestApiEndpoint

#pragma mark - Activation

+ (instancetype) createActivationWithCustomStep:(NSError*(^)(void))customStep
+ (instancetype) createActivationWithCustomStep:(NSError*(^)(PA2RestApiEndpoint * endpoint))customStep
{
PA2RestApiEndpoint * endpoint = [[PA2RestApiEndpoint alloc] initWithPath:@"/pa/v3/activation/create"
request:[PA2CreateActivationRequest class]
Expand Down
Loading

0 comments on commit a67f0af

Please sign in to comment.