Skip to content

Commit c2bb06b

Browse files
authored
Implement API for ExchangeAppAttestAssertionRequest endpoint (#8065)
* Implement assertion exchange * Tweak existing tests * Add tests * Rename JSON to better match gRPC message * Add HTTPBody helper * Review * Review 2 * Review 3
1 parent 2b16a5f commit c2bb06b

File tree

6 files changed

+293
-52
lines changed

6 files changed

+293
-52
lines changed

FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ NS_ASSUME_NONNULL_BEGIN
4242
challenge:(NSData *)challenge;
4343

4444
/// Exchanges attestation data (artifact & assertion) and a challenge for a FAC token.
45-
- (FBLPromise<FIRAppCheckToken *> *)appCheckTokenWithArtifact:(NSData *)artifact
46-
challenge:(NSData *)challenge
47-
assertion:(NSData *)assertion;
45+
- (FBLPromise<FIRAppCheckToken *> *)getAppCheckTokenWithArtifact:(NSData *)artifact
46+
challenge:(NSData *)challenge
47+
assertion:(NSData *)assertion;
4848

4949
@end
5050

FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.m

Lines changed: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,20 @@
3030

3131
NS_ASSUME_NONNULL_BEGIN
3232

33+
// TODO: Verify the following request fields.
34+
static NSString *const kRequestFieldArtifact = @"artifact";
35+
static NSString *const kRequestFieldAssertion = @"assertion";
3336
static NSString *const kRequestFieldAttestation = @"attestation_statement";
34-
static NSString *const kRequestFieldKeyID = @"key_id";
3537
static NSString *const kRequestFieldChallenge = @"challenge";
38+
static NSString *const kRequestFieldKeyID = @"key_id";
39+
40+
static NSString *const kExchangeAppAttestAssertionEndpoint = @"exchangeAppAttestAssertion";
41+
static NSString *const kExchangeAppAttestAttestationEndpoint = @"exchangeAppAttestAttestation";
42+
static NSString *const kGenerateAppAttestChallengeEndpoint = @"generateAppAttestChallenge";
3643

3744
static NSString *const kContentTypeKey = @"Content-Type";
3845
static NSString *const kJSONContentType = @"application/json";
46+
static NSString *const kHTTPMethodPost = @"POST";
3947

4048
@interface FIRAppAttestAPIService ()
4149

@@ -60,29 +68,34 @@ - (instancetype)initWithAPIService:(id<FIRAppCheckAPIServiceProtocol>)APIService
6068
return self;
6169
}
6270

63-
- (dispatch_queue_t)backgroundQueue {
64-
return dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
65-
}
71+
#pragma mark - Assertion request
72+
73+
- (FBLPromise<FIRAppCheckToken *> *)getAppCheckTokenWithArtifact:(NSData *)artifact
74+
challenge:(NSData *)challenge
75+
assertion:(NSData *)assertion {
76+
NSURL *URL = [self URLForEndpoint:kExchangeAppAttestAssertionEndpoint];
6677

67-
- (FBLPromise<FIRAppCheckToken *> *)appCheckTokenWithArtifact:(NSData *)artifact
68-
challenge:(NSData *)challenge
69-
assertion:(NSData *)assertion {
70-
// TODO: Implement.
71-
return [FBLPromise resolvedWith:nil];
78+
return [self HTTPBodyWithArtifact:artifact challenge:challenge assertion:assertion]
79+
.then(^FBLPromise<GULURLSessionDataResponse *> *(NSData *HTTPBody) {
80+
return [self.APIService sendRequestWithURL:URL
81+
HTTPMethod:kHTTPMethodPost
82+
body:HTTPBody
83+
additionalHeaders:@{kContentTypeKey : kJSONContentType}];
84+
})
85+
.then(^id _Nullable(GULURLSessionDataResponse *_Nullable response) {
86+
return [self.APIService appCheckTokenWithAPIResponse:response];
87+
});
7288
}
7389

7490
#pragma mark - Random Challenge
7591

7692
- (nonnull FBLPromise<NSData *> *)getRandomChallenge {
77-
NSString *URLString =
78-
[NSString stringWithFormat:@"%@/projects/%@/apps/%@:generateAppAttestChallenge",
79-
self.APIService.baseURL, self.projectID, self.appID];
80-
NSURL *URL = [NSURL URLWithString:URLString];
93+
NSURL *URL = [self URLForEndpoint:kGenerateAppAttestChallengeEndpoint];
8194

8295
return [FBLPromise onQueue:[self backgroundQueue]
8396
do:^id _Nullable {
8497
return [self.APIService sendRequestWithURL:URL
85-
HTTPMethod:@"POST"
98+
HTTPMethod:kHTTPMethodPost
8699
body:nil
87100
additionalHeaders:nil];
88101
}]
@@ -139,28 +152,50 @@ - (nullable NSData *)randomChallengeFromResponseBody:(NSData *)response error:(N
139152
- (FBLPromise<FIRAppAttestAttestationResponse *> *)attestKeyWithAttestation:(NSData *)attestation
140153
keyID:(NSString *)keyID
141154
challenge:(NSData *)challenge {
142-
NSString *URLString =
143-
[NSString stringWithFormat:@"%@/projects/%@/apps/%@:exchangeAppAttestAttestation",
144-
self.APIService.baseURL, self.projectID, self.appID];
145-
NSURL *URL = [NSURL URLWithString:URLString];
155+
NSURL *URL = [self URLForEndpoint:kExchangeAppAttestAttestationEndpoint];
146156

147157
return [self HTTPBodyWithAttestation:attestation keyID:keyID challenge:challenge]
148158
.then(^FBLPromise<GULURLSessionDataResponse *> *(NSData *HTTPBody) {
149159
return [self.APIService sendRequestWithURL:URL
150-
HTTPMethod:@"POST"
160+
HTTPMethod:kHTTPMethodPost
151161
body:HTTPBody
152162
additionalHeaders:@{kContentTypeKey : kJSONContentType}];
153163
})
154-
.then(^id _Nullable(GULURLSessionDataResponse *_Nullable URLResponse) {
155-
NSError *error;
164+
.thenOn(
165+
[self backgroundQueue], ^id _Nullable(GULURLSessionDataResponse *_Nullable URLResponse) {
166+
NSError *error;
156167

157-
__auto_type response =
158-
[[FIRAppAttestAttestationResponse alloc] initWithResponseData:URLResponse.HTTPBody
159-
requestDate:[NSDate date]
160-
error:&error];
168+
__auto_type response =
169+
[[FIRAppAttestAttestationResponse alloc] initWithResponseData:URLResponse.HTTPBody
170+
requestDate:[NSDate date]
171+
error:&error];
161172

162-
return response ?: error;
163-
});
173+
return response ?: error;
174+
});
175+
}
176+
177+
#pragma mark - Request HTTP Body
178+
179+
- (FBLPromise<NSData *> *)HTTPBodyWithArtifact:(NSData *)artifact
180+
challenge:(NSData *)challenge
181+
assertion:(NSData *)assertion {
182+
if (artifact.length <= 0 || challenge.length <= 0 || assertion.length <= 0) {
183+
FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
184+
[rejectedPromise reject:[FIRAppCheckErrorUtil
185+
errorWithFailureReason:@"Missing or empty request parameter."]];
186+
return rejectedPromise;
187+
}
188+
189+
return [FBLPromise onQueue:[self backgroundQueue]
190+
do:^id {
191+
id JSONObject = @{
192+
kRequestFieldArtifact : [self base64StringWithData:artifact],
193+
kRequestFieldChallenge : [self base64StringWithData:challenge],
194+
kRequestFieldAssertion : [self base64StringWithData:assertion]
195+
};
196+
197+
return [self HTTPBodyWithJSONObject:JSONObject];
198+
}];
164199
}
165200

166201
- (FBLPromise<NSData *> *)HTTPBodyWithAttestation:(NSData *)attestation
@@ -173,31 +208,56 @@ - (nullable NSData *)randomChallengeFromResponseBody:(NSData *)response error:(N
173208
return rejectedPromise;
174209
}
175210

176-
return [FBLPromise
177-
onQueue:[self backgroundQueue]
178-
do:^id _Nullable {
179-
NSError *encodingError;
180-
NSData *payloadJSON = [NSJSONSerialization dataWithJSONObject:@{
181-
kRequestFieldKeyID : keyID,
182-
kRequestFieldAttestation : [self base64StringWithData:attestation],
183-
kRequestFieldChallenge : [self base64StringWithData:challenge]
184-
}
185-
options:0
186-
error:&encodingError];
187-
188-
if (payloadJSON != nil) {
189-
return payloadJSON;
190-
} else {
191-
return [FIRAppCheckErrorUtil JSONSerializationError:encodingError];
192-
}
193-
}];
211+
return [FBLPromise onQueue:[self backgroundQueue]
212+
do:^id {
213+
id JSONObject = @{
214+
kRequestFieldKeyID : keyID,
215+
kRequestFieldAttestation : [self base64StringWithData:attestation],
216+
kRequestFieldChallenge : [self base64StringWithData:challenge]
217+
};
218+
219+
return [self HTTPBodyWithJSONObject:JSONObject];
220+
}];
221+
}
222+
223+
- (FBLPromise<NSData *> *)HTTPBodyWithJSONObject:(nonnull id)JSONObject {
224+
NSError *encodingError;
225+
NSData *payloadJSON = [NSJSONSerialization dataWithJSONObject:JSONObject
226+
options:0
227+
error:&encodingError];
228+
FBLPromise<NSData *> *HTTPBodyPromise = [FBLPromise pendingPromise];
229+
if (payloadJSON) {
230+
[HTTPBodyPromise fulfill:payloadJSON];
231+
} else {
232+
[HTTPBodyPromise reject:[FIRAppCheckErrorUtil JSONSerializationError:encodingError]];
233+
}
234+
return HTTPBodyPromise;
194235
}
195236

237+
#pragma mark - Helpers
238+
196239
- (NSString *)base64StringWithData:(NSData *)data {
197240
// TODO: Need to encode in base64URL?
198241
return [data base64EncodedStringWithOptions:0];
199242
}
200243

244+
- (NSURL *)URLForEndpoint:(NSString *)endpoint {
245+
NSString *URL = [[self class] URLWithBaseURL:self.APIService.baseURL
246+
projectID:self.projectID
247+
appID:self.appID];
248+
return [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", URL, endpoint]];
249+
}
250+
251+
+ (NSString *)URLWithBaseURL:(NSString *)baseURL
252+
projectID:(NSString *)projectID
253+
appID:(NSString *)appID {
254+
return [NSString stringWithFormat:@"%@/projects/%@/apps/%@", baseURL, projectID, appID];
255+
}
256+
257+
- (dispatch_queue_t)backgroundQueue {
258+
return dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
259+
}
260+
201261
@end
202262

203263
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)