MYCertificate.m
author Jens Alfke <jens@mooseyard.com>
Fri Aug 07 11:24:53 2009 -0700 (2009-08-07)
changeset 28 54b373aa65ab
parent 26 d9c2a06d4e4e
permissions -rw-r--r--
Fixed iPhone OS build. (issue 3)
snej@0
     1
//
snej@0
     2
//  MYCertificate.m
snej@0
     3
//  MYCrypto
snej@0
     4
//
snej@0
     5
//  Created by Jens Alfke on 3/26/09.
snej@0
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@0
     7
//
snej@0
     8
snej@0
     9
#import "MYCertificate.h"
snej@0
    10
#import "MYCrypto_Private.h"
jens@16
    11
#import "MYIdentity.h"
snej@8
    12
#import "MYDigest.h"
jens@21
    13
#import "MYCertificateInfo.h"
snej@8
    14
#import "MYErrorUtils.h"
snej@0
    15
snej@0
    16
jens@27
    17
@interface MYCertificate ()
jens@27
    18
- (BOOL) _verify;
jens@27
    19
@end
jens@27
    20
jens@27
    21
jens@27
    22
snej@0
    23
@implementation MYCertificate
snej@0
    24
snej@0
    25
snej@0
    26
/** Creates a MYCertificate object for an existing Keychain certificate reference. */
snej@0
    27
- (id) initWithCertificateRef: (SecCertificateRef)certificateRef {
snej@0
    28
    self = [super initWithKeychainItemRef: (SecKeychainItemRef)certificateRef];
snej@0
    29
    if (self) {
snej@0
    30
        _certificateRef = certificateRef;     // superclass has already CFRetained it
jens@27
    31
        if (![self _verify]) {
jens@27
    32
            Log(@"Self-signed cert failed signature verification (%@)", self);
jens@27
    33
            [self release];
jens@27
    34
            return nil;
jens@27
    35
        }
snej@0
    36
    }
snej@0
    37
    return self;
snej@0
    38
}
snej@0
    39
snej@8
    40
+ (MYCertificate*) certificateWithCertificateRef: (SecCertificateRef)certificateRef {
snej@8
    41
    return [[[self alloc] initWithCertificateRef: certificateRef] autorelease];
snej@8
    42
}
snej@8
    43
snej@0
    44
/** Creates a MYCertificate object from exported key data, but does not add it to any keychain. */
snej@0
    45
- (id) initWithCertificateData: (NSData*)data
jens@24
    46
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    47
                          type: (CSSM_CERT_TYPE) type
snej@0
    48
                      encoding: (CSSM_CERT_ENCODING) encoding
jens@24
    49
#endif
snej@0
    50
{
snej@0
    51
    Assert(data);
jens@24
    52
    SecCertificateRef certificateRef = NULL;
jens@24
    53
#if MYCRYPTO_USE_IPHONE_API
jens@24
    54
    certificateRef = SecCertificateCreateWithData(NULL, (CFDataRef)data);
jens@24
    55
#else
snej@0
    56
    CSSM_DATA cssmData = {.Data=(void*)data.bytes, .Length=data.length};
snej@0
    57
    if (!check(SecCertificateCreateFromData(&cssmData, type, encoding, &certificateRef),
jens@24
    58
               @"SecCertificateCreateFromData"))
jens@24
    59
        certificateRef = NULL;
jens@24
    60
#endif
jens@24
    61
    if (!certificateRef) {
snej@0
    62
        [self release];
snej@0
    63
        return nil;
snej@0
    64
    }
snej@0
    65
    self = [self initWithCertificateRef: certificateRef];
snej@0
    66
    CFRelease(certificateRef);
jens@24
    67
    
jens@27
    68
    if (![self _verify]) {
jens@27
    69
          Log(@"Self-signed cert failed signature verification (%@)", self);
jens@27
    70
          [self release];
jens@27
    71
          return nil;
jens@24
    72
    }
jens@24
    73
    
snej@0
    74
    return self;
snej@0
    75
}
snej@0
    76
jens@24
    77
#if !MYCRYPTO_USE_IPHONE_API
snej@0
    78
- (id) initWithCertificateData: (NSData*)data {
snej@0
    79
    return [self initWithCertificateData: data 
snej@0
    80
                                    type: CSSM_CERT_X_509v3 
snej@0
    81
                                encoding: CSSM_CERT_ENCODING_BER];
snej@0
    82
}
jens@24
    83
#endif
jens@27
    84
jens@21
    85
- (void) dealloc
jens@21
    86
{
jens@21
    87
    [_info release];
jens@21
    88
    [super dealloc];
jens@21
    89
}
jens@21
    90
jens@21
    91
snej@8
    92
snej@8
    93
- (NSString*) description {
snej@8
    94
    return $sprintf(@"%@[%@ %@/%p]", 
snej@8
    95
                    [self class],
snej@8
    96
                    self.commonName,
snej@8
    97
                    self.certificateData.my_SHA1Digest.abbreviatedHexString,
snej@8
    98
                    _certificateRef);
snej@8
    99
}
snej@8
   100
snej@8
   101
snej@8
   102
- (BOOL)isEqualToCertificate:(MYCertificate*)cert {
snej@8
   103
    return [self isEqual: cert] || [self.certificateData isEqual: cert.certificateData];
snej@8
   104
}
snej@8
   105
snej@8
   106
jens@24
   107
#if !TARGET_OS_IPHONE
snej@0
   108
+ (MYCertificate*) preferredCertificateForName: (NSString*)name {
snej@0
   109
    SecCertificateRef certRef = NULL;
snej@0
   110
    if (!check(SecCertificateCopyPreference((CFStringRef)name, 0, &certRef),
snej@0
   111
               @"SecCertificateCopyPreference"))
snej@0
   112
        return nil;
snej@0
   113
    return [[[MYCertificate alloc] initWithCertificateRef: certRef] autorelease];
snej@0
   114
}
snej@0
   115
snej@0
   116
- (BOOL) setPreferredCertificateForName: (NSString*)name {
snej@0
   117
    return check(SecCertificateSetPreference(_certificateRef, (CFStringRef)name, 0, NULL),
snej@0
   118
                 @"SecCertificateSetPreference");
snej@0
   119
}
jens@24
   120
#endif TARGET_OS_IPHONE
snej@0
   121
snej@8
   122
snej@0
   123
@synthesize certificateRef=_certificateRef;
snej@0
   124
snej@0
   125
- (NSData*) certificateData {
jens@24
   126
#if MYCRYPTO_USE_IPHONE_API
jens@24
   127
    CFDataRef data = SecCertificateCopyData(_certificateRef);
jens@24
   128
    return data ?[(id)CFMakeCollectable(data) autorelease] :nil;
jens@24
   129
#else
snej@0
   130
    CSSM_DATA cssmData;
snej@0
   131
    if (!check(SecCertificateGetData(_certificateRef, &cssmData),
snej@0
   132
               @"SecCertificateGetData"))
snej@0
   133
        return nil;
snej@0
   134
    return [NSData dataWithBytes: cssmData.Data length: cssmData.Length];
jens@24
   135
#endif
snej@0
   136
}
snej@0
   137
snej@0
   138
- (MYPublicKey*) publicKey {
snej@0
   139
    SecKeyRef keyRef = NULL;
jens@24
   140
#if MYCRYPTO_USE_IPHONE_API
jens@24
   141
    SecTrustRef trust = NULL;
jens@24
   142
    SecPolicyRef policy = SecPolicyCreateBasicX509();
jens@24
   143
    OSStatus err = SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef),
jens@24
   144
                                                  policy,
jens@24
   145
                                                  &trust);
jens@24
   146
    CFRelease(policy);
jens@24
   147
    if (!check(err,@"SecTrustCreateWithCertificates"))
jens@24
   148
        return nil;
jens@24
   149
    SecTrustResultType result;
jens@24
   150
    if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate")) {
jens@24
   151
        CFRelease(trust);
jens@24
   152
        return nil;
jens@24
   153
    }
jens@24
   154
    keyRef = SecTrustCopyPublicKey(trust);
jens@24
   155
    CFRelease(trust);
jens@24
   156
#else
snej@0
   157
    if (!check(SecCertificateCopyPublicKey(_certificateRef, &keyRef),
snej@0
   158
               @"SecCertificateCopyPublicKey") || !keyRef)
snej@0
   159
        return nil;
jens@24
   160
#endif
jens@24
   161
    if (!keyRef)
jens@24
   162
        return nil;
snej@0
   163
    MYPublicKey *key = [[[MYPublicKey alloc] initWithKeyRef: keyRef] autorelease];
snej@0
   164
    CFRelease(keyRef);
jens@26
   165
#if MYCRYPTO_USE_IPHONE_API
jens@26
   166
    key.certificate = self;
jens@26
   167
    key.isPersistent = NO;
jens@26
   168
#endif
snej@0
   169
    return key;
snej@0
   170
}
snej@0
   171
jens@26
   172
- (MYSHA1Digest*) publicKeyDigest {
jens@26
   173
    return self.publicKey.publicKeyDigest;
jens@26
   174
}
jens@26
   175
jens@16
   176
- (MYIdentity*) identity {
jens@16
   177
    return [[[MYIdentity alloc] initWithCertificateRef: _certificateRef] autorelease];
jens@16
   178
}
jens@16
   179
jens@21
   180
- (MYCertificateInfo*) info {
jens@21
   181
    if (!_info) {
jens@21
   182
        NSError *error;
jens@21
   183
        _info = [[MYCertificateInfo alloc] initWithCertificateData: self.certificateData
jens@21
   184
                                                             error: &error];
jens@21
   185
        if (!_info)
jens@21
   186
            Warn(@"Couldn't parse certificate %@: %@", self, error);
jens@21
   187
    }
jens@21
   188
    return _info;
jens@21
   189
}
jens@21
   190
snej@0
   191
- (NSString*) commonName {
snej@0
   192
    CFStringRef name = NULL;
jens@24
   193
#if MYCRYPTO_USE_IPHONE_API
jens@24
   194
    name = SecCertificateCopySubjectSummary(_certificateRef);
jens@24
   195
#else
snej@0
   196
    if (!check(SecCertificateCopyCommonName(_certificateRef, &name),
jens@24
   197
               @"SecCertificateCopyCommonName"))
snej@0
   198
        return nil;
jens@24
   199
#endif
jens@24
   200
    return name ?[NSMakeCollectable(name) autorelease] :nil;
snej@0
   201
}
snej@0
   202
snej@0
   203
- (NSArray*) emailAddresses {
jens@24
   204
#if MYCRYPTO_USE_IPHONE_API
jens@24
   205
    NSString *email = self.info.subject.emailAddress;
jens@24
   206
    return email ?$array(email) :nil;
jens@24
   207
#else
snej@0
   208
    CFArrayRef addrs = NULL;
snej@0
   209
    if (!check(SecCertificateCopyEmailAddresses(_certificateRef, &addrs),
snej@0
   210
               @"SecCertificateCopyEmailAddresses") || !addrs)
snej@0
   211
        return nil;
snej@0
   212
    return [(id)CFMakeCollectable(addrs) autorelease];
jens@24
   213
#endif
snej@0
   214
}
snej@0
   215
snej@0
   216
snej@8
   217
#pragma mark -
snej@8
   218
#pragma mark TRUST/POLICY STUFF:
snej@8
   219
snej@8
   220
jens@27
   221
- (BOOL) _verify {
jens@27
   222
  // If the cert is self-signed, verify its signature. Apple's frameworks don't do this,
jens@27
   223
  // even the SecTrust API; if the signature doesn't verify, they just assume it could be
jens@27
   224
  // signed by a different cert. Seems like a bad decision to me, so I'll add the check:
jens@27
   225
  MYCertificateInfo *info = self.info;
jens@27
   226
  return !info.isRoot || [info verifySignatureWithKey: self.publicKey];
jens@27
   227
}  
jens@27
   228
jens@27
   229
jens@24
   230
- (SecTrustResultType) evaluateTrustWithPolicy: (SecPolicyRef)policy {
jens@24
   231
    SecTrustRef trust;
jens@24
   232
    if (!check(SecTrustCreateWithCertificates((CFArrayRef)$array((id)_certificateRef), policy, &trust), 
jens@24
   233
               @"SecTrustCreateWithCertificates"))
jens@24
   234
        return kSecTrustResultOtherError;
jens@24
   235
    SecTrustResultType result;
jens@24
   236
    if (!check(SecTrustEvaluate(trust, &result), @"SecTrustEvaluate"))
jens@24
   237
        result = kSecTrustResultOtherError;
jens@24
   238
    
jens@26
   239
#if !MYCRYPTO_USE_IPHONE_API
jens@24
   240
    // This is just to log details:
jens@24
   241
    CSSM_TP_APPLE_EVIDENCE_INFO *status;
jens@24
   242
    CFArrayRef certChain;
jens@24
   243
    if (check(SecTrustGetResult(trust, &result, &certChain, &status), @"SecTrustGetResult")) {
jens@24
   244
        Log(@"evaluateTrust: result=%@, bits=%X, certChain=%@", MYTrustResultDescribe(result),status->StatusBits, certChain);
jens@24
   245
        for (unsigned i=0; i<status->NumStatusCodes; i++)
jens@24
   246
            Log(@"    #%i: %X", i, status->StatusCodes[i]);
jens@24
   247
        CFRelease(certChain);
jens@24
   248
    }
jens@24
   249
#endif
jens@24
   250
    
jens@24
   251
    CFRelease(trust);
jens@24
   252
    return result;
jens@24
   253
}
jens@24
   254
jens@24
   255
- (SecTrustResultType) evaluateTrust {
jens@24
   256
    return [self evaluateTrustWithPolicy: [[self class] X509Policy]];
jens@24
   257
}
jens@24
   258
jens@24
   259
jens@24
   260
#if !MYCRYPTO_USE_IPHONE_API
snej@8
   261
+ (SecPolicyRef) policyForOID: (CSSM_OID) policyOID {
snej@8
   262
    SecPolicySearchRef search;
snej@8
   263
    if (!check(SecPolicySearchCreate(CSSM_CERT_X_509v3, &policyOID, NULL, &search),
snej@8
   264
           @"SecPolicySearchCreate"))
snej@8
   265
        return nil;
snej@8
   266
    SecPolicyRef policy = NULL;
snej@8
   267
    if (!check(SecPolicySearchCopyNext(search, &policy), @"SecPolicySearchCopyNext"))
snej@8
   268
        policy = NULL;
snej@8
   269
    CFRelease(search);
snej@8
   270
    return policy;
snej@8
   271
}
jens@24
   272
#endif
snej@8
   273
snej@8
   274
+ (SecPolicyRef) X509Policy {
snej@8
   275
    static SecPolicyRef sX509Policy = NULL;
jens@24
   276
    if (!sX509Policy) {
jens@24
   277
#if MYCRYPTO_USE_IPHONE_API
jens@24
   278
        sX509Policy = SecPolicyCreateBasicX509();
jens@24
   279
#else
snej@8
   280
        sX509Policy = [self policyForOID: CSSMOID_APPLE_X509_BASIC];
jens@24
   281
#endif
jens@24
   282
    }
snej@8
   283
    return sX509Policy;
snej@8
   284
}
snej@8
   285
snej@8
   286
+ (SecPolicyRef) SSLPolicy {
snej@8
   287
    static SecPolicyRef sSSLPolicy = NULL;
jens@24
   288
    if (!sSSLPolicy) {
jens@24
   289
#if MYCRYPTO_USE_IPHONE_API
jens@24
   290
        sSSLPolicy = SecPolicyCreateSSL(NO,NULL);
jens@24
   291
#else
snej@8
   292
        sSSLPolicy = [self policyForOID: CSSMOID_APPLE_TP_SSL];
jens@24
   293
#endif
jens@24
   294
    }
snej@8
   295
    return sSSLPolicy;
snej@8
   296
}
snej@8
   297
jens@24
   298
#if !TARGET_OS_IPHONE
snej@8
   299
+ (SecPolicyRef) SMIMEPolicy {
snej@8
   300
    static SecPolicyRef sSMIMEPolicy = NULL;
jens@24
   301
    if (!sSMIMEPolicy) {
snej@8
   302
        sSMIMEPolicy = [self policyForOID: CSSMOID_APPLE_TP_SMIME];
jens@24
   303
    }
snej@8
   304
    return sSMIMEPolicy;
snej@8
   305
}
snej@8
   306
snej@8
   307
- (CSSM_CERT_TYPE) certificateType {
snej@8
   308
    CSSM_CERT_TYPE type = CSSM_CERT_UNKNOWN;
snej@8
   309
    if (!check(SecCertificateGetType(_certificateRef, &type), @"SecCertificateGetType"))
snej@8
   310
        type = CSSM_CERT_UNKNOWN;
snej@8
   311
    return type;
snej@8
   312
}
snej@8
   313
snej@8
   314
- (NSArray*) trustSettings {
snej@8
   315
    CFArrayRef settings = NULL;
snej@8
   316
    OSStatus err = SecTrustSettingsCopyTrustSettings(_certificateRef, kSecTrustSettingsDomainUser, 
snej@8
   317
                                                     &settings);
snej@8
   318
    if (err == errSecItemNotFound || !check(err,@"SecTrustSettingsCopyTrustSettings") || !settings)
snej@8
   319
        return nil;
snej@8
   320
    return [(id)CFMakeCollectable(settings) autorelease];
snej@8
   321
}
snej@8
   322
        
snej@8
   323
snej@8
   324
- (BOOL) setUserTrust: (SecTrustUserSetting)trustSetting
snej@8
   325
{
snej@8
   326
    if (trustSetting == kSecTrustResultProceed) {
snej@8
   327
        return check(SecTrustSettingsSetTrustSettings(_certificateRef, 
snej@8
   328
                                                      kSecTrustSettingsDomainUser, nil),
snej@8
   329
                     @"SecTrustSettingsSetTrustSettings");
snej@8
   330
    } else if (trustSetting == kSecTrustResultDeny) {
snej@8
   331
        OSStatus err = SecTrustSettingsRemoveTrustSettings(_certificateRef, 
snej@8
   332
                                                           kSecTrustSettingsDomainUser);
snej@8
   333
        return err == errSecItemNotFound || check(err, @"SecTrustSettingsRemoveTrustSettings");
snej@8
   334
    } else
snej@8
   335
        return paramErr;
snej@8
   336
}
jens@24
   337
#endif
snej@8
   338
snej@8
   339
snej@0
   340
@end
snej@0
   341
snej@0
   342
snej@8
   343
NSString* MYTrustResultDescribe( SecTrustResultType result ) {
snej@8
   344
    static NSString* const kTrustResultNames[kSecTrustResultOtherError+1] = {
snej@8
   345
        @"Invalid",
snej@8
   346
        @"Proceed",
snej@8
   347
        @"Confirm",
snej@8
   348
        @"Deny",
snej@8
   349
        @"Unspecified",
snej@8
   350
        @"RecoverableTrustFailure",
snej@8
   351
        @"FatalTrustFailure",
snej@8
   352
        @"OtherError"
snej@8
   353
    };
snej@8
   354
    if (result>=0 && result <=kSecTrustResultOtherError)
snej@8
   355
        return kTrustResultNames[result];
snej@8
   356
    else
snej@8
   357
        return $sprintf(@"(Unknown trust result %i)", result);
snej@8
   358
}
snej@8
   359
snej@8
   360
jens@24
   361
#if !TARGET_OS_IPHONE
jens@24
   362
NSString* MYPolicyGetName( SecPolicyRef policy ) {
jens@24
   363
    if (!policy)
jens@24
   364
        return @"(null)";
jens@24
   365
    CSSM_OID oid = {};
jens@24
   366
    SecPolicyGetOID(policy, &oid);
jens@24
   367
    return $sprintf(@"SecPolicy[%@]", OIDAsString(oid));
jens@24
   368
}
jens@24
   369
snej@8
   370
NSString* MYTrustDescribe( SecTrustRef trust ) {
snej@8
   371
    SecTrustResultType result;
snej@8
   372
    CFArrayRef certChain=NULL;
snej@8
   373
    CSSM_TP_APPLE_EVIDENCE_INFO* statusChain;
snej@8
   374
    OSStatus err = SecTrustGetResult(trust, &result, &certChain, &statusChain);
snej@8
   375
    NSString *desc;
snej@8
   376
    if (err)
snej@8
   377
        desc = $sprintf(@"SecTrust[%p, err=%@]", trust, MYErrorName(NSOSStatusErrorDomain, err));
snej@8
   378
    else
snej@8
   379
        desc = $sprintf(@"SecTrust[%@, %u in chain]", 
snej@8
   380
                        MYTrustResultDescribe(result),
snej@8
   381
                        CFArrayGetCount(certChain));
snej@8
   382
    if (certChain) CFRelease(certChain);
snej@8
   383
    return desc;
snej@8
   384
}
snej@8
   385
snej@8
   386
snej@12
   387
// Taken from Keychain.framework
snej@12
   388
NSString* OIDAsString(const CSSM_OID oid) {
snej@12
   389
    if ((NULL == oid.Data) || (0 >= oid.Length)) {
snej@12
   390
        return nil;
snej@12
   391
    } else {
snej@12
   392
        NSMutableString *result = [NSMutableString stringWithCapacity:(4 * oid.Length)];
snej@12
   393
        unsigned int i;
snej@12
   394
        
snej@12
   395
        for (i = 0; i < oid.Length; ++i) {
snej@12
   396
            [result appendFormat:@"%s%hhu", ((0 == i) ? "" : ", "), oid.Data[i]];
snej@12
   397
        }
snej@12
   398
        
snej@12
   399
        return result;
snej@12
   400
    }
snej@12
   401
}
jens@24
   402
#endif
snej@12
   403
snej@12
   404
jens@24
   405
#if !TARGET_OS_IPHONE
snej@8
   406
TestCase(Trust) {
snej@8
   407
    Log(@"X.509 policy = %@", MYPolicyGetName([MYCertificate X509Policy]));
snej@8
   408
    Log(@"  SSL policy = %@", MYPolicyGetName([MYCertificate SSLPolicy]));
snej@8
   409
    Log(@"SMIME policy = %@", MYPolicyGetName([MYCertificate SMIMEPolicy]));
snej@8
   410
    for (MYCertificate *cert in [[MYKeychain defaultKeychain] enumerateCertificates]) {
snej@8
   411
        NSArray *settings = cert.trustSettings;
snej@8
   412
        if (settings)
snej@8
   413
            Log(@"---- %@ = %@", cert, settings);
snej@8
   414
    }
snej@8
   415
}
jens@24
   416
#endif
snej@14
   417
snej@14
   418
snej@14
   419
snej@14
   420
/*
snej@14
   421
 Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
snej@14
   422
 
snej@14
   423
 Redistribution and use in source and binary forms, with or without modification, are permitted
snej@14
   424
 provided that the following conditions are met:
snej@14
   425
 
snej@14
   426
 * Redistributions of source code must retain the above copyright notice, this list of conditions
snej@14
   427
 and the following disclaimer.
snej@14
   428
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
snej@14
   429
 and the following disclaimer in the documentation and/or other materials provided with the
snej@14
   430
 distribution.
snej@14
   431
 
snej@14
   432
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
snej@14
   433
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
snej@14
   434
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
snej@14
   435
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
snej@14
   436
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
snej@14
   437
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
snej@14
   438
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
snej@14
   439
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
snej@14
   440
 */