2 // KCAESGCMDuplexSession.m
7 #import <KeychainCircle/KCAESGCMDuplexSession.h>
8 #import <KeychainCircle/KCDer.h>
9 #import <KeychainCircle/KCError.h>
10 #import <NSError+KCCreationHelpers.h>
11 #import <NSData+SecRandom.h>
13 #include <corecrypto/ccaes.h>
14 #include <corecrypto/ccmode.h>
15 #include <corecrypto/cchkdf.h>
16 #include <corecrypto/ccsha2.h>
18 #include <corecrypto/ccder.h>
20 #include <libkern/OSByteOrder.h>
23 #define kdfInfoForwardString "send->recv"
24 #define kdfInfoBackwardString "recv->send"
25 static NSData* kdfInfoSendToReceive = nil;
26 static NSData* kdfInfoReceiveToSend = nil;
28 static const int kKCAESGCMTagSize = CCAES_KEY_SIZE_128;
29 static const int kKCAESGCMKeySize = CCAES_KEY_SIZE_128;
31 static bool derive_and_init(const struct ccmode_gcm *mode, ccgcm_ctx* ctx, NSData* sharedSecret, NSData* info) {
32 const struct ccdigest_info *di = ccsha256_di();
34 NSMutableData* space = [NSMutableData dataWithLength:di->output_size];
38 cc_status = cchkdf(di,
39 sharedSecret.length, sharedSecret.bytes,
41 info.length, info.bytes,
42 space.length, space.mutableBytes);
47 // We only use the first 16 bytes (128 bits) for the key.
48 cc_status = ccgcm_init(mode, ctx, kKCAESGCMKeySize, space.bytes);
49 cc_clear(space.length, space.mutableBytes);
51 return cc_status == 0;
54 @interface NSMutableData(KAESGCM)
55 - (void) replaceTrailingWith7LSB: (uint64_t) value;
58 @implementation NSMutableData(KAESGCM)
59 - (void) replaceTrailingWith7LSB: (uint64_t) value {
60 uint8_t bytes[sizeof(value)];
61 OSWriteBigInt64(bytes, 0, value);
63 [self replaceBytesInRange: NSMakeRange(self.length - 7, 7) withBytes: (bytes + 1)];
69 @interface KCAESGCMDuplexSession ()
70 @property (readwrite) bool asSender;
71 @property (readwrite) uint64_t context;
72 @property (readwrite) NSData* secret;
74 @property (readwrite) ccgcm_ctx * send;
75 @property (readwrite) ccgcm_ctx * receive;
79 @implementation KCAESGCMDuplexSession
81 + (nullable instancetype) sessionAsSender: (NSData*) sharedSecret
82 context: (uint64_t) context {
83 return [[KCAESGCMDuplexSession alloc] initAsSender:sharedSecret
87 + (nullable instancetype) sessionAsReceiver: (NSData*) sharedSecret
88 context: (uint64_t) context {
89 return [[KCAESGCMDuplexSession alloc] initAsReceiver:sharedSecret
94 static NSString* KCDSSender = @"asSender";
95 static NSString* KCDSSecret = @"secret";
96 static NSString* KCDSContext = @"context";
98 - (void)encodeWithCoder:(NSCoder *)aCoder {
99 [aCoder encodeBool: self.asSender forKey:KCDSSender];
100 [aCoder encodeObject: self.secret forKey:KCDSSecret];
101 [aCoder encodeInt64: self.context forKey:KCDSContext];
104 - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
106 bool asSender = [aDecoder decodeBoolForKey:KCDSSender];
107 NSData* secret = [aDecoder decodeObjectOfClass:[NSData class] forKey:KCDSSecret];
108 uint64_t context = [aDecoder decodeInt64ForKey:KCDSContext];
110 return [self initWithSecret:secret context:context as:asSender];
113 + (BOOL)supportsSecureCoding {
119 - (nullable instancetype) initAsSender: (NSData*) sharedSecret context: (uint64_t) context {
120 return [self initWithSecret:sharedSecret context:context as:true];
123 - (nullable instancetype) initAsReceiver: (NSData*) sharedSecret context: (uint64_t) context {
124 return [self initWithSecret:sharedSecret context:context as:false];
127 - (nullable instancetype) initWithSecret: (NSData*) sharedSecret
128 context: (uint64_t) context
130 static dispatch_once_t onceToken;
131 dispatch_once(&onceToken, ^{
132 kdfInfoSendToReceive = [NSData dataWithBytesNoCopy: kdfInfoForwardString
133 length: strlen(kdfInfoForwardString)
134 freeWhenDone: false];
136 kdfInfoReceiveToSend = [NSData dataWithBytesNoCopy: kdfInfoBackwardString
137 length: strlen(kdfInfoBackwardString)
138 freeWhenDone: false];
143 self.asSender = sender;
144 self.secret = sharedSecret;
145 self.send = malloc(ccgcm_context_size(ccaes_gcm_encrypt_mode()));
146 self.receive = malloc(ccgcm_context_size(ccaes_gcm_decrypt_mode()));
147 self.context = context;
149 if (self.send == nil || self.receive == nil) {
153 derive_and_init(ccaes_gcm_encrypt_mode(),
154 self.send, self.secret,
155 sender ? kdfInfoSendToReceive : kdfInfoReceiveToSend);
156 derive_and_init(ccaes_gcm_decrypt_mode(),
157 self.receive, self.secret,
158 !sender ? kdfInfoSendToReceive : kdfInfoReceiveToSend);
163 - (size_t) encryptCapsuleSize: (NSData*) plaintext IV: (NSData*) iv {
164 size_t iv_size = kcder_sizeof_data(iv, nil);
168 size_t text_size = kcder_sizeof_data(plaintext, nil);
169 if (text_size == 0) {
172 size_t tag_size = kcder_sizeof_data([NSMutableData dataWithLength: kKCAESGCMTagSize], nil);
176 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, iv_size + text_size + tag_size);
179 - (bool) GCM:(const struct ccmode_gcm*) mode
180 context:(ccgcm_ctx*) ctx
182 size:(size_t) data_size
183 data:(const uint8_t*) data
184 processed:(uint8_t*) result
185 tag:(uint8_t*) tagBuffer
186 error:(NSError**) error {
189 cc_status = ccgcm_reset(mode, ctx);
190 if (!CoreCryptoError(cc_status, error, @"ccgcm_reset failed: %d", cc_status))
193 cc_status = ccgcm_set_iv(mode, ctx, iv.length, iv.bytes);
194 if (!CoreCryptoError(cc_status, error, @"ccgcm_set_iv failed: %d", cc_status))
197 cc_status = ccgcm_update(mode, ctx, data_size, data, result);
198 if (!CoreCryptoError(cc_status, error, @"ccgcm_update failed: %d", cc_status))
201 cc_status = ccgcm_finalize(mode, ctx, kKCAESGCMTagSize, tagBuffer);
202 return CoreCryptoError(cc_status, error, @"ccgcm_finalize failed: %d", cc_status);
206 - (nullable NSData*) encrypt: (NSData*) data error: (NSError**) error {
207 static const int kIVSizeInBytes = 16;
209 NSMutableData* iv = [NSMutableData dataWithRandomBytes: kIVSizeInBytes];
211 NSMutableData* result = [NSMutableData dataWithLength: [self encryptCapsuleSize: data IV: iv]];
213 // Encode with all the space set up for the result:
215 uint8_t* der_end = result.mutableBytes + result.length;
216 const uint8_t* der = result.bytes;
219 uint8_t* encrypted = NULL;
221 der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
222 kcder_encode_data(iv, error, der,
223 kcder_encode_raw_octet_space(data.length, &encrypted, der,
224 kcder_encode_raw_octet_space(kKCAESGCMTagSize, &tag, der, der_end))));
226 if (der_end != der) {
227 KCJoiningErrorCreate(kAllocationFailure, error, @"Failed to allocate space for der");
231 const struct ccmode_gcm * mode = ccaes_gcm_encrypt_mode();
233 return [self GCM:mode
240 error:error] ? result : nil;
243 - (nullable NSData*) decryptAndVerify: (NSData*) data error: (NSError**) error {
245 const uint8_t *der = data.bytes;
246 const uint8_t *der_end = der + data.length;
248 const uint8_t *sequence_end = 0;
249 der = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, der, der_end);
251 if (der == NULL || sequence_end != der_end) {
252 KCJoiningErrorCreate(kDERUnknownEncoding, error, @"decode failed");
256 const uint8_t *encrypted = 0;
257 size_t encrypted_len = 0;
258 const uint8_t *received_tag = 0;
262 der = kcder_decode_data(&iv, error, der, der_end);
263 if (der == NULL) return nil;
265 encrypted = ccder_decode_constructed_tl(CCDER_OCTET_STRING, &der, der, der_end);
266 encrypted_len = der - encrypted;
268 received_tag = ccder_decode_constructed_tl(CCDER_OCTET_STRING, &der, der, der_end);
271 KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Decode failure");
275 if (der != der_end) {
276 KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Extra space");
280 if (der - received_tag != kKCAESGCMTagSize) {
281 KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Unexpected tag size: %ld", (long)(der - received_tag));
285 NSMutableData* decrypted = [NSMutableData dataWithLength: encrypted_len];
287 uint8_t tag[kKCAESGCMTagSize];
288 memcpy(tag, received_tag, sizeof(tag));
290 const struct ccmode_gcm * mode = ccaes_gcm_decrypt_mode();
292 return [self GCM:mode
297 processed:decrypted.mutableBytes
299 error:error] ? decrypted : nil;
302 #pragma clang diagnostic push
303 #pragma clang diagnostic ignored "-Wdeprecated-implementations"
306 ccgcm_ctx_clear(sizeof(*self.send), self.send);
310 ccgcm_ctx_clear(sizeof(*self.receive), self.receive);
315 #pragma clang diagnostic pop