xref: /illumos-gate/usr/src/lib/pkcs11/libpkcs11/common/pkcs11SUNWExtensions.c (revision 07eb1aef88b873c5c1036d9cf69820c1ef6a32fb)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 /*
26  * Solaris specific functions to reduce the initialization
27  * overhead of using PKCS #11
28  */
29 
30 #include <stdlib.h>
31 #include <sys/types.h>
32 #include <security/cryptoki.h>
33 #include <assert.h>
34 #include <cryptoutil.h>
35 #include <pkcs11Global.h>
36 
37 static CK_OBJECT_CLASS objclass = CKO_SECRET_KEY;
38 static CK_BBOOL falsevalue = FALSE;
39 static CK_BBOOL truevalue = TRUE;
40 
41 #define	NUM_SECRETKEY_ATTRS	12
42 
43 typedef struct _ATTRTYPE_MECHINFO_MAPPING {
44 	CK_ATTRIBUTE_TYPE attr;
45 	CK_FLAGS	flag;
46 } ATTRTYPE_MECHINFO_MAPPING;
47 
48 /* possible attribute types for creating key */
49 ATTRTYPE_MECHINFO_MAPPING mapping[] = {
50 	{CKA_ENCRYPT, CKF_ENCRYPT},
51 	{CKA_DECRYPT, CKF_DECRYPT},
52 	{CKA_SIGN, CKF_SIGN},
53 	{CKA_VERIFY, CKF_VERIFY},
54 	{CKA_WRAP, CKF_WRAP},
55 	{CKA_UNWRAP, CKF_UNWRAP}
56 };
57 
58 
59 /*
60  * List of mechanisms that only supports asymmetric key operations
61  * in PKCS #11 V2.11
62  */
63 CK_MECHANISM_TYPE asymmetric_mechs[] = {
64 	CKM_RSA_PKCS_KEY_PAIR_GEN, CKM_RSA_PKCS, CKM_RSA_9796, CKM_RSA_X_509,
65 	CKM_RSA_PKCS_OAEP, CKM_RSA_X9_31_KEY_PAIR_GEN, CKM_RSA_X9_31,
66 	CKM_RSA_PKCS_PSS, CKM_DSA_KEY_PAIR_GEN, CKM_DSA, CKM_DSA_SHA1,
67 	CKM_DSA_PARAMETER_GEN, CKM_ECDSA_KEY_PAIR_GEN, CKM_EC_KEY_PAIR_GEN,
68 	CKM_ECDSA, CKM_ECDSA_SHA1, CKM_ECDH1_DERIVE,
69 	CKM_ECDH1_COFACTOR_DERIVE, CKM_ECMQV_DERIVE
70 };
71 
72 
73 typedef struct _KEY_TYPE_SIZE_MAPPING {
74 	CK_KEY_TYPE type;
75 	CK_ULONG len;
76 } KEY_TYPE_SIZE_MAPPING;
77 
78 /*
79  * List of secret key types that have fixed sizes and their sizes.
80  * These key types do not allow CKA_VALUE_LEN for key generation.
81  * The sizes are in bytes.
82  *
83  * Discrete-sized keys, such as AES and Twofish, and variable sized
84  * keys, such as Blowfish, are not in this list.
85  */
86 KEY_TYPE_SIZE_MAPPING fixed_size_secrets[] = {
87 	{CKK_DES, 8}, {CKK_DES2, 16}, {CKK_DES3, 24}, {CKK_IDEA, 16},
88 	{CKK_CDMF, 8}, {CKK_SKIPJACK, 12}, {CKK_BATON, 40}, {CKK_JUNIPER, 40}
89 };
90 
91 /*
92  * match_mech is an example of many possible criteria functions.
93  * It matches the given mech type (in args) with the slot's mech info.
94  * If no match is found, pkcs11_GetCriteriaSession is asked to return
95  * CKR_MECHANISM_INVALID.
96  */
97 boolean_t
match_mech(CK_SLOT_ID slot_id,void * args,CK_RV * rv)98 match_mech(CK_SLOT_ID slot_id, void *args, CK_RV *rv)
99 {
100 	CK_MECHANISM_INFO mech_info;
101 	CK_MECHANISM_TYPE mech = (CK_MECHANISM_TYPE)args;
102 
103 	*rv = CKR_MECHANISM_INVALID;
104 	return (C_GetMechanismInfo(slot_id, mech, &mech_info) == CKR_OK);
105 }
106 
107 /*
108  * pkcs11_GetCriteriaSession will initialize the framework and do all
109  * the necessary work of calling C_GetSlotList(), C_GetMechanismInfo()
110  * C_OpenSession() to create a session that meets all the criteria in
111  * the given function pointer.
112  *
113  * The criteria function must return a boolean value of true or false.
114  * The arguments to the function are the current slot id, an opaque
115  * args value that is passed through to the function, and the error
116  * value pkcs11_GetCriteriaSession should return if no slot id meets the
117  * criteria.
118  *
119  * If the function is called multiple times, it will return a new session
120  * without reinitializing the framework.
121  */
122 CK_RV
pkcs11_GetCriteriaSession(boolean_t (* criteria)(CK_SLOT_ID slot_id,void * args,CK_RV * rv),void * args,CK_SESSION_HANDLE_PTR hSession)123 pkcs11_GetCriteriaSession(
124     boolean_t (*criteria)(CK_SLOT_ID slot_id, void *args, CK_RV *rv),
125     void *args, CK_SESSION_HANDLE_PTR hSession)
126 {
127 	CK_RV rv;
128 	CK_ULONG slotcount;
129 	CK_SLOT_ID_PTR slot_list;
130 	CK_SLOT_ID slot_id;
131 	CK_ULONG i;
132 
133 	if (hSession == NULL || criteria == NULL) {
134 		return (CKR_ARGUMENTS_BAD);
135 	}
136 
137 	/* initialize PKCS #11 */
138 	if (!pkcs11_initialized) {
139 		rv = C_Initialize(NULL);
140 		if ((rv != CKR_OK) &&
141 		    (rv != CKR_CRYPTOKI_ALREADY_INITIALIZED)) {
142 			return (rv);
143 		}
144 	}
145 
146 	/* get slot count */
147 	rv = C_GetSlotList(0, NULL, &slotcount);
148 	if (rv != CKR_OK) {
149 		return (rv);
150 	}
151 
152 	if (slotcount == 0) {
153 		return (CKR_FUNCTION_FAILED);
154 	}
155 
156 
157 	/* allocate memory for slot list */
158 	slot_list = malloc(slotcount * sizeof (CK_SLOT_ID));
159 	if (slot_list == NULL) {
160 		return (CKR_HOST_MEMORY);
161 	}
162 
163 	if ((rv = C_GetSlotList(0, slot_list, &slotcount)) != CKR_OK) {
164 		free(slot_list);
165 		return (rv);
166 	}
167 
168 	/* find slot with matching criteria */
169 	for (i = 0; i < slotcount; i++) {
170 		slot_id = slot_list[i];
171 		if ((*criteria)(slot_id, args, &rv)) {
172 			break;
173 		}
174 	}
175 
176 	if (i == slotcount) {
177 		/* no matching slot found */
178 		free(slot_list);
179 		return (rv);	/* this rv is from the criteria function */
180 	}
181 
182 	rv = C_OpenSession(slot_id, CKF_SERIAL_SESSION, NULL,
183 	    NULL, hSession);
184 
185 	free(slot_list);
186 	return (rv);
187 }
188 
189 /*
190  * SUNW_C_GetMechSession will initialize the framework and do all
191  * of the neccessary work of calling C_GetSlotList(), C_GetMechanismInfo()
192  * C_OpenSession() to create a session capable of providing the requested
193  * mechanism.
194  *
195  * If the function is called multiple times, it will return a new session
196  * without reinitializing the framework.
197  */
198 CK_RV
SUNW_C_GetMechSession(CK_MECHANISM_TYPE mech,CK_SESSION_HANDLE_PTR hSession)199 SUNW_C_GetMechSession(CK_MECHANISM_TYPE mech, CK_SESSION_HANDLE_PTR hSession)
200 {
201 	/*
202 	 * All the code in this function can be replaced with one line:
203 	 *
204 	 * return (pkcs11_GetCriteriaSession(match_mech, (void *)mech,
205 	 *	hSession));
206 	 *
207 	 */
208 	CK_RV rv;
209 	CK_ULONG slotcount;
210 	CK_SLOT_ID_PTR slot_list;
211 	CK_SLOT_ID slot_id;
212 	CK_MECHANISM_INFO mech_info;
213 	CK_ULONG i;
214 
215 	if (hSession == NULL) {
216 		return (CKR_ARGUMENTS_BAD);
217 	}
218 
219 	/* initialize PKCS #11 */
220 	if (!pkcs11_initialized) {
221 		rv = C_Initialize(NULL);
222 		if ((rv != CKR_OK) &&
223 		    (rv != CKR_CRYPTOKI_ALREADY_INITIALIZED)) {
224 			return (rv);
225 		}
226 	}
227 
228 	/* get slot count */
229 	rv = C_GetSlotList(0, NULL, &slotcount);
230 	if (rv != CKR_OK) {
231 		return (rv);
232 	}
233 
234 	if (slotcount == 0) {
235 		return (CKR_FUNCTION_FAILED);
236 	}
237 
238 
239 	/* allocate memory for slot list */
240 	slot_list = malloc(slotcount * sizeof (CK_SLOT_ID));
241 	if (slot_list == NULL) {
242 		return (CKR_HOST_MEMORY);
243 	}
244 
245 	if ((rv = C_GetSlotList(0, slot_list, &slotcount)) != CKR_OK) {
246 		free(slot_list);
247 		return (rv);
248 	}
249 
250 	/* find slot with matching mechanism */
251 	for (i = 0; i < slotcount; i++) {
252 		slot_id = slot_list[i];
253 		if (C_GetMechanismInfo(slot_id, mech, &mech_info) == CKR_OK) {
254 			/* found mechanism */
255 			break;
256 		}
257 	}
258 
259 	if (i == slotcount) {
260 		/* no matching mechanism found */
261 		free(slot_list);
262 		return (CKR_MECHANISM_INVALID);
263 	}
264 
265 	rv = C_OpenSession(slot_id, CKF_SERIAL_SESSION, NULL,
266 	    NULL, hSession);
267 
268 	free(slot_list);
269 	return (rv);
270 }
271 
272 /*
273  * SUNW_C_KeyToObject creates a secret key object for the given
274  * mechanism from the rawkey data.
275  */
276 CK_RV
SUNW_C_KeyToObject(CK_SESSION_HANDLE hSession,CK_MECHANISM_TYPE mech,const void * rawkey,size_t rawkey_len,CK_OBJECT_HANDLE_PTR obj)277 SUNW_C_KeyToObject(CK_SESSION_HANDLE hSession, CK_MECHANISM_TYPE mech,
278     const void *rawkey, size_t rawkey_len, CK_OBJECT_HANDLE_PTR obj)
279 {
280 
281 	CK_RV rv;
282 	CK_SESSION_INFO session_info;
283 	CK_SLOT_ID slot_id;
284 	CK_MECHANISM_INFO mech_info;
285 	CK_ULONG i, j;
286 	CK_KEY_TYPE keytype;
287 	CK_ULONG num_asym_mechs, num_mapping;
288 
289 	/* template for creating generic secret key object */
290 	CK_ATTRIBUTE template[NUM_SECRETKEY_ATTRS];
291 
292 	if ((hSession == CK_INVALID_HANDLE) || (obj == NULL) ||
293 	    (rawkey == NULL) || (rawkey_len == 0)) {
294 		return (CKR_ARGUMENTS_BAD);
295 	}
296 
297 	/*
298 	 * Check to make sure mechanism type is not for asymmetric key
299 	 * only operations.  This function is only applicable to
300 	 * generating secret key.
301 	 */
302 	num_asym_mechs = sizeof (asymmetric_mechs) / sizeof (CK_MECHANISM_TYPE);
303 	for (i = 0; i < num_asym_mechs; i++) {
304 		if (mech == asymmetric_mechs[i]) {
305 			return (CKR_MECHANISM_INVALID);
306 		}
307 	}
308 
309 	rv = C_GetSessionInfo(hSession, &session_info);
310 	if (rv != CKR_OK) {
311 		return (rv);
312 	}
313 
314 	slot_id = session_info.slotID;
315 
316 	i = 0;
317 	template[i].type = CKA_CLASS;
318 	template[i].pValue = &objclass;
319 	template[i].ulValueLen = sizeof (objclass);
320 	i++;
321 
322 	/* get the key type for this mechanism */
323 	if ((rv = pkcs11_mech2keytype(mech, &keytype)) != CKR_OK) {
324 		return (rv);
325 	}
326 
327 	assert(i < NUM_SECRETKEY_ATTRS);
328 	template[i].type = CKA_KEY_TYPE;
329 	template[i].pValue = &keytype;
330 	template[i].ulValueLen = sizeof (keytype);
331 	i++;
332 
333 	rv = C_GetMechanismInfo(slot_id, mech, &mech_info);
334 	if (rv != CKR_OK) {
335 		return (rv);
336 	}
337 
338 	/* set the attribute type flag on object based on mechanism */
339 	num_mapping = sizeof (mapping) / sizeof (ATTRTYPE_MECHINFO_MAPPING);
340 	for (j = 0; j < num_mapping; j++) {
341 		assert(i < NUM_SECRETKEY_ATTRS);
342 		template[i].type = mapping[j].attr;
343 		template[i].ulValueLen = sizeof (falsevalue);
344 		if (mech_info.flags & ((mapping[j]).flag)) {
345 			template[i].pValue = &truevalue;
346 		} else {
347 			template[i].pValue = &falsevalue;
348 		}
349 		i++;
350 	}
351 
352 	assert(i < NUM_SECRETKEY_ATTRS);
353 	template[i].type = CKA_TOKEN;
354 	template[i].pValue = &falsevalue;
355 	template[i].ulValueLen = sizeof (falsevalue);
356 	i++;
357 
358 	assert(i < NUM_SECRETKEY_ATTRS);
359 	template[i].type = CKA_VALUE;
360 	template[i].pValue = (CK_VOID_PTR)rawkey;
361 	template[i].ulValueLen = (CK_ULONG)rawkey_len;
362 	i++;
363 
364 	rv = C_CreateObject(hSession, template, i, obj);
365 	return (rv);
366 }
367 
368 
369 /*
370  * pkcs11_PasswdToPBKD2Object will create a secret key from the given string
371  * (e.g. passphrase) using PKCS#5 Password-Based Key Derivation Function 2
372  * (PBKD2).
373  *
374  * Session must be open.  Salt and iterations use defaults.
375  */
376 CK_RV
pkcs11_PasswdToPBKD2Object(CK_SESSION_HANDLE hSession,char * passphrase,size_t passphrase_len,void * salt,size_t salt_len,CK_ULONG iterations,CK_KEY_TYPE key_type,CK_ULONG key_len,CK_FLAGS key_flags,CK_OBJECT_HANDLE_PTR obj)377 pkcs11_PasswdToPBKD2Object(CK_SESSION_HANDLE hSession, char *passphrase,
378     size_t passphrase_len, void *salt, size_t salt_len, CK_ULONG iterations,
379     CK_KEY_TYPE key_type, CK_ULONG key_len, CK_FLAGS key_flags,
380     CK_OBJECT_HANDLE_PTR obj)
381 {
382 	CK_RV rv;
383 	CK_PKCS5_PBKD2_PARAMS params;
384 	CK_MECHANISM mechanism;
385 	CK_KEY_TYPE asym_key_type;
386 	CK_ULONG i, j, num_asym_mechs, num_fixed_secs, num_mapping;
387 	CK_ATTRIBUTE template[NUM_SECRETKEY_ATTRS];
388 
389 	if (hSession == CK_INVALID_HANDLE || obj == NULL ||
390 	    passphrase == NULL || passphrase_len == 0 ||
391 	    iterations == 0UL) {
392 		return (CKR_ARGUMENTS_BAD);
393 	}
394 
395 	/*
396 	 * Check to make sure key type is not asymmetric.  This function
397 	 * is only applicable to generating secret key.
398 	 */
399 	num_asym_mechs = sizeof (asymmetric_mechs) / sizeof (CK_MECHANISM_TYPE);
400 	for (i = 0; i < num_asym_mechs; i++) {
401 		rv = pkcs11_mech2keytype(asymmetric_mechs[i], &asym_key_type);
402 		assert(rv == CKR_OK);
403 		if (key_type == asym_key_type) {
404 			return (CKR_KEY_TYPE_INCONSISTENT);
405 		}
406 	}
407 
408 	/*
409 	 * Key length must either be 0 or the correct size for PBKD of
410 	 * fixed-size secret keys.  However, underlying key generation
411 	 * cannot have CKA_VALUE_LEN set for the key length attribute.
412 	 */
413 	num_fixed_secs =
414 	    sizeof (fixed_size_secrets) / sizeof (KEY_TYPE_SIZE_MAPPING);
415 	for (i = 0; i < num_fixed_secs; i++) {
416 		if (key_type == fixed_size_secrets[i].type) {
417 			if (key_len == fixed_size_secrets[i].len) {
418 				key_len = 0;
419 			}
420 			if (key_len == 0) {
421 				break;
422 			}
423 			return (CKR_KEY_SIZE_RANGE);
424 		}
425 	}
426 
427 	if (salt == NULL || salt_len == 0) {
428 		params.saltSource = 0;
429 		params.pSaltSourceData = NULL;
430 		params.ulSaltSourceDataLen = 0;
431 	} else {
432 		params.saltSource = CKZ_SALT_SPECIFIED;
433 		params.pSaltSourceData = salt;
434 		params.ulSaltSourceDataLen = salt_len;
435 	}
436 	params.iterations = iterations;
437 	params.prf = CKP_PKCS5_PBKD2_HMAC_SHA1;
438 	params.pPrfData = NULL;
439 	params.ulPrfDataLen = 0;
440 	params.pPassword = (CK_UTF8CHAR_PTR)passphrase;
441 	params.ulPasswordLen = (CK_ULONG_PTR)&passphrase_len;
442 	/*
443 	 * PKCS#11 spec error, ulPasswordLen should have been pulPasswordLen,
444 	 * or its type should have been CK_ULONG instead of CK_ULONG_PTR,
445 	 * but it's legacy now
446 	 */
447 
448 	mechanism.mechanism = CKM_PKCS5_PBKD2;
449 	mechanism.pParameter = &params;
450 	mechanism.ulParameterLen = sizeof (params);
451 
452 	i = 0;
453 	template[i].type = CKA_CLASS;
454 	template[i].pValue = &objclass;
455 	template[i].ulValueLen = sizeof (objclass);
456 	i++;
457 
458 	assert(i < NUM_SECRETKEY_ATTRS);
459 	template[i].type = CKA_KEY_TYPE;
460 	template[i].pValue = &key_type;
461 	template[i].ulValueLen = sizeof (key_type);
462 	i++;
463 
464 	assert(i < NUM_SECRETKEY_ATTRS);
465 	template[i].type = CKA_TOKEN;
466 	template[i].pValue = &falsevalue;
467 	template[i].ulValueLen = sizeof (falsevalue);
468 	i++;
469 
470 	if (key_len != 0) {
471 		assert(i < NUM_SECRETKEY_ATTRS);
472 		template[i].type = CKA_VALUE_LEN;
473 		template[i].pValue = &key_len;
474 		template[i].ulValueLen = sizeof (key_len);
475 		i++;
476 	}
477 
478 	/*
479 	 * C_GenerateKey may not implicitly set capability attributes,
480 	 * e.g. CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, ...
481 	 */
482 	num_mapping = sizeof (mapping) / sizeof (ATTRTYPE_MECHINFO_MAPPING);
483 	for (j = 0; j < num_mapping; j++) {
484 		assert(i < NUM_SECRETKEY_ATTRS);
485 		template[i].type = mapping[j].attr;
486 		template[i].pValue = (key_flags & ((mapping[j]).flag)) ?
487 		    &truevalue : &falsevalue;
488 		template[i].ulValueLen = sizeof (falsevalue);
489 		i++;
490 	}
491 
492 	rv = C_GenerateKey(hSession, &mechanism, template, i, obj);
493 	return (rv);
494 }
495 
496 /*
497  * pkcs11_ObjectToKey gets the rawkey data from a secret key object.
498  * The caller is responsible to free the allocated rawkey data.
499  *
500  * Optionally the object can be destroyed after the value is retrieved.
501  * As an example, after using pkcs11_PasswdToPBKD2Object() to create a
502  * secret key object from a passphrase, an app may call pkcs11_ObjectToKey
503  * to get the rawkey data.  The intermediate object may no longer be needed
504  * and should be destroyed.
505  */
506 CK_RV
pkcs11_ObjectToKey(CK_SESSION_HANDLE hSession,CK_OBJECT_HANDLE obj,void ** rawkey,size_t * rawkey_len,boolean_t destroy_obj)507 pkcs11_ObjectToKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE obj,
508     void **rawkey, size_t *rawkey_len, boolean_t destroy_obj)
509 {
510 	CK_RV rv;
511 	CK_ATTRIBUTE template;
512 
513 	if (hSession == CK_INVALID_HANDLE)
514 		return (CKR_SESSION_HANDLE_INVALID);
515 	if (obj == 0)
516 		return (CKR_OBJECT_HANDLE_INVALID);
517 	if (rawkey == NULL || rawkey_len == NULL)
518 		return (CKR_ARGUMENTS_BAD);
519 
520 	template.type = CKA_VALUE;
521 	template.pValue = NULL;
522 	template.ulValueLen = 0;
523 
524 	/* First get the size of the rawkey */
525 	rv = C_GetAttributeValue(hSession, obj, &template, 1);
526 	if (rv != CKR_OK) {
527 		return (rv);
528 	}
529 
530 	template.pValue = malloc(template.ulValueLen);
531 	if (template.pValue == NULL) {
532 		return (CKR_HOST_MEMORY);
533 	}
534 
535 	/* Then get the rawkey data */
536 	rv = C_GetAttributeValue(hSession, obj, &template, 1);
537 	if (rv != CKR_OK) {
538 		free(template.pValue);
539 		return (rv);
540 	}
541 
542 	if (destroy_obj) {
543 		/*
544 		 * Could have asserted rv == CKR_OK, making threaded
545 		 * apps that share objects see stars.  Here mercy is ok.
546 		 */
547 		(void) C_DestroyObject(hSession, obj);
548 	}
549 
550 	*rawkey = template.pValue;
551 	*rawkey_len = template.ulValueLen;
552 
553 	return (CKR_OK);
554 }
555 
556 /*
557  * pkcs11_PasswdToKey will create PKCS#5 PBKD2 rawkey data from the
558  * given passphrase.  The caller is responsible to free the allocated
559  * rawkey data.
560  */
561 CK_RV
pkcs11_PasswdToKey(CK_SESSION_HANDLE hSession,char * passphrase,size_t passphrase_len,void * salt,size_t salt_len,CK_KEY_TYPE key_type,CK_ULONG key_len,void ** rawkey,size_t * rawkey_len)562 pkcs11_PasswdToKey(CK_SESSION_HANDLE hSession, char *passphrase,
563     size_t passphrase_len, void *salt, size_t salt_len, CK_KEY_TYPE key_type,
564     CK_ULONG key_len, void **rawkey, size_t *rawkey_len)
565 {
566 	CK_RV rv;
567 	CK_OBJECT_HANDLE obj;
568 
569 	rv = pkcs11_PasswdToPBKD2Object(hSession, passphrase, passphrase_len,
570 	    salt, salt_len, CK_PKCS5_PBKD2_ITERATIONS, key_type, key_len, 0,
571 	    &obj);
572 	if (rv != CKR_OK)
573 		return (rv);
574 	rv = pkcs11_ObjectToKey(hSession, obj, rawkey, rawkey_len, B_TRUE);
575 	return (rv);
576 }
577