Skip to content

Commit 2706835

Browse files
committed
Introduce v2 of signed archives. The signature goes at the end and signs everything that precedes it, rather than just the payload. Version 1 is still accepted as input. Output contains the v1 header but also contains the v2 signature, which is preferred when available. The migration path will be to ship this and rebuild all its files with it. Then the next version will build only v2. Then the version after that will accept only v2, and the migration code can be deleted.
1 parent 3af21f1 commit 2706835

File tree

11 files changed

+209
-23
lines changed

11 files changed

+209
-23
lines changed

SignedArchive/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
SignedArchive.xcodeproj/xcuserdata
2+
SignedArchive.xcodeproj/project.xcworkspace/xcuserdata
3+
SignedArchive.xcodeproj/xcshareddata/xcschemes

SignedArchive/SignedArchive/SIGArchiveBuilder.m

+84-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "SIGArchiveBuilder.h"
1010

1111
#import "SIGArchiveChunk.h"
12+
#import "SIGArchiveFlags.h"
1213
#import "SIGArchiveVerifier.h"
1314
#import "SIGCertificate.h"
1415
#import "SIGError.h"
@@ -34,11 +35,12 @@ - (instancetype)initWithPayloadFileURL:(NSURL *)url
3435

3536
- (BOOL)writeToURL:(NSURL *)url
3637
error:(out NSError * _Nullable __autoreleasing *)error {
38+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION
3739
NSData *signature = [self signature:error];
3840
if (!signature) {
3941
return NO;
4042
}
41-
43+
#endif
4244
NSData *certificate = _identity.signingCertificate.data;
4345
if (!certificate) {
4446
if (error) {
@@ -47,44 +49,73 @@ - (BOOL)writeToURL:(NSURL *)url
4749
return NO;
4850
}
4951

50-
NSOutputStream *writeStream = [NSOutputStream outputStreamWithURL:url append:NO];
51-
if (!writeStream) {
52+
// Write everything but the signature to a buffer in memory.
53+
NSOutputStream *combinedOutputStream = [NSOutputStream outputStreamToMemory];
54+
if (!combinedOutputStream) {
5255
if (error) {
5356
*error = [SIGError errorWithCode:SIGErrorCodeIOWrite detail:@"Could not create write stream"];
5457
}
5558
return NO;
5659
}
57-
[writeStream open];
58-
59-
if (![self writeHeaderToStream:writeStream error:error]) {
60+
[combinedOutputStream open];
61+
62+
if (![self writeHeaderToStream:combinedOutputStream error:error]) {
6063
return NO;
6164
}
62-
if (![self writeMetadataToStream:writeStream error:error]) {
65+
if (![self writeMetadataToStream:combinedOutputStream error:error]) {
6366
return NO;
6467
}
65-
if (![self writePayloadToStream:writeStream error:error]) {
68+
if (![self writePayloadToStream:combinedOutputStream error:error]) {
6669
return NO;
6770
}
68-
if (![self writeSignature:signature toStream:writeStream error:error]) {
71+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION
72+
if (![self writeSignature:signature toStream:combinedOutputStream error:error]) {
6973
return NO;
7074
}
71-
if (![self writeCertificate:certificate toStream:writeStream error:error]) {
75+
#endif
76+
if (![self writeCertificate:certificate toStream:combinedOutputStream error:error]) {
7277
return NO;
7378
}
7479

7580
// NOTE: The signing certificate must be first. This is a requirement of SecTrustCreateWithCertificates
7681
// which is implicit in the file format.
7782
SIGCertificate *issuerCertificate = _identity.signingCertificate.issuer;
7883
while (issuerCertificate != nil) {
79-
if (![self writeCertificate:issuerCertificate.data toStream:writeStream error:error]) {
84+
if (![self writeCertificate:issuerCertificate.data toStream:combinedOutputStream error:error]) {
8085
return NO;
8186
}
8287
if ([issuerCertificate.issuer isEqual:issuerCertificate]) {
8388
break;
8489
}
8590
issuerCertificate = issuerCertificate.issuer;
8691
}
92+
[combinedOutputStream close];
93+
94+
// Now compute the signature of that buffer.
95+
NSData *combinedData = [combinedOutputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
96+
if (!combinedData) {
97+
return NO;
98+
}
99+
NSData *signature2 = [self signatureForData:combinedData error:error];
100+
if (!signature2) {
101+
return NO;
102+
}
103+
104+
// Concatenate the combined data and the signature chunk to a file on disk.
105+
NSOutputStream *writeStream = [NSOutputStream outputStreamWithURL:url append:NO];
106+
[writeStream open];
107+
const long long length = [writeStream write:combinedData.bytes maxLength:combinedData.length];
108+
if (length != combinedData.length) {
109+
if (error) {
110+
*error = [SIGError errorWithCode:SIGErrorCodeIOWrite];
111+
}
112+
return NO;
113+
}
114+
if (![self writeSignature2:signature2 toStream:writeStream error:error]) {
115+
return NO;
116+
}
87117
[writeStream close];
118+
88119
return YES;
89120
}
90121

@@ -131,8 +162,14 @@ - (BOOL)writePayloadToStream:(NSOutputStream *)writeStream error:(out NSError *
131162
}
132163

133164
- (BOOL)writeMetadataToStream:(NSOutputStream *)writeStream error:(out NSError * _Nullable __autoreleasing *)error {
134-
NSArray<NSString *> *fields = @[ @"version=1",
135-
@"digest-type=SHA2" ];
165+
// Version 1 signed only the payload. Version 2 signs the container except the signature's chunk.
166+
NSArray<NSString *> *fields = @[
167+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION
168+
@"version=1",
169+
#else
170+
@"version=2",
171+
#endif
172+
@"digest-type=SHA2" ];
136173
NSString *metadata = [fields componentsJoinedByString:@"\n"];
137174
NSData *data = [metadata dataUsingEncoding:NSUTF8StringEncoding];
138175
SIGArchiveChunkWriter *chunkWriter = [[SIGArchiveChunkWriter alloc] initWithTag:SIGArchiveTagMetadata
@@ -145,6 +182,7 @@ - (BOOL)writeMetadataToStream:(NSOutputStream *)writeStream error:(out NSError *
145182
return ok;
146183
}
147184

185+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION
148186
- (BOOL)writeSignature:(NSData *)signature toStream:(NSOutputStream *)writeStream error:(out NSError * _Nullable __autoreleasing *)error {
149187
SIGArchiveChunkWriter *chunkWriter = [[SIGArchiveChunkWriter alloc] initWithTag:SIGArchiveTagSignature
150188
length:signature.length
@@ -155,6 +193,18 @@ - (BOOL)writeSignature:(NSData *)signature toStream:(NSOutputStream *)writeStrea
155193
_offset += chunkWriter.chunkLength;
156194
return ok;
157195
}
196+
#endif
197+
198+
- (BOOL)writeSignature2:(NSData *)signature toStream:(NSOutputStream *)writeStream error:(out NSError * _Nullable __autoreleasing *)error {
199+
SIGArchiveChunkWriter *chunkWriter = [[SIGArchiveChunkWriter alloc] initWithTag:SIGArchiveTagSignature2
200+
length:signature.length
201+
offset:_offset];
202+
const BOOL ok = [chunkWriter writeData:signature
203+
toStream:writeStream
204+
error:error];
205+
_offset += chunkWriter.chunkLength;
206+
return ok;
207+
}
158208

159209
- (BOOL)writeCertificate:(NSData *)certificate toStream:(NSOutputStream *)writeStream error:(out NSError * _Nullable __autoreleasing *)error {
160210
SIGArchiveChunkWriter *chunkWriter = [[SIGArchiveChunkWriter alloc] initWithTag:SIGArchiveTagCertificate
@@ -179,6 +229,7 @@ - (BOOL)writeCertificate:(NSData *)certificate toStream:(NSOutputStream *)writeS
179229
return algorithm;
180230
}
181231

232+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION
182233
- (NSData *)signature:(out NSError **)error {
183234
id<SIGSigningAlgorithm> algorithm = [self signingAlgorithm:error];
184235
if (!algorithm) {
@@ -192,6 +243,26 @@ - (NSData *)signature:(out NSError **)error {
192243
}
193244
return nil;
194245
}
246+
247+
return [algorithm signatureForInputStream:readStream
248+
usingIdentity:_identity
249+
error:error];
250+
}
251+
#endif
252+
253+
- (NSData *)signatureForData:(NSData *)data error:(out NSError **)error {
254+
id<SIGSigningAlgorithm> algorithm = [self signingAlgorithm:error];
255+
if (!algorithm) {
256+
return nil;
257+
}
258+
259+
NSInputStream *readStream = [NSInputStream inputStreamWithData:data];
260+
if (!readStream) {
261+
if (error) {
262+
*error = [SIGError errorWithCode:SIGErrorCodeIORead];
263+
}
264+
return nil;
265+
}
195266

196267
return [algorithm signatureForInputStream:readStream
197268
usingIdentity:_identity

SignedArchive/SignedArchive/SIGArchiveChunk.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ typedef NS_ENUM(long long, SIGArchiveTag) {
1414
SIGArchiveTagHeader = 0,
1515
SIGArchiveTagPayload = 1,
1616
SIGArchiveTagMetadata = 2,
17-
SIGArchiveTagSignature = 3,
17+
SIGArchiveTagSignature = 3, // Deprecated. Signs only the payload.
1818
SIGArchiveTagCertificate = 4,
19+
SIGArchiveTagSignature2 = 5, // Signs the entire file excluding the signature2 chunk. Must be the last entry.
1920
};
2021

2122
extern NSString *const SIGArchiveHeaderMagicString;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Create archives readable by older versions?
2+
#define ENABLE_SIGARCHIVE_MIGRATION_CREATION 1
3+
4+
// Accept archives from older versions?
5+
#define ENABLE_SIGARCHIVE_MIGRATION_VALIDATION 1
6+
7+
#if ENABLE_SIGARCHIVE_MIGRATION_CREATION || ENABLE_SIGARCHIVE_MIGRATION_VALIDATION
8+
#warning NOTE: SIGArchive migration flags enabled
9+
#endif
10+

SignedArchive/SignedArchive/SIGArchiveReader.h

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#import <Foundation/Foundation.h>
1010

11+
#import "SIGArchiveFlags.h"
12+
1113
NS_ASSUME_NONNULL_BEGIN
1214

1315
@interface SIGArchiveReader : NSObject
@@ -22,8 +24,16 @@ NS_ASSUME_NONNULL_BEGIN
2224

2325
- (nullable NSString *)header:(out NSError **)error;
2426
- (nullable NSString *)metadata:(out NSError **)error;
27+
28+
#if ENABLE_SIGARCHIVE_MIGRATION_VALIDATION
2529
- (nullable NSData *)signature:(out NSError **)error;
30+
#endif
31+
32+
- (NSData *)signature2:(out NSError * _Nullable __autoreleasing *)error;
33+
34+
2635
- (nullable NSInputStream *)payloadInputStream:(out NSError **)error;
36+
- (nullable NSInputStream *)payload2InputStream:(out NSError **)error;
2737
- (nullable NSArray<NSData *> *)signingCertificates:(out NSError **)error;
2838

2939
@end

SignedArchive/SignedArchive/SIGArchiveReader.m

+48
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#import "SIGArchiveBuilder.h"
1212
#import "SIGArchiveChunk.h"
13+
#import "SIGArchiveFlags.h"
1314
#import "SIGError.h"
1415
#import "SIGPartialInputStream.h"
1516

@@ -86,6 +87,7 @@ - (NSString *)metadata:(out NSError **)error {
8687
return string;
8788
}
8889

90+
#if ENABLE_SIGARCHIVE_MIGRATION_VALIDATION
8991
- (NSData *)signature:(out NSError * _Nullable __autoreleasing *)error {
9092
SIGArchiveChunk *chunk = [self chunkWithTag:SIGArchiveTagSignature];
9193
if (!chunk) {
@@ -96,6 +98,18 @@ - (NSData *)signature:(out NSError * _Nullable __autoreleasing *)error {
9698
}
9799
return [chunk data:error];
98100
}
101+
#endif
102+
103+
- (NSData *)signature2:(out NSError * _Nullable __autoreleasing *)error {
104+
SIGArchiveChunk *chunk = [self lastChunk];
105+
if (chunk.tag != SIGArchiveTagSignature2) {
106+
if (error) {
107+
*error = [SIGError errorWithCode:SIGErrorCodeNoSignature];
108+
}
109+
return nil;
110+
}
111+
return [chunk data:error];
112+
}
99113

100114
- (NSArray<NSData *> *)signingCertificates:(out NSError * _Nullable __autoreleasing *)error {
101115
NSArray<SIGArchiveChunk *> *chunks = [self chunksWithTag:SIGArchiveTagCertificate];
@@ -129,6 +143,29 @@ - (NSInputStream *)payloadInputStream:(out NSError **)error {
129143
chunk.payloadLength)];
130144
}
131145

146+
- (NSInputStream *)payload2InputStream:(out NSError **)error {
147+
// The last chunk is the signature. It signs the chunks that precede it. Find the chunk before
148+
// the signature to establish the number of bytes to sign.
149+
SIGArchiveChunk *chunk = [self penultimateChunk];
150+
if (!chunk) {
151+
if (error) {
152+
*error = [SIGError errorWithCode:SIGErrorCodeNoPayload];
153+
}
154+
return nil;
155+
}
156+
157+
const long long length = [chunk payloadOffset] + [chunk payloadLength];
158+
if (length <= 0) {
159+
if (error) {
160+
*error = [SIGError errorWithCode:SIGErrorCodeNoPayload];
161+
}
162+
return nil;
163+
}
164+
165+
return [[SIGPartialInputStream alloc] initWithURL:_url
166+
range:NSMakeRange(0, length)];
167+
}
168+
132169
- (long long)payloadLength {
133170
SIGArchiveChunk *chunk = [self chunkWithTag:SIGArchiveTagPayload];
134171
if (!chunk) {
@@ -187,6 +224,17 @@ - (BOOL)load:(out NSError **)errorOut {
187224

188225
#pragma mark - Private
189226

227+
- (SIGArchiveChunk *)lastChunk {
228+
return _chunks.lastObject;
229+
}
230+
231+
- (SIGArchiveChunk *)penultimateChunk {
232+
if (_chunks.count < 2) {
233+
return nil;
234+
}
235+
return _chunks[_chunks.count - 2];
236+
}
237+
190238
- (SIGArchiveChunk *)chunkWithTag:(SIGArchiveTag)tag {
191239
NSInteger index = [_chunks indexOfObjectPassingTest:^BOOL(SIGArchiveChunk * _Nonnull obj,
192240
NSUInteger idx,

0 commit comments

Comments
 (0)