]>
Commit | Line | Data |
---|---|---|
fa7225c8 A |
1 | // |
2 | // KCJoiningAcceptSession.m | |
3 | // Security | |
4 | // | |
5 | // | |
6 | ||
7 | #import <Foundation/Foundation.h> | |
8 | ||
9 | #import <KeychainCircle/KCJoiningSession.h> | |
10 | ||
11 | #import <KeychainCircle/KCError.h> | |
12 | #import <KeychainCircle/KCDer.h> | |
13 | #import <KeychainCircle/KCJoiningMessages.h> | |
14 | ||
15 | #import <KeychainCircle/NSError+KCCreationHelpers.h> | |
16 | ||
17 | #include <corecrypto/ccder.h> | |
18 | #include <corecrypto/ccrng.h> | |
19 | #include <corecrypto/ccsha2.h> | |
20 | #include <corecrypto/ccdh_gp.h> | |
866f8763 | 21 | #include <utilities/debugging.h> |
fa7225c8 A |
22 | #include <CommonCrypto/CommonRandomSPI.h> |
23 | ||
24 | typedef enum { | |
25 | kExpectingA, | |
26 | kExpectingM, | |
27 | kExpectingPeerInfo, | |
28 | kAcceptDone | |
29 | } KCJoiningAcceptSessionState; | |
30 | ||
31 | @interface KCJoiningAcceptSession () | |
32 | @property (readonly) uint64_t dsid; | |
33 | @property (readonly) NSObject<KCJoiningAcceptSecretDelegate>* secretDelegate; | |
34 | @property (readonly) NSObject<KCJoiningAcceptCircleDelegate>* circleDelegate; | |
35 | @property (readonly) KCSRPServerContext* context; | |
36 | @property (readonly) KCAESGCMDuplexSession* session; | |
37 | @property (readonly) KCJoiningAcceptSessionState state; | |
38 | @property (readwrite) NSData* startMessage; | |
866f8763 A |
39 | @property (readwrite) NSString *piggy_uuid; |
40 | @property (readwrite) PiggyBackProtocolVersion piggy_version; | |
fa7225c8 A |
41 | @end |
42 | ||
43 | @implementation KCJoiningAcceptSession | |
44 | ||
45 | + (nullable instancetype) sessionWithInitialMessage: (NSData*) message | |
46 | secretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate | |
47 | circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate | |
48 | dsid: (uint64_t) dsid | |
49 | error: (NSError**) error { | |
50 | ||
51 | int cc_error = 0; | |
52 | struct ccrng_state * rng = ccrng(&cc_error); | |
53 | ||
54 | if (rng == nil) { | |
55 | CoreCryptoError(cc_error, error, @"RNG fetch failed"); | |
56 | return nil; | |
57 | } | |
58 | ||
59 | return [[KCJoiningAcceptSession alloc] initWithSecretDelegate: secretDelegate | |
60 | circleDelegate: circleDelegate | |
61 | dsid: dsid | |
62 | rng: rng | |
63 | error: error]; | |
64 | } | |
65 | ||
66 | - (bool) setupSession: (NSError**) error { | |
67 | NSData* key = [self->_context getKey]; | |
68 | ||
69 | if (key == nil) { | |
70 | KCJoiningErrorCreate(kInternalError, error, @"No session key available"); | |
71 | return nil; | |
72 | } | |
73 | ||
74 | self->_session = [KCAESGCMDuplexSession sessionAsReceiver:key context:self.dsid]; | |
75 | ||
76 | return self.session != nil; | |
77 | } | |
78 | ||
79 | - (nullable instancetype) initWithSecretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate | |
80 | circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate | |
81 | dsid: (uint64_t) dsid | |
82 | rng: (struct ccrng_state *)rng | |
83 | error: (NSError**) error { | |
84 | self = [super init]; | |
85 | ||
86 | NSString* name = [NSString stringWithFormat: @"%llu", dsid]; | |
87 | ||
88 | self->_context = [[KCSRPServerContext alloc] initWithUser: name | |
89 | password: [secretDelegate secret] | |
90 | digestInfo: ccsha256_di() | |
91 | group: ccsrp_gp_rfc5054_3072() | |
92 | randomSource: rng]; | |
93 | self->_secretDelegate = secretDelegate; | |
94 | self->_circleDelegate = circleDelegate; | |
95 | self->_state = kExpectingA; | |
96 | self->_dsid = dsid; | |
866f8763 A |
97 | self->_piggy_uuid = nil; |
98 | self->_piggy_version = kPiggyV1; | |
fa7225c8 A |
99 | |
100 | return self; | |
101 | } | |
102 | ||
103 | - (NSString*) stateString { | |
104 | switch (self.state) { | |
105 | case kExpectingA: return @"→A"; | |
106 | case kExpectingM: return @"→M"; | |
107 | case kExpectingPeerInfo: return @"→PeerInfo"; | |
108 | case kAcceptDone: return @"done"; | |
109 | default: return [NSString stringWithFormat:@"%d", self.state]; | |
110 | } | |
111 | } | |
112 | ||
113 | - (NSString *)description { | |
114 | return [NSString stringWithFormat: @"<KCJoiningAcceptSession@%p %lld %@ %@>", self, self.dsid, [self stateString], self.context]; | |
115 | } | |
116 | ||
117 | - (NSData*) copyChallengeMessage: (NSError**) error { | |
118 | NSData* challenge = [self.context copyChallengeFor: self.startMessage error: error]; | |
119 | if (challenge == nil) return nil; | |
120 | ||
121 | NSData* srpMessage = [NSData dataWithEncodedSequenceData:self.context.salt data:challenge error:error]; | |
122 | ||
123 | if (![self setupSession:error]) return nil; | |
124 | ||
125 | return srpMessage; | |
126 | } | |
127 | ||
128 | - (NSData*) processInitialMessage: (NSData*) initialMessage error: (NSError**) error { | |
866f8763 A |
129 | uint64_t version = 0; |
130 | NSString *uuid = nil; | |
131 | ||
132 | self.startMessage = extractStartFromInitialMessage(initialMessage, &version, &uuid, error); | |
133 | self.piggy_uuid = uuid; | |
134 | self.piggy_version = (PiggyBackProtocolVersion)version; | |
135 | ||
fa7225c8 A |
136 | if (self.startMessage == nil) return nil; |
137 | ||
138 | NSData* srpMessage = [self copyChallengeMessage: error]; | |
139 | if (srpMessage == nil) return nil; | |
140 | ||
141 | self->_state = kExpectingM; | |
142 | return [[KCJoiningMessage messageWithType:kChallenge | |
143 | data:srpMessage | |
144 | error:error] der]; | |
145 | } | |
146 | ||
147 | - (NSData*) processResponse: (KCJoiningMessage*) message error:(NSError**) error { | |
148 | if ([message type] != kResponse) { | |
149 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected response!"); | |
150 | return nil; | |
151 | } | |
152 | ||
153 | // We handle failure, don't capture the error. | |
154 | NSData* confirmation = [self.context copyConfirmationFor:message.firstData error:NULL]; | |
155 | if (!confirmation) { | |
156 | // Find out what kind of error we should send. | |
157 | NSData* errorData = nil; | |
158 | ||
159 | switch ([self.secretDelegate verificationFailed: error]) { | |
160 | case kKCRetryError: | |
161 | // We fill in an error if they didn't, but if they did this wont bother. | |
162 | KCJoiningErrorCreate(kInternalError, error, @"Delegate returned error without filling in error: %@", self.secretDelegate); | |
163 | return nil; | |
164 | case kKCRetryWithSameChallenge: | |
165 | errorData = [NSData data]; | |
166 | break; | |
167 | case kKCRetryWithNewChallenge: | |
168 | if ([self.context resetWithPassword:[self.secretDelegate secret] error:error]) { | |
169 | errorData = [self copyChallengeMessage: error]; | |
170 | } | |
171 | break; | |
172 | } | |
173 | if (errorData == nil) return nil; | |
174 | ||
175 | return [[KCJoiningMessage messageWithType:kError | |
176 | data:errorData | |
177 | error:error] der]; | |
178 | } | |
179 | ||
180 | NSData* encoded = [NSData dataWithEncodedString:[self.secretDelegate accountCode] error:error]; | |
181 | if (encoded == nil) | |
182 | return nil; | |
183 | ||
184 | NSData* encrypted = [self.session encrypt:encoded error:error]; | |
185 | if (encrypted == nil) return nil; | |
186 | ||
187 | self->_state = kExpectingPeerInfo; | |
188 | ||
189 | return [[KCJoiningMessage messageWithType:kVerification | |
190 | data:confirmation | |
191 | payload:encrypted | |
192 | error:error] der]; | |
193 | } | |
194 | ||
fa7225c8 A |
195 | - (NSData*) processApplication: (KCJoiningMessage*) message error:(NSError**) error { |
196 | if ([message type] != kPeerInfo) { | |
197 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected peerInfo!"); | |
198 | return nil; | |
199 | } | |
200 | ||
201 | NSData* decryptedPayload = [self.session decryptAndVerify:message.firstData error:error]; | |
202 | if (decryptedPayload == nil) return nil; | |
203 | ||
204 | CFErrorRef cfError = NULL; | |
205 | SOSPeerInfoRef ref = SOSPeerInfoCreateFromData(NULL, &cfError, (__bridge CFDataRef) decryptedPayload); | |
206 | if (ref == NULL) { | |
207 | if (error) *error = (__bridge_transfer NSError*) cfError; | |
208 | cfError = NULL; | |
209 | return nil; | |
210 | } | |
211 | ||
212 | NSData* joinData = [self.circleDelegate circleJoinDataFor:ref error:error]; | |
213 | if (joinData == nil) return nil; | |
866f8763 A |
214 | |
215 | if(self->_piggy_version == kPiggyV1){ | |
216 | //grab iCloud Identity, TLK, BackupV0 thing | |
217 | secnotice("acceptor", "piggy version is 1"); | |
218 | NSData* initialSyncData = [self.circleDelegate circleGetInitialSyncViews:error]; | |
219 | NSMutableData* growPacket = [[NSMutableData alloc] initWithData:joinData]; | |
220 | [growPacket appendData:initialSyncData]; | |
221 | joinData = growPacket; | |
222 | } | |
223 | ||
fa7225c8 A |
224 | NSData* encryptedOutgoing = [self.session encrypt:joinData error:error]; |
225 | if (encryptedOutgoing == nil) return nil; | |
866f8763 | 226 | |
fa7225c8 A |
227 | self->_state = kAcceptDone; |
228 | ||
229 | return [[KCJoiningMessage messageWithType:kCircleBlob | |
230 | data:encryptedOutgoing | |
231 | error:error] der]; | |
232 | } | |
233 | ||
234 | ||
235 | - (nullable NSData*) processMessage: (NSData*) incomingMessage error: (NSError**) error { | |
236 | NSData* result = nil; | |
237 | ||
238 | KCJoiningMessage *message = (self.state != kExpectingA) ? [KCJoiningMessage messageWithDER:incomingMessage error:error] : nil; | |
239 | ||
240 | switch(self.state) { | |
241 | case kExpectingA: | |
242 | return [self processInitialMessage:incomingMessage error: error]; | |
243 | case kExpectingM: | |
244 | if (message == nil) return nil; | |
245 | return [self processResponse:message error: error]; | |
246 | break; | |
247 | case kExpectingPeerInfo: | |
248 | if (message == nil) return nil; | |
249 | return [self processApplication:message error: error]; | |
250 | break; | |
251 | case kAcceptDone: | |
252 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Unexpected message while done"); | |
253 | break; | |
254 | } | |
255 | return result; | |
256 | } | |
257 | ||
258 | - (bool) isDone { | |
259 | return self.state == kAcceptDone; | |
260 | } | |
261 | ||
262 | @end |