# 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