]>
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> | |
21 | ||
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; | |
39 | @end | |
40 | ||
41 | @implementation KCJoiningAcceptSession | |
42 | ||
43 | + (nullable instancetype) sessionWithInitialMessage: (NSData*) message | |
44 | secretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate | |
45 | circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate | |
46 | dsid: (uint64_t) dsid | |
47 | error: (NSError**) error { | |
48 | ||
49 | int cc_error = 0; | |
50 | struct ccrng_state * rng = ccrng(&cc_error); | |
51 | ||
52 | if (rng == nil) { | |
53 | CoreCryptoError(cc_error, error, @"RNG fetch failed"); | |
54 | return nil; | |
55 | } | |
56 | ||
57 | return [[KCJoiningAcceptSession alloc] initWithSecretDelegate: secretDelegate | |
58 | circleDelegate: circleDelegate | |
59 | dsid: dsid | |
60 | rng: rng | |
61 | error: error]; | |
62 | } | |
63 | ||
64 | - (bool) setupSession: (NSError**) error { | |
65 | NSData* key = [self->_context getKey]; | |
66 | ||
67 | if (key == nil) { | |
68 | KCJoiningErrorCreate(kInternalError, error, @"No session key available"); | |
69 | return nil; | |
70 | } | |
71 | ||
72 | self->_session = [KCAESGCMDuplexSession sessionAsReceiver:key context:self.dsid]; | |
73 | ||
74 | return self.session != nil; | |
75 | } | |
76 | ||
77 | - (nullable instancetype) initWithSecretDelegate: (NSObject<KCJoiningAcceptSecretDelegate>*) secretDelegate | |
78 | circleDelegate: (NSObject<KCJoiningAcceptCircleDelegate>*) circleDelegate | |
79 | dsid: (uint64_t) dsid | |
80 | rng: (struct ccrng_state *)rng | |
81 | error: (NSError**) error { | |
82 | self = [super init]; | |
83 | ||
84 | NSString* name = [NSString stringWithFormat: @"%llu", dsid]; | |
85 | ||
86 | self->_context = [[KCSRPServerContext alloc] initWithUser: name | |
87 | password: [secretDelegate secret] | |
88 | digestInfo: ccsha256_di() | |
89 | group: ccsrp_gp_rfc5054_3072() | |
90 | randomSource: rng]; | |
91 | self->_secretDelegate = secretDelegate; | |
92 | self->_circleDelegate = circleDelegate; | |
93 | self->_state = kExpectingA; | |
94 | self->_dsid = dsid; | |
95 | ||
96 | return self; | |
97 | } | |
98 | ||
99 | - (NSString*) stateString { | |
100 | switch (self.state) { | |
101 | case kExpectingA: return @"→A"; | |
102 | case kExpectingM: return @"→M"; | |
103 | case kExpectingPeerInfo: return @"→PeerInfo"; | |
104 | case kAcceptDone: return @"done"; | |
105 | default: return [NSString stringWithFormat:@"%d", self.state]; | |
106 | } | |
107 | } | |
108 | ||
109 | - (NSString *)description { | |
110 | return [NSString stringWithFormat: @"<KCJoiningAcceptSession@%p %lld %@ %@>", self, self.dsid, [self stateString], self.context]; | |
111 | } | |
112 | ||
113 | - (NSData*) copyChallengeMessage: (NSError**) error { | |
114 | NSData* challenge = [self.context copyChallengeFor: self.startMessage error: error]; | |
115 | if (challenge == nil) return nil; | |
116 | ||
117 | NSData* srpMessage = [NSData dataWithEncodedSequenceData:self.context.salt data:challenge error:error]; | |
118 | ||
119 | if (![self setupSession:error]) return nil; | |
120 | ||
121 | return srpMessage; | |
122 | } | |
123 | ||
124 | - (NSData*) processInitialMessage: (NSData*) initialMessage error: (NSError**) error { | |
125 | self.startMessage = extractStartFromInitialMessage(initialMessage, error); | |
126 | if (self.startMessage == nil) return nil; | |
127 | ||
128 | NSData* srpMessage = [self copyChallengeMessage: error]; | |
129 | if (srpMessage == nil) return nil; | |
130 | ||
131 | self->_state = kExpectingM; | |
132 | return [[KCJoiningMessage messageWithType:kChallenge | |
133 | data:srpMessage | |
134 | error:error] der]; | |
135 | } | |
136 | ||
137 | - (NSData*) processResponse: (KCJoiningMessage*) message error:(NSError**) error { | |
138 | if ([message type] != kResponse) { | |
139 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected response!"); | |
140 | return nil; | |
141 | } | |
142 | ||
143 | // We handle failure, don't capture the error. | |
144 | NSData* confirmation = [self.context copyConfirmationFor:message.firstData error:NULL]; | |
145 | if (!confirmation) { | |
146 | // Find out what kind of error we should send. | |
147 | NSData* errorData = nil; | |
148 | ||
149 | switch ([self.secretDelegate verificationFailed: error]) { | |
150 | case kKCRetryError: | |
151 | // We fill in an error if they didn't, but if they did this wont bother. | |
152 | KCJoiningErrorCreate(kInternalError, error, @"Delegate returned error without filling in error: %@", self.secretDelegate); | |
153 | return nil; | |
154 | case kKCRetryWithSameChallenge: | |
155 | errorData = [NSData data]; | |
156 | break; | |
157 | case kKCRetryWithNewChallenge: | |
158 | if ([self.context resetWithPassword:[self.secretDelegate secret] error:error]) { | |
159 | errorData = [self copyChallengeMessage: error]; | |
160 | } | |
161 | break; | |
162 | } | |
163 | if (errorData == nil) return nil; | |
164 | ||
165 | return [[KCJoiningMessage messageWithType:kError | |
166 | data:errorData | |
167 | error:error] der]; | |
168 | } | |
169 | ||
170 | NSData* encoded = [NSData dataWithEncodedString:[self.secretDelegate accountCode] error:error]; | |
171 | if (encoded == nil) | |
172 | return nil; | |
173 | ||
174 | NSData* encrypted = [self.session encrypt:encoded error:error]; | |
175 | if (encrypted == nil) return nil; | |
176 | ||
177 | self->_state = kExpectingPeerInfo; | |
178 | ||
179 | return [[KCJoiningMessage messageWithType:kVerification | |
180 | data:confirmation | |
181 | payload:encrypted | |
182 | error:error] der]; | |
183 | } | |
184 | ||
185 | ||
186 | - (NSData*) processApplication: (KCJoiningMessage*) message error:(NSError**) error { | |
187 | if ([message type] != kPeerInfo) { | |
188 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Expected peerInfo!"); | |
189 | return nil; | |
190 | } | |
191 | ||
192 | NSData* decryptedPayload = [self.session decryptAndVerify:message.firstData error:error]; | |
193 | if (decryptedPayload == nil) return nil; | |
194 | ||
195 | CFErrorRef cfError = NULL; | |
196 | SOSPeerInfoRef ref = SOSPeerInfoCreateFromData(NULL, &cfError, (__bridge CFDataRef) decryptedPayload); | |
197 | if (ref == NULL) { | |
198 | if (error) *error = (__bridge_transfer NSError*) cfError; | |
199 | cfError = NULL; | |
200 | return nil; | |
201 | } | |
202 | ||
203 | NSData* joinData = [self.circleDelegate circleJoinDataFor:ref error:error]; | |
204 | if (joinData == nil) return nil; | |
205 | ||
206 | NSData* encryptedOutgoing = [self.session encrypt:joinData error:error]; | |
207 | if (encryptedOutgoing == nil) return nil; | |
208 | ||
209 | self->_state = kAcceptDone; | |
210 | ||
211 | return [[KCJoiningMessage messageWithType:kCircleBlob | |
212 | data:encryptedOutgoing | |
213 | error:error] der]; | |
214 | } | |
215 | ||
216 | ||
217 | - (nullable NSData*) processMessage: (NSData*) incomingMessage error: (NSError**) error { | |
218 | NSData* result = nil; | |
219 | ||
220 | KCJoiningMessage *message = (self.state != kExpectingA) ? [KCJoiningMessage messageWithDER:incomingMessage error:error] : nil; | |
221 | ||
222 | switch(self.state) { | |
223 | case kExpectingA: | |
224 | return [self processInitialMessage:incomingMessage error: error]; | |
225 | case kExpectingM: | |
226 | if (message == nil) return nil; | |
227 | return [self processResponse:message error: error]; | |
228 | break; | |
229 | case kExpectingPeerInfo: | |
230 | if (message == nil) return nil; | |
231 | return [self processApplication:message error: error]; | |
232 | break; | |
233 | case kAcceptDone: | |
234 | KCJoiningErrorCreate(kUnexpectedMessage, error, @"Unexpected message while done"); | |
235 | break; | |
236 | } | |
237 | return result; | |
238 | } | |
239 | ||
240 | - (bool) isDone { | |
241 | return self.state == kAcceptDone; | |
242 | } | |
243 | ||
244 | @end |