snej@3
|
1 |
//
|
snej@3
|
2 |
// MYPrivateKey.m
|
snej@3
|
3 |
// MYCrypto
|
snej@3
|
4 |
//
|
snej@3
|
5 |
// Created by Jens Alfke on 4/7/09.
|
snej@3
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
snej@3
|
7 |
//
|
snej@3
|
8 |
|
snej@3
|
9 |
#import "MYPrivateKey.h"
|
snej@3
|
10 |
#import "MYCrypto_Private.h"
|
snej@3
|
11 |
#import "MYDigest.h"
|
snej@3
|
12 |
#import <CommonCrypto/CommonDigest.h>
|
snej@3
|
13 |
|
snej@5
|
14 |
#if !TARGET_OS_IPHONE
|
snej@5
|
15 |
#import "MYCertGen.h"
|
snej@5
|
16 |
#endif
|
snej@3
|
17 |
|
snej@3
|
18 |
@implementation MYPrivateKey
|
snej@3
|
19 |
|
snej@3
|
20 |
|
snej@3
|
21 |
- (id) initWithKeyRef: (SecKeyRef)privateKey
|
snej@3
|
22 |
{
|
snej@3
|
23 |
self = [super initWithKeyRef: privateKey];
|
snej@3
|
24 |
if (self) {
|
snej@3
|
25 |
// No public key given, so look it up:
|
snej@3
|
26 |
MYSHA1Digest *digest = self._keyDigest;
|
snej@3
|
27 |
if (digest)
|
snej@3
|
28 |
_publicKey = [[self.keychain publicKeyWithDigest: digest] retain];
|
snej@3
|
29 |
if (!_publicKey) {
|
snej@3
|
30 |
// The matching public key won't turn up if it's embedded in a certificate;
|
snej@3
|
31 |
// I'd have to search for certs if I wanted to look that up. Skip it for now.
|
snej@3
|
32 |
Log(@"MYPrivateKey(%p): Couldn't find matching public key for private key! digest=%@",
|
snej@3
|
33 |
self, digest);
|
snej@3
|
34 |
[self release];
|
snej@3
|
35 |
return nil;
|
snej@3
|
36 |
}
|
snej@3
|
37 |
}
|
snej@3
|
38 |
return self;
|
snej@3
|
39 |
}
|
snej@3
|
40 |
|
snej@3
|
41 |
|
snej@3
|
42 |
- (id) _initWithKeyRef: (SecKeyRef)privateKey
|
snej@3
|
43 |
publicKey: (MYPublicKey*)publicKey
|
snej@3
|
44 |
{
|
snej@3
|
45 |
Assert(publicKey);
|
snej@3
|
46 |
self = [super initWithKeyRef: privateKey];
|
snej@3
|
47 |
if (self) {
|
snej@3
|
48 |
_publicKey = [publicKey retain];
|
snej@3
|
49 |
}
|
snej@3
|
50 |
return self;
|
snej@3
|
51 |
}
|
snej@3
|
52 |
|
snej@3
|
53 |
- (id) initWithKeyRef: (SecKeyRef)privateKey
|
snej@3
|
54 |
publicKeyRef: (SecKeyRef)publicKeyRef
|
snej@3
|
55 |
{
|
snej@3
|
56 |
MYPublicKey *publicKey = [[MYPublicKey alloc] initWithKeyRef: publicKeyRef];
|
snej@3
|
57 |
self = [self _initWithKeyRef: privateKey publicKey: publicKey];
|
snej@3
|
58 |
[publicKey release];
|
snej@3
|
59 |
return self;
|
snej@3
|
60 |
}
|
snej@3
|
61 |
|
snej@3
|
62 |
- (id) _initWithKeyRef: (SecKeyRef)privateKey
|
snej@3
|
63 |
publicKeyData: (NSData*)pubKeyData
|
snej@3
|
64 |
forKeychain: (SecKeychainRef)keychain
|
snej@3
|
65 |
{
|
snej@3
|
66 |
if (!privateKey) {
|
snej@3
|
67 |
[self release];
|
snej@3
|
68 |
return nil;
|
snej@3
|
69 |
}
|
snej@3
|
70 |
MYPublicKey *pubKey = [[MYPublicKey alloc] _initWithKeyData: pubKeyData forKeychain: keychain];
|
snej@3
|
71 |
if (!pubKey) {
|
snej@3
|
72 |
[self release];
|
snej@3
|
73 |
return nil;
|
snej@3
|
74 |
}
|
snej@3
|
75 |
self = [super initWithKeyRef: privateKey];
|
snej@3
|
76 |
if (self) {
|
snej@3
|
77 |
_publicKey = pubKey;
|
snej@3
|
78 |
} else {
|
snej@3
|
79 |
[pubKey removeFromKeychain];
|
snej@3
|
80 |
[pubKey release];
|
snej@3
|
81 |
}
|
snej@3
|
82 |
return self;
|
snej@3
|
83 |
}
|
snej@3
|
84 |
|
snej@3
|
85 |
|
snej@3
|
86 |
#if !TARGET_OS_IPHONE
|
snej@3
|
87 |
|
snej@3
|
88 |
// The public API for this is in MYKeychain.
|
snej@3
|
89 |
- (id) _initWithKeyData: (NSData*)privKeyData
|
snej@3
|
90 |
publicKeyData: (NSData*)pubKeyData
|
snej@3
|
91 |
forKeychain: (SecKeychainRef)keychain
|
snej@3
|
92 |
alertTitle: (NSString*)title
|
snej@3
|
93 |
alertPrompt: (NSString*)prompt
|
snej@3
|
94 |
{
|
snej@3
|
95 |
// Try to import the private key first, since the user might cancel the passphrase alert.
|
snej@3
|
96 |
SecKeyImportExportParameters params = {
|
snej@3
|
97 |
.flags = kSecKeySecurePassphrase,
|
snej@3
|
98 |
.alertTitle = (CFStringRef) title,
|
snej@3
|
99 |
.alertPrompt = (CFStringRef) prompt
|
snej@3
|
100 |
};
|
snej@3
|
101 |
SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,¶ms);
|
snej@3
|
102 |
return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
|
snej@3
|
103 |
}
|
snej@3
|
104 |
|
snej@3
|
105 |
// This method is for testing, so unit-tests don't require user intervention.
|
snej@3
|
106 |
// It's deliberately not made public, to discourage clients from trying to manage the passphrases
|
snej@3
|
107 |
// themselves (this is less secure than letting the Security agent do it.)
|
snej@3
|
108 |
- (id) _initWithKeyData: (NSData*)privKeyData
|
snej@3
|
109 |
publicKeyData: (NSData*)pubKeyData
|
snej@3
|
110 |
forKeychain: (SecKeychainRef)keychain
|
snej@3
|
111 |
passphrase: (NSString*)passphrase
|
snej@3
|
112 |
{
|
snej@3
|
113 |
SecKeyImportExportParameters params = {
|
snej@3
|
114 |
.passphrase = (CFStringRef) passphrase,
|
snej@3
|
115 |
};
|
snej@3
|
116 |
SecKeyRef privateKey = importKey(privKeyData,kSecItemTypePrivateKey,keychain,¶ms);
|
snej@3
|
117 |
return [self _initWithKeyRef: privateKey publicKeyData: pubKeyData forKeychain: keychain];
|
snej@3
|
118 |
}
|
snej@3
|
119 |
|
snej@4
|
120 |
|
snej@4
|
121 |
- (MYIdentity*) createSelfSignedIdentityWithAttributes: (NSDictionary*)attributes {
|
snej@4
|
122 |
return MYIdentityCreateSelfSigned(self, attributes);
|
snej@4
|
123 |
}
|
snej@4
|
124 |
|
snej@4
|
125 |
|
snej@3
|
126 |
#endif
|
snej@3
|
127 |
|
snej@3
|
128 |
|
snej@3
|
129 |
- (void) dealloc
|
snej@3
|
130 |
{
|
snej@3
|
131 |
[_publicKey release];
|
snej@3
|
132 |
[super dealloc];
|
snej@3
|
133 |
}
|
snej@3
|
134 |
|
snej@3
|
135 |
|
snej@3
|
136 |
+ (MYPrivateKey*) _generateRSAKeyPairOfSize: (unsigned)keySize
|
snej@3
|
137 |
inKeychain: (MYKeychain*)keychain
|
snej@3
|
138 |
{
|
snej@3
|
139 |
Assert( keySize == 512 || keySize == 1024 || keySize == 2048, @"Unsupported key size %u", keySize );
|
snej@3
|
140 |
SecKeyRef pubKey=NULL, privKey=NULL;
|
snej@3
|
141 |
OSStatus err;
|
snej@3
|
142 |
|
snej@3
|
143 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@3
|
144 |
NSDictionary *pubKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
|
snej@3
|
145 |
NSDictionary *privKeyAttrs = $dict({(id)kSecAttrIsPermanent, $true});
|
snej@3
|
146 |
NSDictionary *keyAttrs = $dict( {(id)kSecAttrKeyType, (id)kSecAttrKeyTypeRSA},
|
snej@3
|
147 |
{(id)kSecAttrKeySizeInBits, $object(keySize)},
|
snej@3
|
148 |
{(id)kSecPublicKeyAttrs, pubKeyAttrs},
|
snej@3
|
149 |
{(id)kSecPrivateKeyAttrs, privKeyAttrs} );
|
snej@3
|
150 |
err = SecKeyGeneratePair((CFDictionaryRef)keyAttrs,&pubKey,&privKey);
|
snej@3
|
151 |
#else
|
snej@3
|
152 |
err = SecKeyCreatePair(keychain.keychainRefOrDefault,
|
snej@3
|
153 |
CSSM_ALGID_RSA,
|
snej@3
|
154 |
keySize,
|
snej@3
|
155 |
0LL,
|
snej@13
|
156 |
CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, // public key
|
snej@3
|
157 |
CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
|
snej@8
|
158 |
CSSM_KEYUSE_ANY, // private key
|
snej@8
|
159 |
CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
|
snej@8
|
160 |
NULL, // SecAccessRef
|
snej@3
|
161 |
&pubKey, &privKey);
|
snej@3
|
162 |
#endif
|
snej@3
|
163 |
if (!check(err, @"SecKeyCreatePair")) {
|
snej@3
|
164 |
return nil;
|
snej@3
|
165 |
} else
|
snej@3
|
166 |
return [[[self alloc] initWithKeyRef: privKey publicKeyRef: pubKey] autorelease];
|
snej@3
|
167 |
}
|
snej@3
|
168 |
|
snej@3
|
169 |
|
snej@3
|
170 |
#pragma mark -
|
snej@3
|
171 |
#pragma mark ACCESSORS:
|
snej@3
|
172 |
|
snej@3
|
173 |
|
snej@3
|
174 |
- (NSString*) description {
|
snej@8
|
175 |
return $sprintf(@"%@[%@ %@ /%p]", [self class],
|
snej@8
|
176 |
self.publicKeyDigest.abbreviatedHexString,
|
snej@8
|
177 |
(self.name ?:@""),
|
snej@8
|
178 |
self.keychainItemRef);
|
snej@3
|
179 |
}
|
snej@3
|
180 |
|
snej@3
|
181 |
@synthesize publicKey=_publicKey;
|
snej@3
|
182 |
|
snej@3
|
183 |
- (MYSHA1Digest*) publicKeyDigest {
|
snej@3
|
184 |
return _publicKey.publicKeyDigest;
|
snej@3
|
185 |
}
|
snej@3
|
186 |
|
snej@3
|
187 |
- (SecExternalItemType) keyType {
|
snej@3
|
188 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@3
|
189 |
return kSecAttrKeyClassPublic;
|
snej@3
|
190 |
#else
|
snej@3
|
191 |
return kSecItemTypePrivateKey;
|
snej@3
|
192 |
#endif
|
snej@3
|
193 |
}
|
snej@3
|
194 |
|
snej@3
|
195 |
- (NSData *) keyData {
|
snej@3
|
196 |
[NSException raise: NSGenericException format: @"Can't access keyData of a PrivateKey"];
|
snej@3
|
197 |
return nil;
|
snej@3
|
198 |
}
|
snej@3
|
199 |
|
snej@3
|
200 |
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
|
snej@3
|
201 |
return [super setValue: valueStr ofAttribute: attr]
|
snej@3
|
202 |
&& [_publicKey setValue: valueStr ofAttribute: attr];
|
snej@3
|
203 |
}
|
snej@3
|
204 |
|
snej@3
|
205 |
|
snej@3
|
206 |
#pragma mark -
|
snej@3
|
207 |
#pragma mark OPERATIONS:
|
snej@3
|
208 |
|
snej@3
|
209 |
|
snej@3
|
210 |
- (BOOL) removeFromKeychain {
|
snej@3
|
211 |
return [super removeFromKeychain]
|
snej@3
|
212 |
&& [_publicKey removeFromKeychain];
|
snej@3
|
213 |
}
|
snej@3
|
214 |
|
snej@3
|
215 |
|
snej@13
|
216 |
- (NSData*) rawDecryptData: (NSData*)data {
|
snej@3
|
217 |
return [self _crypt: data operation: NO];
|
snej@3
|
218 |
}
|
snej@3
|
219 |
|
snej@3
|
220 |
|
snej@3
|
221 |
- (NSData*) signData: (NSData*)data {
|
snej@3
|
222 |
Assert(data);
|
snej@3
|
223 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@3
|
224 |
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
|
snej@3
|
225 |
CC_SHA1(data.bytes,data.length, digest);
|
snej@3
|
226 |
|
snej@3
|
227 |
size_t sigLen = 1024;
|
snej@3
|
228 |
uint8_t sigBuf[sigLen];
|
snej@3
|
229 |
OSStatus err = SecKeyRawSign(self.keyRef, kSecPaddingPKCS1SHA1,
|
snej@3
|
230 |
digest,sizeof(digest), //data.bytes, data.length,
|
snej@3
|
231 |
sigBuf, &sigLen);
|
snej@3
|
232 |
if(err) {
|
snej@3
|
233 |
Warn(@"SecKeyRawSign failed: %i",err);
|
snej@3
|
234 |
return nil;
|
snej@3
|
235 |
} else
|
snej@3
|
236 |
return [NSData dataWithBytes: sigBuf length: sigLen];
|
snej@3
|
237 |
#else
|
snej@3
|
238 |
NSData *signature = nil;
|
jens@17
|
239 |
CSSM_CC_HANDLE ccHandle = [self _createSignatureContext: CSSM_ALGID_SHA1WithRSA];
|
snej@3
|
240 |
if (!ccHandle) return nil;
|
snej@3
|
241 |
CSSM_DATA original = {data.length, (void*)data.bytes};
|
snej@3
|
242 |
CSSM_DATA result = {0,NULL};
|
snej@3
|
243 |
if (checkcssm(CSSM_SignData(ccHandle, &original, 1, CSSM_ALGID_NONE, &result), @"CSSM_SignData"))
|
snej@3
|
244 |
signature = [NSData dataWithBytesNoCopy: result.Data length: result.Length
|
snej@3
|
245 |
freeWhenDone: YES];
|
snej@3
|
246 |
CSSM_DeleteContext(ccHandle);
|
snej@3
|
247 |
return signature;
|
snej@3
|
248 |
#endif
|
snej@3
|
249 |
}
|
snej@3
|
250 |
|
snej@3
|
251 |
|
snej@3
|
252 |
#if !TARGET_OS_IPHONE
|
snej@3
|
253 |
|
snej@3
|
254 |
- (NSData*) exportKeyInFormat: (SecExternalFormat)format
|
snej@3
|
255 |
withPEM: (BOOL)withPEM
|
snej@3
|
256 |
alertTitle: (NSString*)title
|
snej@3
|
257 |
alertPrompt: (NSString*)prompt
|
snej@3
|
258 |
{
|
snej@3
|
259 |
SecKeyImportExportParameters params = {
|
snej@3
|
260 |
.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
|
snej@3
|
261 |
.flags = kSecKeySecurePassphrase,
|
snej@3
|
262 |
.alertTitle = (CFStringRef)title,
|
snej@3
|
263 |
.alertPrompt = (CFStringRef)prompt
|
snej@3
|
264 |
};
|
snej@3
|
265 |
CFDataRef data = NULL;
|
snej@3
|
266 |
if (check(SecKeychainItemExport(self.keyRef,
|
snej@3
|
267 |
format, (withPEM ?kSecItemPemArmour :0),
|
snej@3
|
268 |
¶ms, &data),
|
snej@3
|
269 |
@"SecKeychainItemExport"))
|
snej@3
|
270 |
return [(id)CFMakeCollectable(data) autorelease];
|
snej@3
|
271 |
else
|
snej@3
|
272 |
return nil;
|
snej@3
|
273 |
}
|
snej@3
|
274 |
|
snej@3
|
275 |
- (NSData*) exportKey {
|
snej@3
|
276 |
return [self exportKeyInFormat: kSecFormatWrappedOpenSSL withPEM: YES
|
snej@3
|
277 |
alertTitle: @"Export Private Key"
|
snej@3
|
278 |
alertPrompt: @"Enter a passphrase to protect the private-key file.\n"
|
snej@3
|
279 |
"You will need to re-enter the passphrase later when importing the key from this file, "
|
snej@3
|
280 |
"so keep it in a safe place."];
|
snej@3
|
281 |
//FIX: Should make these messages localizable.
|
snej@3
|
282 |
}
|
snej@3
|
283 |
|
snej@3
|
284 |
|
snej@3
|
285 |
- (NSData*) _exportKeyInFormat: (SecExternalFormat)format
|
snej@3
|
286 |
withPEM: (BOOL)withPEM
|
snej@3
|
287 |
passphrase: (NSString*)passphrase
|
snej@3
|
288 |
{
|
snej@3
|
289 |
SecKeyImportExportParameters params = {
|
snej@3
|
290 |
.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION,
|
snej@3
|
291 |
.passphrase = (CFStringRef)passphrase
|
snej@3
|
292 |
};
|
snej@3
|
293 |
CFDataRef data = NULL;
|
snej@3
|
294 |
if (check(SecKeychainItemExport(self.keyRef,
|
snej@3
|
295 |
format, (withPEM ?kSecItemPemArmour :0),
|
snej@3
|
296 |
¶ms, &data),
|
snej@3
|
297 |
@"SecKeychainItemExport"))
|
snej@3
|
298 |
return [(id)CFMakeCollectable(data) autorelease];
|
snej@3
|
299 |
else
|
snej@3
|
300 |
return nil;
|
snej@3
|
301 |
}
|
snej@3
|
302 |
|
snej@13
|
303 |
|
snej@13
|
304 |
- (MYSymmetricKey*) unwrapSessionKey: (NSData*)wrappedData
|
snej@13
|
305 |
withAlgorithm: (CCAlgorithm)algorithm
|
snej@13
|
306 |
sizeInBits: (unsigned)sizeInBits
|
snej@13
|
307 |
{
|
snej@13
|
308 |
// First create a wrapped-key structure from the data:
|
snej@13
|
309 |
CSSM_WRAP_KEY wrappedKey = {
|
snej@13
|
310 |
.KeyHeader = {
|
snej@13
|
311 |
.BlobType = CSSM_KEYBLOB_WRAPPED,
|
snej@13
|
312 |
.Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS3,
|
snej@13
|
313 |
.AlgorithmId = CSSMFromCCAlgorithm(algorithm),
|
snej@13
|
314 |
.KeyClass = CSSM_KEYCLASS_SESSION_KEY,
|
snej@13
|
315 |
.LogicalKeySizeInBits = sizeInBits,
|
snej@13
|
316 |
.KeyAttr = CSSM_KEYATTR_EXTRACTABLE,
|
snej@13
|
317 |
.KeyUsage = CSSM_KEYUSE_ANY,
|
snej@13
|
318 |
.WrapAlgorithmId = self.cssmAlgorithm,
|
snej@13
|
319 |
},
|
snej@13
|
320 |
.KeyData = {
|
snej@13
|
321 |
.Data = (void*)wrappedData.bytes,
|
snej@13
|
322 |
.Length = wrappedData.length
|
snej@13
|
323 |
}
|
snej@13
|
324 |
};
|
snej@13
|
325 |
|
snej@13
|
326 |
const CSSM_ACCESS_CREDENTIALS* credentials;
|
snej@13
|
327 |
credentials = [self cssmCredentialsForOperation: CSSM_ACL_AUTHORIZATION_IMPORT_WRAPPED
|
snej@13
|
328 |
type: kSecCredentialTypeDefault error: nil];
|
snej@13
|
329 |
CSSM_CSP_HANDLE cspHandle = self.cssmCSPHandle;
|
snej@13
|
330 |
CSSM_CC_HANDLE ctx;
|
snej@13
|
331 |
if (!checkcssm(CSSM_CSP_CreateAsymmetricContext(cspHandle,
|
snej@13
|
332 |
self.cssmAlgorithm,
|
snej@13
|
333 |
credentials,
|
snej@13
|
334 |
self.cssmKey,
|
snej@13
|
335 |
CSSM_PADDING_PKCS1,
|
snej@13
|
336 |
&ctx),
|
snej@13
|
337 |
@"CSSM_CSP_CreateAsymmetricContext"))
|
snej@13
|
338 |
return nil;
|
snej@13
|
339 |
|
snej@13
|
340 |
// Now unwrap the key:
|
snej@13
|
341 |
MYSymmetricKey *result = nil;
|
snej@13
|
342 |
CSSM_KEY *unwrappedKey = calloc(1,sizeof(CSSM_KEY));
|
snej@14
|
343 |
CSSM_DATA label = {.Data=(void*)"Imported key", .Length=strlen("Imported key")};
|
snej@14
|
344 |
CSSM_DATA descriptiveData = {};
|
snej@13
|
345 |
if (checkcssm(CSSM_UnwrapKey(ctx,
|
snej@13
|
346 |
self.cssmKey,
|
snej@13
|
347 |
&wrappedKey,
|
snej@13
|
348 |
wrappedKey.KeyHeader.KeyUsage,
|
snej@13
|
349 |
wrappedKey.KeyHeader.KeyAttr,
|
snej@14
|
350 |
&label,
|
snej@14
|
351 |
NULL,
|
snej@13
|
352 |
unwrappedKey,
|
snej@14
|
353 |
&descriptiveData),
|
snej@13
|
354 |
@"CSSM_UnwrapKey")) {
|
snej@13
|
355 |
result = [[[MYSymmetricKey alloc] _initWithCSSMKey: unwrappedKey] autorelease];
|
snej@13
|
356 |
}
|
snej@13
|
357 |
// Finally, delete the context
|
snej@14
|
358 |
if (!result)
|
snej@14
|
359 |
free(unwrappedKey);
|
snej@13
|
360 |
CSSM_DeleteContext(ctx);
|
snej@13
|
361 |
return result;
|
snej@13
|
362 |
}
|
snej@13
|
363 |
|
snej@13
|
364 |
|
snej@13
|
365 |
#endif !TARGET_OS_IPHONE
|
snej@3
|
366 |
|
snej@3
|
367 |
@end
|
snej@14
|
368 |
|
snej@14
|
369 |
|
snej@14
|
370 |
|
snej@14
|
371 |
/*
|
snej@14
|
372 |
Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
|
snej@14
|
373 |
|
snej@14
|
374 |
Redistribution and use in source and binary forms, with or without modification, are permitted
|
snej@14
|
375 |
provided that the following conditions are met:
|
snej@14
|
376 |
|
snej@14
|
377 |
* Redistributions of source code must retain the above copyright notice, this list of conditions
|
snej@14
|
378 |
and the following disclaimer.
|
snej@14
|
379 |
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
snej@14
|
380 |
and the following disclaimer in the documentation and/or other materials provided with the
|
snej@14
|
381 |
distribution.
|
snej@14
|
382 |
|
snej@14
|
383 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
snej@14
|
384 |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
snej@14
|
385 |
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
|
snej@14
|
386 |
BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
snej@14
|
387 |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
snej@14
|
388 |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
snej@14
|
389 |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
snej@14
|
390 |
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
snej@14
|
391 |
*/
|