# HG changeset patch # User snej@snej.local # Date 1239598940 25200 # Node ID 4c0eafa7b233b7a0b6fc1ef8a785075b77fec045 # Parent dee779b84a95b5fc6f52df7fe5b44cadd16f8f2c * Added MYEncoder/Decoder (CMS) * Fixed some key-generation parameters to make the keys work with CMS. * Added MYCrypto+Cocoa, for identity picker. diff -r dee779b84a95 -r 4c0eafa7b233 MYCertGen.m --- a/MYCertGen.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCertGen.m Sun Apr 12 22:02:20 2009 -0700 @@ -177,7 +177,7 @@ // that's binary 111111000; see http://tools.ietf.org/html/rfc3280#section-4.2.1.3 CSSM_X509_EXTENSION keyUsage = { CSSMOID_KeyUsage, - true, + false, // non-critical CSSM_X509_DATAFORMAT_PARSED, {.parsedValue = &keyUsageBits} }; @@ -187,11 +187,11 @@ UInt32 count; const CSSM_OID *oids; }; - CSSM_OID usageOids[2] = {CSSMOID_ServerAuth, CSSMOID_ClientAuth}; - struct ExtendedUsageList extUsageBits = {2, usageOids}; + CSSM_OID usageOids[3] = {CSSMOID_ServerAuth, CSSMOID_ClientAuth, CSSMOID_ExtendedKeyUsageAny}; + struct ExtendedUsageList extUsageBits = {3, usageOids}; CSSM_X509_EXTENSION extendedKeyUsage = { CSSMOID_ExtendedKeyUsage, - true, + false, // non-critical CSSM_X509_DATAFORMAT_PARSED, {.parsedValue = &extUsageBits} }; @@ -466,10 +466,9 @@ Log(@"CSSM_CL_HANDLE = %p", cl); CAssert(cl); - MYKeychain *keychain = [MYKeychain allKeychains]; - Log(@"Looking for a key pair..."); - MYPrivateKey *privateKey = [[keychain enumeratePrivateKeys] nextObject]; - Log(@"Using key pair { %@, %@ }", privateKey, privateKey.publicKey); + Log(@"Generating a key pair..."); + MYPrivateKey *privateKey = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 2048]; + Log(@"Key-pair = { %@, %@ }", privateKey, privateKey.publicKey); Log(@"...creating cert..."); @@ -483,6 +482,7 @@ )); Log(@"Cert = %@", cert); CAssert(cert); + [cert.certificateData writeToFile: @"/tmp/MYCryptoTest.cer" atomically: NO]; Log(@"Cert name = %@", cert.commonName); Log(@"Cert email = %@", cert.emailAddresses); @@ -491,5 +491,7 @@ CAssertEqual(cert.emailAddresses, $array(@"waldo@example.com")); CAssertEqual(cert.publicKey.publicKeyDigest, privateKey.publicKeyDigest); - [cert.certificateData writeToFile: @"/tmp/MYCryptoTest.cer" atomically: NO]; + CAssert([[MYKeychain defaultKeychain] addCertificate: cert]); + + CAssert([cert setUserTrust: kSecTrustResultProceed]); } diff -r dee779b84a95 -r 4c0eafa7b233 MYCertificate-iPhone.m --- a/MYCertificate-iPhone.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCertificate-iPhone.m Sun Apr 12 22:02:20 2009 -0700 @@ -15,6 +15,10 @@ @implementation MYCertificate ++ (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef { + return [[[self alloc] initWithCertificateRef: certificateRef] autorelease]; +} + /** Creates a MYCertificate object for an existing Keychain certificate reference. */ - (id) initWithCertificateRef: (SecCertificateRef)certificateRef { self = [super initWithKeychainItemRef: (SecKeychainItemRef)certificateRef]; @@ -34,6 +38,10 @@ } +- (BOOL)isEqualToCertificate:(MYCertificate*)cert { + return [self isEqual: cert] || [self.certificateData isEqual: cert.certificateData]; +} + @synthesize certificateRef=_certificateRef; - (NSData*) certificateData { diff -r dee779b84a95 -r 4c0eafa7b233 MYCertificate.h --- a/MYCertificate.h Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCertificate.h Sun Apr 12 22:02:20 2009 -0700 @@ -22,11 +22,17 @@ } /** Creates a MYCertificate object for an existing Keychain certificate reference. */ ++ (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef; + +/** Initializes a MYCertificate object for an existing Keychain certificate reference. */ - (id) initWithCertificateRef: (SecCertificateRef)certificateRef; /** Creates a MYCertificate object from exported key data, but does not add it to any keychain. */ - (id) initWithCertificateData: (NSData*)data; +/** Checks whether two MYCertificate objects have bit-for-bit identical certificate data. */ +- (BOOL)isEqualToCertificate:(MYCertificate*)cert; + /** The Keychain object reference for this certificate. */ @property (readonly) SecCertificateRef certificateRef; @@ -63,4 +69,25 @@ #endif //@} + +/** @name Expert + */ +//@{ +#if !TARGET_OS_IPHONE + ++ (SecPolicyRef) X509Policy; ++ (SecPolicyRef) SSLPolicy; ++ (SecPolicyRef) SMIMEPolicy; +- (CSSM_CERT_TYPE) certificateType; +- (NSArray*) trustSettings; +- (BOOL) setUserTrust: (SecTrustUserSetting)trustSetting; + +#endif +//@} + @end + + +NSString* MYPolicyGetName( SecPolicyRef policy ); +NSString* MYTrustDescribe( SecTrustRef trust ); +NSString* MYTrustResultDescribe( SecTrustResultType result ); diff -r dee779b84a95 -r 4c0eafa7b233 MYCertificate.m --- a/MYCertificate.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCertificate.m Sun Apr 12 22:02:20 2009 -0700 @@ -8,6 +8,8 @@ #import "MYCertificate.h" #import "MYCrypto_Private.h" +#import "MYDigest.h" +#import "MYErrorUtils.h" #if !MYCRYPTO_USE_IPHONE_API @@ -24,6 +26,10 @@ return self; } ++ (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef { + return [[[self alloc] initWithCertificateRef: certificateRef] autorelease]; +} + /** Creates a MYCertificate object from exported key data, but does not add it to any keychain. */ - (id) initWithCertificateData: (NSData*)data type: (CSSM_CERT_TYPE) type @@ -48,6 +54,21 @@ encoding: CSSM_CERT_ENCODING_BER]; } + +- (NSString*) description { + return $sprintf(@"%@[%@ %@/%p]", + [self class], + self.commonName, + self.certificateData.my_SHA1Digest.abbreviatedHexString, + _certificateRef); +} + + +- (BOOL)isEqualToCertificate:(MYCertificate*)cert { + return [self isEqual: cert] || [self.certificateData isEqual: cert.certificateData]; +} + + + (MYCertificate*) preferredCertificateForName: (NSString*)name { SecCertificateRef certRef = NULL; if (!check(SecCertificateCopyPreference((CFStringRef)name, 0, &certRef), @@ -61,6 +82,7 @@ @"SecCertificateSetPreference"); } + @synthesize certificateRef=_certificateRef; - (NSData*) certificateData { @@ -98,7 +120,133 @@ } +#pragma mark - +#pragma mark TRUST/POLICY STUFF: + + ++ (SecPolicyRef) policyForOID: (CSSM_OID) policyOID { + SecPolicySearchRef search; + if (!check(SecPolicySearchCreate(CSSM_CERT_X_509v3, &policyOID, NULL, &search), + @"SecPolicySearchCreate")) + return nil; + SecPolicyRef policy = NULL; + if (!check(SecPolicySearchCopyNext(search, &policy), @"SecPolicySearchCopyNext")) + policy = NULL; + CFRelease(search); + return policy; +} + ++ (SecPolicyRef) X509Policy { + static SecPolicyRef sX509Policy = NULL; + if (!sX509Policy) + sX509Policy = [self policyForOID: CSSMOID_APPLE_X509_BASIC]; + return sX509Policy; +} + ++ (SecPolicyRef) SSLPolicy { + static SecPolicyRef sSSLPolicy = NULL; + if (!sSSLPolicy) + sSSLPolicy = [self policyForOID: CSSMOID_APPLE_TP_SSL]; + return sSSLPolicy; +} + ++ (SecPolicyRef) SMIMEPolicy { + static SecPolicyRef sSMIMEPolicy = NULL; + if (!sSMIMEPolicy) + sSMIMEPolicy = [self policyForOID: CSSMOID_APPLE_TP_SMIME]; + return sSMIMEPolicy; +} + + +- (CSSM_CERT_TYPE) certificateType { + CSSM_CERT_TYPE type = CSSM_CERT_UNKNOWN; + if (!check(SecCertificateGetType(_certificateRef, &type), @"SecCertificateGetType")) + type = CSSM_CERT_UNKNOWN; + return type; +} + +- (NSArray*) trustSettings { + CFArrayRef settings = NULL; + OSStatus err = SecTrustSettingsCopyTrustSettings(_certificateRef, kSecTrustSettingsDomainUser, + &settings); + if (err == errSecItemNotFound || !check(err,@"SecTrustSettingsCopyTrustSettings") || !settings) + return nil; + return [(id)CFMakeCollectable(settings) autorelease]; +} + + +- (BOOL) setUserTrust: (SecTrustUserSetting)trustSetting +{ + if (trustSetting == kSecTrustResultProceed) { + return check(SecTrustSettingsSetTrustSettings(_certificateRef, + kSecTrustSettingsDomainUser, nil), + @"SecTrustSettingsSetTrustSettings"); + } else if (trustSetting == kSecTrustResultDeny) { + OSStatus err = SecTrustSettingsRemoveTrustSettings(_certificateRef, + kSecTrustSettingsDomainUser); + return err == errSecItemNotFound || check(err, @"SecTrustSettingsRemoveTrustSettings"); + } else + return paramErr; +} + + @end +NSString* MYPolicyGetName( SecPolicyRef policy ) { + if (!policy) + return @"(null)"; + CSSM_OID oid = {}; + SecPolicyGetOID(policy, &oid); + return $sprintf(@"SecPolicy[%@]", OIDAsString(oid)); +} + +NSString* MYTrustResultDescribe( SecTrustResultType result ) { + static NSString* const kTrustResultNames[kSecTrustResultOtherError+1] = { + @"Invalid", + @"Proceed", + @"Confirm", + @"Deny", + @"Unspecified", + @"RecoverableTrustFailure", + @"FatalTrustFailure", + @"OtherError" + }; + if (result>=0 && result <=kSecTrustResultOtherError) + return kTrustResultNames[result]; + else + return $sprintf(@"(Unknown trust result %i)", result); +} + + +NSString* MYTrustDescribe( SecTrustRef trust ) { + SecTrustResultType result; + CFArrayRef certChain=NULL; + CSSM_TP_APPLE_EVIDENCE_INFO* statusChain; + OSStatus err = SecTrustGetResult(trust, &result, &certChain, &statusChain); + NSString *desc; + if (err) + desc = $sprintf(@"SecTrust[%p, err=%@]", trust, MYErrorName(NSOSStatusErrorDomain, err)); + else + desc = $sprintf(@"SecTrust[%@, %u in chain]", + MYTrustResultDescribe(result), + CFArrayGetCount(certChain)); + if (certChain) CFRelease(certChain); + return desc; +} + + + +TestCase(Trust) { + Log(@"X.509 policy = %@", MYPolicyGetName([MYCertificate X509Policy])); + Log(@" SSL policy = %@", MYPolicyGetName([MYCertificate SSLPolicy])); + Log(@"SMIME policy = %@", MYPolicyGetName([MYCertificate SMIMEPolicy])); + for (MYCertificate *cert in [[MYKeychain defaultKeychain] enumerateCertificates]) { + NSArray *settings = cert.trustSettings; + if (settings) + Log(@"---- %@ = %@", cert, settings); + } +} + + #endif !MYCRYPTO_USE_IPHONE_API diff -r dee779b84a95 -r 4c0eafa7b233 MYCrypto+Cocoa.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYCrypto+Cocoa.h Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,27 @@ +// +// MYCrypto+Cocoa.h +// MYCrypto +// +// Created by Jens Alfke on 4/10/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import <SecurityInterface/SFChooseIdentityPanel.h> +@class MYIdentity; + + +@interface SFChooseIdentityPanel (MYCrypto) + +- (NSInteger)my_runModalForIdentities:(NSArray *)myIdentitObjects + message:(NSString *)message; + +- (void)my_beginSheetForWindow:(NSWindow *)docWindow + modalDelegate:(id)delegate + didEndSelector:(SEL)didEndSelector + contextInfo:(void *)contextInfo + identities:(NSArray *)myIdentitObjects + message:(NSString *)message; + +- (MYIdentity*) my_identity; + +@end diff -r dee779b84a95 -r 4c0eafa7b233 MYCrypto+Cocoa.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYCrypto+Cocoa.m Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,48 @@ +// +// MYCrypto+Cocoa.m +// MYCrypto +// +// Created by Jens Alfke on 4/10/09. +// Copyright 2009 Jens Alfke. All rights reserved. +// + +#import "MYCrypto+Cocoa.h" +#import "MYCrypto_Private.h" +#import "MYIdentity.h" + + +@implementation SFChooseIdentityPanel (MYCrypto) + + +- (NSInteger)my_runModalForIdentities:(NSArray *)identities + message:(NSString *)message +{ + NSMutableArray *identityRefs = $marray(); + for (MYIdentity *ident in identities) + [identityRefs addObject: (id)ident.identityRef]; + return [self runModalForIdentities: identityRefs message: message]; +} + +- (void)my_beginSheetForWindow:(NSWindow *)docWindow + modalDelegate:(id)delegate + didEndSelector:(SEL)didEndSelector + contextInfo:(void *)contextInfo + identities:(NSArray *)identities + message:(NSString *)message +{ + NSMutableArray *identityRefs = $marray(); + for (MYIdentity *ident in identities) + [identityRefs addObject: (id)ident.identityRef]; + [self beginSheetForWindow:docWindow + modalDelegate:delegate + didEndSelector:didEndSelector + contextInfo:contextInfo + identities:identityRefs + message:message]; +} + +- (MYIdentity*) my_identity { + return [MYIdentity identityWithIdentityRef: [self identity]]; +} + +@end diff -r dee779b84a95 -r 4c0eafa7b233 MYCrypto-iPhone.xcodeproj/project.pbxproj --- a/MYCrypto-iPhone.xcodeproj/project.pbxproj Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCrypto-iPhone.xcodeproj/project.pbxproj Sun Apr 12 22:02:20 2009 -0700 @@ -22,7 +22,7 @@ 276FB34B0F856CA400CB326E /* MYCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 276FB34A0F856CA400CB326E /* MYCertificate.m */; }; 27A430120F87C6C10063D362 /* MYKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E823110F81D56E0019BE60 /* MYKey.m */; }; 27A430140F87C6D50063D362 /* MYSymmetricKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A430130F87C6D50063D362 /* MYSymmetricKey.m */; }; - 27E823210F81D56E0019BE60 /* MYCryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E8230F0F81D56E0019BE60 /* MYCryptor.m */; }; + 27E3A6AA0F91B8B30079D4D9 /* MYCryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E3A6A70F91B8B30079D4D9 /* MYCryptor.m */; }; 27E823240F81D56E0019BE60 /* MYKeychainItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E823150F81D56E0019BE60 /* MYKeychainItem.m */; }; 27E823270F81D56E0019BE60 /* MYDigest.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E8231C0F81D56E0019BE60 /* MYDigest.m */; }; 27E823290F81D56E0019BE60 /* MYCrypto.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 27E8231E0F81D56E0019BE60 /* MYCrypto.xcconfig */; }; @@ -58,9 +58,9 @@ 27A430130F87C6D50063D362 /* MYSymmetricKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYSymmetricKey.m; sourceTree = "<group>"; }; 27A430150F87C6DB0063D362 /* MYSymmetricKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYSymmetricKey.h; sourceTree = "<group>"; }; 27AAD9710F8927DB0064DD7C /* MYCryptoConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCryptoConfig.h; sourceTree = "<group>"; }; + 27E3A6A60F91B8B30079D4D9 /* MYCryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCryptor.h; sourceTree = "<group>"; }; + 27E3A6A70F91B8B30079D4D9 /* MYCryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYCryptor.m; sourceTree = "<group>"; }; 27E8230C0F81D56E0019BE60 /* MYCertificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCertificate.h; sourceTree = "<group>"; }; - 27E8230E0F81D56E0019BE60 /* MYCryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYCryptor.h; sourceTree = "<group>"; }; - 27E8230F0F81D56E0019BE60 /* MYCryptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYCryptor.m; sourceTree = "<group>"; }; 27E823100F81D56E0019BE60 /* MYKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYKey.h; sourceTree = "<group>"; }; 27E823110F81D56E0019BE60 /* MYKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYKey.m; sourceTree = "<group>"; }; 27E823120F81D56E0019BE60 /* MYKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYKeychain.h; sourceTree = "<group>"; }; @@ -125,6 +125,15 @@ name = Products; sourceTree = "<group>"; }; + 27E3A6A10F91B8B30079D4D9 /* Encryption */ = { + isa = PBXGroup; + children = ( + 27E3A6A60F91B8B30079D4D9 /* MYCryptor.h */, + 27E3A6A70F91B8B30079D4D9 /* MYCryptor.m */, + ); + name = Encryption; + sourceTree = "<group>"; + }; 27E8230B0F81D56E0019BE60 /* Source */ = { isa = PBXGroup; children = ( @@ -132,8 +141,6 @@ 27E8230C0F81D56E0019BE60 /* MYCertificate.h */, 276FB34A0F856CA400CB326E /* MYCertificate.m */, 273391CC0F81EC70009414D9 /* MYCertificate-iPhone.m */, - 27E8230E0F81D56E0019BE60 /* MYCryptor.h */, - 27E8230F0F81D56E0019BE60 /* MYCryptor.m */, 27059CF10F8F0F8E00A8422F /* MYIdentity.h */, 27059CF20F8F0F9200A8422F /* MYIdentity.m */, 27E823100F81D56E0019BE60 /* MYKey.h */, @@ -182,6 +189,7 @@ isa = PBXGroup; children = ( 27E8230B0F81D56E0019BE60 /* Source */, + 27E3A6A10F91B8B30079D4D9 /* Encryption */, 27E8232B0F81D5760019BE60 /* MYUtilities */, 080E96DDFE201D6D7F000001 /* iPhone */, 29B97323FDCFA39411CA2CEA /* Frameworks */, @@ -259,7 +267,6 @@ files = ( 1D60589B0D05DD56006BFB54 /* MYCrypto_iPhone_main.m in Sources */, 1D3623260D0F684500981E51 /* MYCrypto_iPhoneAppDelegate.m in Sources */, - 27E823210F81D56E0019BE60 /* MYCryptor.m in Sources */, 27E823240F81D56E0019BE60 /* MYKeychainItem.m in Sources */, 27E823270F81D56E0019BE60 /* MYDigest.m in Sources */, 27E823370F81D5760019BE60 /* CollectionUtils.m in Sources */, @@ -278,6 +285,7 @@ 27FE453F0F87CC8500A86D63 /* MYKey-iPhone.m in Sources */, 2748607F0F8D5E0600FE617B /* MYPrivateKey.m in Sources */, 27059CF30F8F0F9200A8422F /* MYIdentity.m in Sources */, + 27E3A6AA0F91B8B30079D4D9 /* MYCryptor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r dee779b84a95 -r 4c0eafa7b233 MYCrypto.xcodeproj/project.pbxproj --- a/MYCrypto.xcodeproj/project.pbxproj Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCrypto.xcodeproj/project.pbxproj Sun Apr 12 22:02:20 2009 -0700 @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 27059D530F8F9BB500A8422F /* MYEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 27059D500F8F9BB500A8422F /* MYEncoder.m */; }; + 27059D770F8FA23100A8422F /* MYCrypto+Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = 27059D760F8FA23100A8422F /* MYCrypto+Cocoa.m */; }; + 27059D840F8FA82500A8422F /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27059D830F8FA82500A8422F /* SecurityInterface.framework */; }; + 27059DE50F8FAF6500A8422F /* MYDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 27059D520F8F9BB500A8422F /* MYDecoder.m */; }; 270B879F0F8C565000C56781 /* MYPrivateKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 270B879E0F8C565000C56781 /* MYPrivateKey.m */; }; 274861D50F8E4B5200FE617B /* MYCertGen.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A42ECD0F8689D30063D362 /* MYCertGen.m */; }; 274863A20F8EF39400FE617B /* MYIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = 274863A10F8EF39400FE617B /* MYIdentity.m */; }; @@ -44,6 +48,13 @@ /* Begin PBXFileReference section */ 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; + 27059D4F0F8F9BB500A8422F /* MYEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYEncoder.h; sourceTree = "<group>"; }; + 27059D500F8F9BB500A8422F /* MYEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYEncoder.m; sourceTree = "<group>"; }; + 27059D510F8F9BB500A8422F /* MYDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYDecoder.h; sourceTree = "<group>"; }; + 27059D520F8F9BB500A8422F /* MYDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYDecoder.m; sourceTree = "<group>"; }; + 27059D750F8FA23100A8422F /* MYCrypto+Cocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MYCrypto+Cocoa.h"; sourceTree = "<group>"; }; + 27059D760F8FA23100A8422F /* MYCrypto+Cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MYCrypto+Cocoa.m"; sourceTree = "<group>"; }; + 27059D830F8FA82500A8422F /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = /System/Library/Frameworks/SecurityInterface.framework; sourceTree = "<absolute>"; }; 270B879D0F8C565000C56781 /* MYPrivateKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYPrivateKey.h; sourceTree = "<group>"; }; 270B879E0F8C565000C56781 /* MYPrivateKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYPrivateKey.m; sourceTree = "<group>"; }; 2748604D0F8D5C4C00FE617B /* MYCrypto_Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = MYCrypto_Release.xcconfig; sourceTree = "<group>"; }; @@ -98,6 +109,7 @@ 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, 27CFF51F0F7E94AE000B418E /* Security.framework in Frameworks */, 27E820720F7EA6260019BE60 /* CoreServices.framework in Frameworks */, + 27059D840F8FA82500A8422F /* SecurityInterface.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,6 +120,7 @@ isa = PBXGroup; children = ( 08FB7795FE84155DC02AAC07 /* Source */, + 27059D4E0F8F9B9E00A8422F /* Encryption */, 274861440F8D757600FE617B /* Certificate Generation */, 270B881C0F8D055A00C56781 /* Internal */, 27CFF4CC0F7E86E8000B418E /* MYUtilities */, @@ -116,6 +129,7 @@ 1AB674ADFE9D54B511CA2CBB /* Products */, 27CFF51E0F7E94AE000B418E /* Security.framework */, 27E820710F7EA6260019BE60 /* CoreServices.framework */, + 27059D830F8FA82500A8422F /* SecurityInterface.framework */, ); name = MYCrypto; sourceTree = "<group>"; @@ -125,8 +139,6 @@ children = ( 27CFF4B10F7E8535000B418E /* MYCertificate.h */, 27CFF4B20F7E8535000B418E /* MYCertificate.m */, - 27CFF4B30F7E8535000B418E /* MYCryptor.h */, - 27CFF4B40F7E8535000B418E /* MYCryptor.m */, 27CFF4BF0F7E8535000B418E /* MYDigest.h */, 27CFF4C00F7E8535000B418E /* MYDigest.m */, 274863A00F8EF39400FE617B /* MYIdentity.h */, @@ -145,6 +157,8 @@ 27A42D410F858ED80063D362 /* MYSymmetricKey.m */, 27AAD97B0F892A0D0064DD7C /* MYCryptoConfig.h */, 27EAF0390F8B2D700091AF95 /* README.textile */, + 27059D750F8FA23100A8422F /* MYCrypto+Cocoa.h */, + 27059D760F8FA23100A8422F /* MYCrypto+Cocoa.m */, ); name = Source; sourceTree = "<group>"; @@ -165,6 +179,19 @@ name = Products; sourceTree = "<group>"; }; + 27059D4E0F8F9B9E00A8422F /* Encryption */ = { + isa = PBXGroup; + children = ( + 27059D4F0F8F9BB500A8422F /* MYEncoder.h */, + 27059D500F8F9BB500A8422F /* MYEncoder.m */, + 27059D510F8F9BB500A8422F /* MYDecoder.h */, + 27059D520F8F9BB500A8422F /* MYDecoder.m */, + 27CFF4B30F7E8535000B418E /* MYCryptor.h */, + 27CFF4B40F7E8535000B418E /* MYCryptor.m */, + ); + name = Encryption; + sourceTree = "<group>"; + }; 270B881C0F8D055A00C56781 /* Internal */ = { isa = PBXGroup; children = ( @@ -263,7 +290,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/csh; - shellScript = "doxygen\n"; + shellScript = "doxygen |& sed s/Warning/warning/\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -291,6 +318,9 @@ 270B879F0F8C565000C56781 /* MYPrivateKey.m in Sources */, 274861D50F8E4B5200FE617B /* MYCertGen.m in Sources */, 274863A20F8EF39400FE617B /* MYIdentity.m in Sources */, + 27059D530F8F9BB500A8422F /* MYEncoder.m in Sources */, + 27059D770F8FA23100A8422F /* MYCrypto+Cocoa.m in Sources */, + 27059DE50F8FAF6500A8422F /* MYDecoder.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff -r dee779b84a95 -r 4c0eafa7b233 MYCryptoTest.m --- a/MYCryptoTest.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCryptoTest.m Sun Apr 12 22:02:20 2009 -0700 @@ -11,6 +11,7 @@ #import "MYKeychain.h" #import "MYDigest.h" #import "MYIdentity.h" +#import "MYCrypto+Cocoa.h" #import "MYCrypto_Private.h" @@ -78,7 +79,7 @@ Log(@"Enumerator = %@", e); CAssert(e); for (MYIdentity *ident in e) { - Log(@"Found %@ -- name=%@, emails=(%@), key=%@", + Log(@"Found %@\n\tcommonName=%@\n\temails=(%@)\n\tkey=%@", ident, ident.commonName, #if TARGET_OS_IPHONE nil, @@ -172,33 +173,63 @@ #pragma mark KEY-PAIRS: -TestCase(MYPrivateKey) { - RequireTestCase(MYKeychain); - - Log(@"Generating key pair..."); - MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512]; - Log(@"...created { %@ , %@ }.", pair, pair.publicKey); +static void TestUseKeyPair(MYPrivateKey *pair) { + Log(@"---- TestUseKeyPair { %@ , %@ }.", pair, pair.publicKey); CAssert(pair); CAssert(pair.keyRef); MYPublicKey *publicKey = pair.publicKey; CAssert(publicKey.keyRef); + NSData *pubKeyData = publicKey.keyData; + Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length); + CAssert(pubKeyData); + + MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest; + Log(@"Public key digest = %@",pubKeyDigest); + CAssertEqual(pair.publicKeyDigest, pubKeyDigest); + + Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData); + + // Let's sign data: + NSData *data = [@"This is a test. This is only a test!" dataUsingEncoding: NSUTF8StringEncoding]; + NSData *sig = [pair signData: data]; + Log(@"Signature = %@ (%u bytes)",sig,sig.length); + CAssert(sig); + CAssert( [publicKey verifySignature: sig ofData: data] ); + + // Now let's encrypt... + NSData *crypted = [publicKey encryptData: data]; + Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length); + CAssert(crypted); + CAssertEqual([pair decryptData: crypted], data); + Log(@"Verified decryption."); + + // Test creating a standalone public key: + MYPublicKey *pub = [[MYPublicKey alloc] initWithKeyRef: publicKey.keyRef]; + CAssert( [pub verifySignature: sig ofData: data] ); + Log(@"Verified signature."); + + // Test creating a public key from data: + Log(@"Reconstituting public key from data..."); + pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData]; + CAssert(pub); + CAssertEqual(pub.keyData, pubKeyData); + CAssertEqual(pub.publicKeyDigest, pubKeyDigest); + CAssert( [pub verifySignature: sig ofData: data] ); + Log(@"Verified signature from reconstituted key."); +} + + +TestCase(MYGenerateKeyPair) { + RequireTestCase(MYKeychain); + + Log(@"Generating key pair..."); + MYPrivateKey *pair = [[MYKeychain defaultKeychain] generateRSAKeyPairOfSize: 512]; + MYPublicKey *publicKey = pair.publicKey; + Log(@"...created { %@ , %@ }.", pair, publicKey); + @try{ - NSData *pubKeyData = publicKey.keyData; - Log(@"Public key = %@ (%u bytes)",pubKeyData,pubKeyData.length); - CAssert(pubKeyData); - - MYSHA1Digest *pubKeyDigest = publicKey.publicKeyDigest; - Log(@"Public key digest = %@",pubKeyDigest); - CAssertEqual(pair.publicKeyDigest, pubKeyDigest); - - Log(@"SHA1 of pub key = %@", pubKeyData.my_SHA1Digest.asData); - - NSData *data = [@"This is a test. This is only a test!" dataUsingEncoding: NSUTF8StringEncoding]; - NSData *sig = [pair signData: data]; - Log(@"Signature = %@ (%u bytes)",sig,sig.length); - CAssert(sig); - CAssert( [publicKey verifySignature: sig ofData: data] ); + TestUseKeyPair(pair); [pair setName: @"Test KeyPair Label"]; CAssertEqual(pair.name, @"Test KeyPair Label"); @@ -212,28 +243,6 @@ CAssertEqual(pair.alias, @"TestCase@mooseyard.com"); CAssertEqual(publicKey.alias, @"TestCase@mooseyard.com"); - // Test creating a standalone public key: - MYPublicKey *pub = [[MYPublicKey alloc] initWithKeyRef: publicKey.keyRef]; - CAssert( [pub verifySignature: sig ofData: data] ); - Log(@"Verified signature."); - - // Test creating a public key from data: - Log(@"Reconstituting public key from data..."); - pub = [[MYPublicKey alloc] initWithKeyData: pubKeyData]; - CAssert(pub); - CAssertEqual(pub.keyData, pubKeyData); - CAssertEqual(pub.publicKeyDigest, pubKeyDigest); - CAssert( [pub verifySignature: sig ofData: data] ); - Log(@"Verified signature from reconstituted key."); - - // Now let's encrypt... - NSData *crypted = [pub encryptData: data]; - Log(@"Encrypted = %@ (%u bytes)",crypted,crypted.length); - CAssert(crypted); - - CAssertEqual([pair decryptData: crypted], data); - Log(@"Verified decryption."); - CAssert([pair removeFromKeychain]); Log(@"Removed key-pair."); pair = nil; @@ -249,6 +258,24 @@ } +TestCase(MYUseIdentity) { + MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"]; + if (!me) { + NSArray *idents = [[[MYKeychain allKeychains] enumerateIdentities] allObjects]; + SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel]; + [panel setAlternateButtonTitle: @"Cancel"]; + if ([panel my_runModalForIdentities: idents + message: @"Choose an identity for the MYEncoder test case:"] + != NSOKButton) { + [NSException raise: NSGenericException format: @"User canceled"]; + } + me = [panel my_identity]; + [me makePreferredIdentityForName: @"MYCryptoTest"]; + } + CAssert(me,@"No default identity has been set up in the Keychain"); + TestUseKeyPair(me.privateKey); +} + #pragma mark - #pragma mark KEYPAIR EXPORT: diff -r dee779b84a95 -r 4c0eafa7b233 MYCrypto_Private.h --- a/MYCrypto_Private.h Thu Apr 09 22:47:11 2009 -0700 +++ b/MYCrypto_Private.h Sun Apr 12 22:02:20 2009 -0700 @@ -114,6 +114,8 @@ #undef check BOOL check(OSStatus err, NSString *what); +#define checksave(CALL) ({OSStatus err=(CALL); check(err,@""#CALL) || (_error=err, NO);}) + #if !MYCRYPTO_USE_IPHONE_API BOOL checkcssm(CSSM_RETURN err, NSString *what); @@ -121,4 +123,7 @@ SecExternalItemType type, SecKeychainRef keychain, SecKeyImportExportParameters *params /*non-null*/); + +NSString* OIDAsString(CSSM_OID OID); + #endif diff -r dee779b84a95 -r 4c0eafa7b233 MYDecoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYDecoder.h Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,170 @@ +// +// CryptoDecoder.h +// Cloudy +// +// Created by Jens Alfke on 1/16/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import <Security/CMSDecoder.h> + +@class MYKeychain, MYCertificate; + + +/** Decodes a CMS-formatted message into the original data, and identifies & verifies signatures. */ +@interface MYDecoder : NSObject +{ + @private + CMSDecoderRef _decoder; + OSStatus _error; + SecPolicyRef _policy; +} + +/** Initializes a new decoder. */ +- (id) init; + +/** Initializes a new decoder and reads the entire message data. */ +- (id) initWithData: (NSData*)data error: (NSError**)outError; + +/** Specifies a keychain to use to look up certificates and keys, instead of the default + keychain search path. */ +- (BOOL) useKeychain: (MYKeychain*)keychain; + +/** Adds data to the decoder. You can add the entire data at once, or in bits and pieces + (if you're reading it from a stream). */ +- (BOOL) addData: (NSData*)data; + +/** The error, if any, that occurred while decoding the content. + If -addData: returns NO, read this property to find out what went wrong. + The most likely error is (NSOSStatusErrorDomain, errSecUnknownFormat). */ +@property (readonly) NSError *error; + +/** If the message content is detached (stored separately from the encoded message), + you must copy it to this property before calling -finish, so that the decoder can use it + to verify signatures. */ +@property (retain) NSData *detachedContent; + +/** Tells the decoder that all of the data has been read, after the last call to -addData:. + You must call this before accessing the message content or metadata. */ +- (BOOL) finish; + +/** The decoded message content. */ +@property (readonly) NSData* content; + +/** YES if the message was signed. (Use the signers property to see who signed it.) */ +@property (readonly) BOOL isSigned; + +/** YES if the message was encrypted. */ +@property (readonly) BOOL isEncrypted; + +/** An array of MYSigner objects representing the identities who signed the message. + Nil if the message is unsigned. */ +@property (readonly) NSArray* signers; + +/** All of the certificates (as MYCertificate objects) that were attached to the message. */ +@property (readonly) NSArray* certificates; + + +/** @name Expert + * Advanced methods. + */ +//@{ + +/** The X.509 content-type of the message contents. + The Data field points to autoreleased memory: do not free it yourself, and do not + expect it to remain valid after the calling method returns. */ +@property (readonly) CSSM_OID contentType; + +/** The Policy that will be used to evaluate trust when calling MYSigner.copyTrust. + NULL by default. */ +@property (assign) SecPolicyRef policy; + +/** Returns a string with detailed information about the message metadata. + Not user-presentable; used for debugging. */ +- (NSString*) dump; + +//@} + +@end + + +/** Represents a signer of a CMS message, as returned by the MYDecoder.signers property. */ +@interface MYSigner : NSObject +{ + @private + CMSDecoderRef _decoder; + size_t _index; + CFTypeRef _policy; + CMSSignerStatus _status; + OSStatus _verifyResult; + SecTrustRef _trust; +} + +/** The status of the signature, i.e. whether it's valid or not. + * Values include: + * kCMSSignerValid :both signature and signer certificate verified OK. + * kCMSSignerNeedsDetachedContent:the MYDecoder's detachedContent property must be set, + * to ascertain the signature status. + * kCMSSignerInvalidSignature :bad signature -- either the content or the signature + * data were tampered with after the message was encoded. + * kCMSSignerInvalidCert :an error occurred verifying the signer's certificate. + * Further information available via the verifyResult + * and copyTrust methods. + */ +@property (readonly) CMSSignerStatus status; + +/** The signer's certificate. + You should check the status property first, to see whether the signature and certificate + are valid. + For safety purposes, if you haven't checked status yet, this method will return nil + if the signer status is not kCMSSignerValid. */ +@property (readonly) MYCertificate *certificate; + +/** The signer's email address (if any), as stored in the certificate. */ +@property (readonly) NSString* emailAddress; + +/** @name Expert + * Advanced methods. + */ +//@{ + +/** Returns the SecTrustRef that was used to verify the certificate. + You can use this object to get more detailed information about how the verification was done. + If you set the parent decoder's policy property, then that SecPolicy will be used to evaluate + trust; otherwise you'll need to do it yourself using the SecTrust object. + You must CFRelease the result when you're finished with it. */ +- (SecTrustRef) trust; + +/** The result of certificate verification, as a CSSM_RESULT code; + * a nonzero value indicates an error. + * + * Some of the most common and interesting errors are: + * + * CSSMERR_TP_INVALID_ANCHOR_CERT : The cert was verified back to a + * self-signed (root) cert which was present in the message, but + * that root cert is not a known, trusted root cert. + * CSSMERR_TP_NOT_TRUSTED: The cert could not be verified back to + * a root cert. + * CSSMERR_TP_VERIFICATION_FAILURE: A root cert was found which does + * not self-verify. + * CSSMERR_TP_VERIFY_ACTION_FAILED: Indicates a failure of the requested + * policy action. + * CSSMERR_TP_INVALID_CERTIFICATE: Indicates a bad leaf cert. + * CSSMERR_TP_CERT_EXPIRED: A cert in the chain was expired at the time of + * verification. + * CSSMERR_TP_CERT_NOT_VALID_YET: A cert in the chain was not yet valie at + * the time of verification. + */ +@property (readonly) OSStatus verifyResult; + +//@} + +@end + + +enum { + /** Returned from MYSigner.status to indicate a failure (non-noErr return value) + of the underlying CMSDecoderCopySignerStatus call. Should never occur. */ + kMYSignerStatusCheckFailed = 666 +}; diff -r dee779b84a95 -r 4c0eafa7b233 MYDecoder.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYDecoder.m Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,389 @@ +// +// MYDecoder.m +// Cloudy +// +// Created by Jens Alfke on 1/16/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "MYDecoder.h" +#import "MYCrypto_Private.h" +#import "Test.h" +#import "MYErrorUtils.h" + + +@interface MYSigner () +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy; +@end + + +@implementation MYDecoder + + +- (id) initWithData: (NSData*)data error: (NSError**)outError +{ + self = [self init]; + if( self ) { + [self addData: data]; + [self finish]; + *outError = self.error; + if( *outError ) { + [self release]; + return nil; + } + } + return self; +} + +- (id) init +{ + self = [super init]; + if (self != nil) { + OSStatus err = CMSDecoderCreate(&_decoder); + if( err ) { + [self release]; + self = nil; + } + } + return self; +} + +- (void) dealloc +{ + if( _decoder ) CFRelease(_decoder); + if (_policy) CFRelease(_policy); + [super dealloc]; +} + + +- (BOOL) addData: (NSData*)data +{ + Assert(data); + return !_error && checksave( CMSDecoderUpdateMessage(_decoder, data.bytes, data.length) ); +} + + +- (BOOL) finish +{ + return !_error && checksave( CMSDecoderFinalizeMessage(_decoder) ); +} + +- (NSError*) error +{ + if( _error ) + return MYError(_error, NSOSStatusErrorDomain, + @"%@", MYErrorName(NSOSStatusErrorDomain,_error)); + else + return nil; +} + +- (BOOL) useKeychain: (MYKeychain*)keychain +{ + return !_error && checksave( CMSDecoderSetSearchKeychain(_decoder, keychain.keychainRef) ); +} + + +@synthesize policy=_policy; + + +- (NSData*) _dataFromFunction: (OSStatus (*)(CMSDecoderRef,CFDataRef*))function +{ + CFDataRef data=NULL; + if( checksave( (*function)(_decoder, &data) ) ) + return [(NSData*)CFMakeCollectable(data) autorelease]; + else + return nil; +} + + +- (NSData*) detachedContent +{ + return [self _dataFromFunction: &CMSDecoderCopyDetachedContent]; +} + +- (void) setDetachedContent: (NSData*)detachedContent +{ + if( ! _error ) + checksave( CMSDecoderSetDetachedContent(_decoder, (CFDataRef)detachedContent) ); +} + +- (CSSM_OID) contentType +{ + NSData *data = [self _dataFromFunction: &CMSDecoderCopyEncapsulatedContentType]; + return (CSSM_OID){data.length,(uint8*)data.bytes}; // safe since data is autoreleased +} + +- (NSData*) content +{ + return [self _dataFromFunction: &CMSDecoderCopyContent]; +} + +- (BOOL) isSigned +{ + size_t n; + return checksave( CMSDecoderGetNumSigners(_decoder, &n) ) && n > 0; +} + +- (BOOL) isEncrypted +{ + Boolean isEncrypted; + return check(CMSDecoderIsContentEncrypted(_decoder,&isEncrypted), @"CMSDecoderIsContentEncrypted") + && isEncrypted; +} + +- (NSArray*) signers +{ + size_t n; + if( ! checksave( CMSDecoderGetNumSigners(_decoder, &n) ) ) + return nil; + NSMutableArray *signers = [NSMutableArray arrayWithCapacity: n]; + for( size_t i=0; i<n; i++ ) { + MYSigner *signer = [[MYSigner alloc] initWithDecoder: _decoder + index: i + policy: _policy]; + [signers addObject: signer]; + [signer release]; + } + return signers; +} + + +- (NSArray*) certificates +{ + CFArrayRef certRefs = NULL; + if( ! checksave( CMSDecoderCopyAllCerts(_decoder, &certRefs) ) || ! certRefs ) + return nil; + unsigned n = CFArrayGetCount(certRefs); + NSMutableArray *certs = [NSMutableArray arrayWithCapacity: n]; + for( unsigned i=0; i<n; i++ ) { + SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(certRefs, i); + [certs addObject: [MYCertificate certificateWithCertificateRef: certRef]]; + } + CFRelease(certRefs); + return certs; +} + + +- (NSString*) dump +{ + static const char * kStatusNames[kCMSSignerInvalidIndex+1] = { + "kCMSSignerUnsigned", "kCMSSignerValid", "kCMSSignerNeedsDetachedContent", + "kCMSSignerInvalidSignature","kCMSSignerInvalidCert","kCMSSignerInvalidIndex"}; + + CSSM_OID contentType = self.contentType; + NSMutableString *s = [NSMutableString stringWithFormat: @"%@<%p>:\n" + "\tcontentType = %@ (\"%@\")\n" + "\tcontent = %u bytes\n" + "\tsigned=%i, encrypted=%i\n" + "\tpolicy=%@\n" + "\t%u certificates\n" + "\tsigners =\n", + self.class, self, + OIDAsString(contentType), @"??"/*nameOfOID(&contentType)*/, + self.content.length, + self.isSigned,self.isEncrypted, + MYPolicyGetName(_policy), + self.certificates.count]; + for( MYSigner *signer in self.signers ) { + CMSSignerStatus status = signer.status; + const char *statusName = (status<=kCMSSignerInvalidIndex) ?kStatusNames[status] :"??"; + [s appendFormat:@"\t\t- status = %s\n" + "\t\t verifyResult = %@\n" + "\t\t trust = %@\n" + "\t\t cert = %@\n", + statusName, + (signer.verifyResult ?MYErrorName(NSOSStatusErrorDomain,signer.verifyResult) + :@"OK"), + MYTrustDescribe(signer.trust), + signer.certificate]; + } + return s; +} + + +@end + + + +#pragma mark - +@implementation MYSigner : NSObject + +#define kUncheckedStatus ((CMSSignerStatus)-1) + +- (id) initWithDecoder: (CMSDecoderRef)decoder index: (size_t)index policy: (CFTypeRef)policy +{ + self = [super init]; + if( self ) { + CFRetain(decoder); + _decoder = decoder; + _index = index; + if(policy) _policy = CFRetain(policy); + _status = kUncheckedStatus; + } + return self; +} + +- (void) dealloc +{ + if(_decoder) CFRelease(_decoder); + if(_policy) CFRelease(_policy); + if(_trust) CFRelease(_trust); + [super dealloc]; +} + +- (void) _getInfo { + if (_status == kUncheckedStatus) { + if( !check(CMSDecoderCopySignerStatus(_decoder, _index, _policy, (_policy!=nil), + &_status, &_trust, &_verifyResult), + @"CMSDecoderCopySignerStatus")) + _status = kMYSignerStatusCheckFailed; + } +} + +- (CMSSignerStatus) status +{ + [self _getInfo]; + return _status; +} + +- (OSStatus) verifyResult +{ + [self _getInfo]; + return _verifyResult; +} + +- (SecTrustRef) trust +{ + [self _getInfo]; + return _trust; +} + + +- (NSString*) emailAddress +{ + // Don't let caller see the addr if they haven't checked validity & the signature's invalid: + if (_status==kUncheckedStatus && self.status != kCMSSignerValid) + return nil; + + CFStringRef email=NULL; + if( CMSDecoderCopySignerEmailAddress(_decoder, _index, &email) == noErr ) + return [(NSString*)CFMakeCollectable(email) autorelease]; + return nil; +} + +- (MYCertificate *) certificate +{ + // Don't let caller see the cert if they haven't checked validity & the signature's invalid: + if (_status==kUncheckedStatus && self.status != kCMSSignerValid) + return nil; + + SecCertificateRef certRef=NULL; + OSStatus err = CMSDecoderCopySignerCert(_decoder, _index, &certRef); + if( err == noErr ) + return [MYCertificate certificateWithCertificateRef: certRef]; + else { + Warn(@"CMSDecoderCopySignerCert returned err %i",err); + return nil; + } +} + + +- (NSString*) description +{ + NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[st=%i", self.class,(int)self.status]; + int verify = self.verifyResult; + if( verify ) + [desc appendFormat: @"; verify error %i",verify]; + else { + MYCertificate *cert = self.certificate; + if( cert ) + [desc appendFormat: @"; %@",cert.commonName]; + } + [desc appendString: @"]"]; + return desc; +} + + +@end + + + +// Taken from Keychain.framework +NSString* OIDAsString(const CSSM_OID oid) { + if ((NULL == oid.Data) || (0 >= oid.Length)) { + return nil; + } else { + NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)]; + unsigned int i; + + for (i = 0; i < oid.Length; ++i) { + [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]]; + } + + return result; + } +} + + + +#pragma mark - +#pragma mark TEST CASE: + + +#if DEBUG + +#import "MYEncoder.h" +#import "MYIdentity.h" + +static void TestRoundTrip( NSString *title, NSData *source, MYIdentity *signer, MYCertificate *recipient ) +{ + Log(@"Testing MYEncoder/Decoder %@...",title); + NSError *error; + NSData *encoded = [MYEncoder encodeData: source signer: signer recipient: recipient error: &error]; + CAssertEq(error,nil); + CAssert([encoded length]); + Log(@"MYEncoder encoded %u bytes into %u bytes", source.length,encoded.length); + Log(@"Decoding..."); + MYDecoder *d = [[MYDecoder alloc] init]; + d.policy = [MYCertificate X509Policy]; + [d addData: encoded]; + [d finish]; + + CAssertEq(d.error,nil); + Log(@"%@", d.dump); + CAssert(d.content); + CAssert([d.content isEqual: source]); + CAssertEq(d.detachedContent,nil); + CAssertEq(d.isSigned,(signer!=nil)); + CAssertEq(d.isEncrypted,(recipient!=nil)); + + if( signer ) { + CAssert(d.certificates.count >= 1); // may include extra parent certs + CAssertEq(d.signers.count,1U); + MYSigner *outSigner = [d.signers objectAtIndex: 0]; + CAssertEq(outSigner.status,(CMSSignerStatus)kCMSSignerValid); + CAssertEq(outSigner.verifyResult,noErr); + CAssert([outSigner.certificate isEqualToCertificate: signer]); + } else { + CAssertEq(d.certificates.count, 0U); + CAssertEq(d.signers.count,0U); + } +} + + +TestCase(MYDecoder) { + RequireTestCase(MYEncoder); + + MYIdentity *me = [MYIdentity preferredIdentityForName: @"MYCryptoTest"]; + CAssert(me,@"No default identity has been set up in the Keychain"); + Log(@"Using %@", me); + + NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"]; + CAssert(source); + + TestRoundTrip(@"signing", source, me, nil); + TestRoundTrip(@"encryption", source, nil, me); + TestRoundTrip(@"signing+encryption", source, me, me); +} + +#endif DEBUG diff -r dee779b84a95 -r 4c0eafa7b233 MYEncoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYEncoder.h Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,90 @@ +// +// MYEncoder.h +// MYCrypto +// +// Created by Jens Alfke on 1/16/08. +// Copyright 2008-2009 Jens Alfke. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <Security/CMSEncoder.h> + +@class MYIdentity, MYCertificate; + + +/** Creates a CMS-formatted message from a blob of data; it can be signed and/or encrypted. */ +@interface MYEncoder : NSObject +{ + @private + CMSEncoderRef _encoder; + OSStatus _error; +} + +/** A convenience method for one-shot encoding of a block of data. + @param data The data that will be signed/encrypted. + @param signerOrNil If non-nil, an Identity whose private key will sign the data. + @param recipientOrNil If non-nil, the data will be encrypted so only the owner of this + certificate can read it. + @param outError On return, will be set to an NSError if something went wrong. + @return The encoded data. */ ++ (NSData*) encodeData: (NSData*)data + signer: (MYIdentity*)signerOrNil + recipient: (MYCertificate*)recipientOrNil + error: (NSError**)outError; + +/** Initializes a new encoder. + You must add at least one signer or recipient. */ +- (id) init; + +/** Tells the encoder to sign the content with this identity's private key. + (Multiple signers can be added, but this is rare.) */ +- (BOOL) addSigner: (MYIdentity*)signer; + +/** Tells the encoder to encrypt the content with this recipient's public key. + Multiple recipients can be added; any one of them will be able to decrypt the message. */ +- (BOOL) addRecipient: (MYCertificate*)recipient; + +/** The current error status of the encoder. + If something goes wrong with an operation, it will return NO, + and this property will contain the error. */ +@property (readonly) NSError* error; + +/** Setting this property to YES tells the encoder not to copy the content itself into the + encoded message. The encodedData property will then contain only metadata, such as + signatures and certificates. + This is useful if you're working with a data format that already specifies a content + format: it allows you to attach the encoded data elsewhere, e.g. in a header or metadata + attribute. */ +@property BOOL hasDetachedContent; + +/** Adds data to the encoder. You can add the entire data at once, or in bits and pieces + (if you're reading it from a stream). */ +- (BOOL) addData: (NSData*)data; + +/** The signed/encoded output data. + Don't call this until after the last call to -addData:. */ +- (NSData*) encodedData; + + +/** @name Expert + * Advanced methods. + */ +//@{ + +/** Adds a timestamp showing when the message was encoded. + [Unfortunately there is no system API for reading these timestamps in decoded messages...] */ +- (BOOL) addTimestamp; + +/** Specifies which certificates to include in the message: none, only the signer certs, + or the signer certs' entire chain (the default). */ +@property CMSCertificateChainMode certificateChainMode; + +/** Adds an extra certificate to the encoded data, for the recipient's use. Rarely needed. */ +- (BOOL) addSupportingCert: (MYCertificate*)supportingCert; + +/** The X.509 content type of the message data. */ +@property CSSM_OID contentType; + +//@} + +@end diff -r dee779b84a95 -r 4c0eafa7b233 MYEncoder.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYEncoder.m Sun Apr 12 22:02:20 2009 -0700 @@ -0,0 +1,201 @@ +// +// MYEncoder.m +// MYCrypto +// +// Created by Jens Alfke on 1/16/08. +// Copyright 2008-2009 Jens Alfke. All rights reserved. +// + +#import "MYEncoder.h" +#import "MYIdentity.h" +#import "MYCrypto_Private.h" +#import "Test.h" +#import "MYErrorUtils.h" + + +@implementation MYEncoder + + +- (id) init +{ + self = [super init]; + if (self != nil) { + if( ! checksave(CMSEncoderCreate(&_encoder)) ) { + [self release]; + return nil; + } + } + return self; +} + +- (void) dealloc +{ + if(_encoder) CFRelease(_encoder); + [super dealloc]; +} + + + +- (BOOL) addSigner: (MYIdentity*)signer +{ + Assert(signer); + return checksave( CMSEncoderAddSigners(_encoder, signer.identityRef) ); +} + +- (BOOL) addRecipient: (MYCertificate*)recipient +{ + Assert(recipient); + return checksave( CMSEncoderAddRecipients(_encoder, recipient.certificateRef) ); +} + +- (BOOL) addSupportingCert: (MYCertificate*)supportingCert +{ + Assert(supportingCert); + return checksave( CMSEncoderAddSupportingCerts(_encoder, supportingCert.certificateRef) ); +} + +- (BOOL) addTimestamp +{ + return checksave( CMSEncoderAddSignedAttributes(_encoder, kCMSAttrSigningTime) ); +} + + +- (NSError*) error +{ + if( _error ) + return MYError(_error, NSOSStatusErrorDomain, + @"%@", MYErrorName(NSOSStatusErrorDomain,_error)); + else + return nil; +} + + +- (CMSCertificateChainMode) certificateChainMode +{ + CMSCertificateChainMode mode; + if( CMSEncoderGetCertificateChainMode(_encoder, &mode) == noErr ) + return mode; + else + return -1; +} + +- (void) setCertificateChainMode: (CMSCertificateChainMode)mode +{ + checksave( CMSEncoderSetCertificateChainMode(_encoder, mode) ); +} + +- (BOOL) hasDetachedContent +{ + Boolean detached; + return CMSEncoderGetHasDetachedContent(_encoder, &detached)==noErr && detached; +} + +- (void) setHasDetachedContent: (BOOL)detached +{ + checksave( CMSEncoderSetHasDetachedContent(_encoder, detached) ); +} + +- (NSData*) _dataFromFunction: (OSStatus (*)(CMSEncoderRef,CFDataRef*))function +{ + CFDataRef data=NULL; + if( checksave( (*function)(_encoder, &data) ) ) + return [(NSData*)CFMakeCollectable(data) autorelease]; + else + return nil; +} + + +- (CSSM_OID) contentType +{ + NSData *data = [self _dataFromFunction: &CMSEncoderCopyEncapsulatedContentType]; + return (CSSM_OID){data.length,(uint8*)data.bytes}; +} + +- (void) setContentType: (CSSM_OID)contentType +{ + checksave( CMSEncoderSetEncapsulatedContentType(_encoder, &contentType) ); +} + + +- (BOOL) addData: (NSData*)data +{ + Assert(data); + return ! _error && checksave( CMSEncoderUpdateContent(_encoder, data.bytes, data.length) ); +} + + +- (NSData*) encodedData +{ + if( ! _error ) + return [self _dataFromFunction: &CMSEncoderCopyEncodedContent]; + else + return nil; +} + + ++ (NSData*) encodeData: (NSData*)data + signer: (MYIdentity*)signer + recipient: (MYCertificate*)recipient + error: (NSError**)outError +{ + MYEncoder *e = [[self alloc] init]; + if( signer ) + [e addSigner: signer]; + if( recipient ) + [e addRecipient: recipient]; + [e addData: data]; + *outError = e.error; + NSData *result = e.encodedData; + [e release]; + return result; +} + + +@end + + +#if DEBUG + +#import "MYCrypto+Cocoa.h" + +TestCase(MYEncoder) { + MYIdentity *me = nil;//[MYIdentity preferredIdentityForName: @"MYCryptoTest"]; + if (!me) { + NSArray *idents = [[[MYKeychain allKeychains] enumerateIdentities] allObjects]; + SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel]; + [panel setAlternateButtonTitle: @"Cancel"]; + if ([panel my_runModalForIdentities: idents + message: @"Choose an identity for the MYEncoder test case:"] + != NSOKButton) { + [NSException raise: NSGenericException format: @"User canceled"]; + } + me = [panel my_identity]; + [me makePreferredIdentityForName: @"MYCryptoTest"]; + } + CAssert(me,@"No default identity has been set up in the Keychain"); + + NSData *source = [NSData dataWithContentsOfFile: @"/Library/Desktop Pictures/Nature/Zen Garden.jpg"]; + CAssert(source); + + NSError *error; + NSData *encoded; + + Log(@"Testing signing..."); + encoded = [MYEncoder encodeData: source signer: me recipient: nil error: &error]; + CAssertEq(error,nil); + CAssert([encoded length]); + Log(@"MYEncoder signed %u bytes into %u bytes", source.length,encoded.length); + + Log(@"Testing encryption..."); + encoded = [MYEncoder encodeData: source signer: nil recipient: me error: &error]; + CAssertEq(error,nil); + CAssert([encoded length]); + Log(@"MYEncoder encrypted %u bytes into %u bytes", source.length,encoded.length); + + Log(@"Testing signing+encryption..."); + encoded = [MYEncoder encodeData: source signer: me recipient: me error: &error]; + CAssertEq(error,nil); + CAssert([encoded length]); + Log(@"MYEncoder signed/encrypted %u bytes into %u bytes", source.length,encoded.length); +} +#endif diff -r dee779b84a95 -r 4c0eafa7b233 MYIdentity.h --- a/MYIdentity.h Thu Apr 09 22:47:11 2009 -0700 +++ b/MYIdentity.h Sun Apr 12 22:02:20 2009 -0700 @@ -17,12 +17,20 @@ SecIdentityRef _identityRef; } -/** Initializes a MYIdentity given an existing SecIdentityRef. */ -- (id) initWithIdentityRef: (SecIdentityRef)identityRef; +/** Creates a MYIdentity object for an existing Keychain identity reference. */ ++ (MYIdentity*) identityWithIdentityRef: (SecIdentityRef)identityRef; + +/** The underlying SecIdentityRef. */ +@property (readonly) SecIdentityRef identityRef; /** The identity's associated private key. */ @property (readonly) MYPrivateKey *privateKey; + +/** @name Mac-Only + * Functionality not available on iPhone. + */ +//@{ #if !TARGET_OS_IPHONE /** Returns the identity that's been set as the preferred one for the given name, or nil. */ @@ -33,5 +41,17 @@ - (BOOL) makePreferredIdentityForName: (NSString*)name; #endif +//@} + + +/** @name Expert + * Advanced methods. + */ +//@{ + +/** Initializes a MYIdentity given an existing SecIdentityRef. */ +- (id) initWithIdentityRef: (SecIdentityRef)identityRef; + +//@} @end diff -r dee779b84a95 -r 4c0eafa7b233 MYIdentity.m --- a/MYIdentity.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYIdentity.m Sun Apr 12 22:02:20 2009 -0700 @@ -13,6 +13,11 @@ @implementation MYIdentity +/** Creates a MYIdentity object for an existing Keychain identity reference. */ ++ (MYIdentity*) identityWithIdentityRef: (SecIdentityRef)identityRef { + return [[[self alloc] initWithIdentityRef: identityRef] autorelease]; +} + - (id) initWithIdentityRef: (SecIdentityRef)identityRef { Assert(identityRef); SecCertificateRef certificateRef; @@ -57,6 +62,8 @@ } +@synthesize identityRef=_identityRef; + - (MYPrivateKey*) privateKey { SecKeyRef keyRef = NULL; if (!check(SecIdentityCopyPrivateKey(_identityRef, &keyRef), @"SecIdentityCopyPrivateKey")) @@ -74,10 +81,10 @@ { Assert(name); SecIdentityRef identityRef; - if (!check(SecIdentityCopyPreference((CFStringRef)name, 0, NULL, &identityRef), - @"SecIdentityCopyPreference")) + OSStatus err = SecIdentityCopyPreference((CFStringRef)name, 0, NULL, &identityRef); + if (err==errKCItemNotFound || !check(err,@"SecIdentityCopyPreference") || !identityRef) return nil; - return identityRef ?[[[self alloc] initWithIdentityRef: identityRef] autorelease] :nil; + return [self identityWithIdentityRef: identityRef]; } - (BOOL) makePreferredIdentityForName: (NSString*)name { diff -r dee779b84a95 -r 4c0eafa7b233 MYKey.m --- a/MYKey.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYKey.m Sun Apr 12 22:02:20 2009 -0700 @@ -40,6 +40,10 @@ } +- (NSString*) description { + return $sprintf(@"%@[%@ /%p]", [self class], (self.name ?:@""), self.keychainItemRef); +} + - (SecExternalItemType) keyType { AssertAbstractMethod(); } diff -r dee779b84a95 -r 4c0eafa7b233 MYPrivateKey.h --- a/MYPrivateKey.h Thu Apr 09 22:47:11 2009 -0700 +++ b/MYPrivateKey.h Sun Apr 12 22:02:20 2009 -0700 @@ -51,7 +51,10 @@ /** Creates a self-signed identity certificate using this key-pair. The attributes describe the certificate's metadata, including its expiration date and the subject's name. Keys for the dictionary are given below; the only mandatory one is - kMYIdentityCommonNameKey. */ + kMYIdentityCommonNameKey. + The resulting identity certificate includes X.509 extended attributes allowing it to be + used for SSL connections. (Plug: See my MYNetwork library for an easy way to run SSL + servers and clients.) */ - (MYIdentity*) createSelfSignedIdentityWithAttributes: (NSDictionary*)attributes; /** Exports the private key as a data blob, so that it can be stored as a backup, or transferred @@ -84,6 +87,7 @@ /* Attribute keys for creating identities: */ + #define kMYIdentityCommonNameKey @"Common Name" // NSString. Required! #define kMYIdentityGivenNameKey @"Given Name" #define kMYIdentitySurnameKey @"Surname" diff -r dee779b84a95 -r 4c0eafa7b233 MYPrivateKey.m --- a/MYPrivateKey.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYPrivateKey.m Sun Apr 12 22:02:20 2009 -0700 @@ -155,9 +155,9 @@ 0LL, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY, // public key CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT, - CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN, // private key - CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_PERMANENT, - NULL, // SecAccessRef + CSSM_KEYUSE_ANY, // private key + CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE, + NULL, // SecAccessRef &pubKey, &privKey); #endif if (!check(err, @"SecKeyCreatePair")) { @@ -172,7 +172,10 @@ - (NSString*) description { - return $sprintf(@"%@[%@]", [self class], self.publicKeyDigest.abbreviatedHexString); + return $sprintf(@"%@[%@ %@ /%p]", [self class], + self.publicKeyDigest.abbreviatedHexString, + (self.name ?:@""), + self.keychainItemRef); } @synthesize publicKey=_publicKey; diff -r dee779b84a95 -r 4c0eafa7b233 MYSymmetricKey.m --- a/MYSymmetricKey.m Thu Apr 09 22:47:11 2009 -0700 +++ b/MYSymmetricKey.m Sun Apr 12 22:02:20 2009 -0700 @@ -62,7 +62,6 @@ {(id)kSecAttrCanSign, $false}, {(id)kSecAttrCanVerify, $false}, {(id)kSecValueData, keyData}, - //{(id)kSecAttrApplicationTag, [@"foo" dataUsingEncoding: NSUTF8StringEncoding]}, //TEMP {(id)kSecReturnPersistentRef, $true}); if (!check(SecItemAdd((CFDictionaryRef)keyAttrs, (CFTypeRef*)&keyRef), @"SecItemAdd")) { [self release]; diff -r dee779b84a95 -r 4c0eafa7b233 README.textile --- a/README.textile Thu Apr 09 22:47:11 2009 -0700 +++ b/README.textile Sun Apr 12 22:02:20 2009 -0700 @@ -20,6 +20,8 @@ * Cryptographic digests/hashes (effectively-unique IDs for data) * The Keychain (a secure, encrypted storage system for keys and passwords) +It's open source, released under a friendly BSD license. + h3. Requirements * Mac OS X 10.5 or later _[has been tested on 10.5.6]_ @@ -32,7 +34,7 @@ * "Download the current source code":http://mooseyard.com/hg/hgwebdir.cgi/MYCrypto/archive/tip.zip * To check out the source code using "Mercurial":http://selenic.com/mercurial/:<br> -@hg clone /hg/hgwebdir.cgi/MYCrypto/ MYCrypto@ +@hg clone http://mooseyard.com/hg/hgwebdir.cgi/MYCrypto/ MYCrypto@ * As described above, you'll also need to download or check out MYUtilities and put it in a directory next to MYCrypto. * Or if you're just looking: ** "Browse the source code":http://mooseyard.com/hg/hgwebdir.cgi/MYCrypto/file/tip