|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
Packit |
40b132 |
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
Packit |
40b132 |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
* This file PKCS #12 fuctions that should really be moved to the
|
|
Packit |
40b132 |
* PKCS #12 directory, however we can't do that in a point release
|
|
Packit |
40b132 |
* because that will break binary compatibility, so we keep them here for now.
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
#include "seccomon.h"
|
|
Packit |
40b132 |
#include "secmod.h"
|
|
Packit |
40b132 |
#include "secmodi.h"
|
|
Packit |
40b132 |
#include "pkcs11.h"
|
|
Packit |
40b132 |
#include "pk11func.h"
|
|
Packit |
40b132 |
#include "secitem.h"
|
|
Packit |
40b132 |
#include "key.h"
|
|
Packit |
40b132 |
#include "secoid.h"
|
|
Packit |
40b132 |
#include "secasn1.h"
|
|
Packit |
40b132 |
#include "secerr.h"
|
|
Packit |
40b132 |
#include "prerror.h"
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* These data structures should move to a common .h file shared between the
|
|
Packit |
40b132 |
* wrappers and the pkcs 12 code. */
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
** RSA Raw Private Key structures
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* member names from PKCS#1, section 7.2 */
|
|
Packit |
40b132 |
struct SECKEYRSAPrivateKeyStr {
|
|
Packit |
40b132 |
PLArenaPool * arena;
|
|
Packit |
40b132 |
SECItem version;
|
|
Packit |
40b132 |
SECItem modulus;
|
|
Packit |
40b132 |
SECItem publicExponent;
|
|
Packit |
40b132 |
SECItem privateExponent;
|
|
Packit |
40b132 |
SECItem prime1;
|
|
Packit |
40b132 |
SECItem prime2;
|
|
Packit |
40b132 |
SECItem exponent1;
|
|
Packit |
40b132 |
SECItem exponent2;
|
|
Packit |
40b132 |
SECItem coefficient;
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
typedef struct SECKEYRSAPrivateKeyStr SECKEYRSAPrivateKey;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
** DSA Raw Private Key structures
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
struct SECKEYDSAPrivateKeyStr {
|
|
Packit |
40b132 |
SECKEYPQGParams params;
|
|
Packit |
40b132 |
SECItem privateValue;
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
typedef struct SECKEYDSAPrivateKeyStr SECKEYDSAPrivateKey;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
** Diffie-Hellman Raw Private Key structures
|
|
Packit |
40b132 |
** Structure member names suggested by PKCS#3.
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
struct SECKEYDHPrivateKeyStr {
|
|
Packit |
40b132 |
PLArenaPool * arena;
|
|
Packit |
40b132 |
SECItem prime;
|
|
Packit |
40b132 |
SECItem base;
|
|
Packit |
40b132 |
SECItem privateValue;
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
typedef struct SECKEYDHPrivateKeyStr SECKEYDHPrivateKey;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
** raw private key object
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
struct SECKEYRawPrivateKeyStr {
|
|
Packit |
40b132 |
PLArenaPool *arena;
|
|
Packit |
40b132 |
KeyType keyType;
|
|
Packit |
40b132 |
union {
|
|
Packit |
40b132 |
SECKEYRSAPrivateKey rsa;
|
|
Packit |
40b132 |
SECKEYDSAPrivateKey dsa;
|
|
Packit |
40b132 |
SECKEYDHPrivateKey dh;
|
|
Packit |
40b132 |
} u;
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
typedef struct SECKEYRawPrivateKeyStr SECKEYRawPrivateKey;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SEC_ASN1_MKSUB(SEC_AnyTemplate)
|
|
Packit |
40b132 |
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* ASN1 Templates for new decoder/encoder */
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
* Attribute value for PKCS8 entries (static?)
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_AttributeTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_SEQUENCE,
|
|
Packit |
40b132 |
0, NULL, sizeof(SECKEYAttribute) },
|
|
Packit |
40b132 |
{ SEC_ASN1_OBJECT_ID, offsetof(SECKEYAttribute, attrType) },
|
|
Packit |
40b132 |
{ SEC_ASN1_SET_OF | SEC_ASN1_XTRN, offsetof(SECKEYAttribute, attrValue),
|
|
Packit |
40b132 |
SEC_ASN1_SUB(SEC_AnyTemplate) },
|
|
Packit |
40b132 |
{ 0 }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_SetOfAttributeTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_SET_OF, 0, SECKEY_AttributeTemplate },
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_PrivateKeyInfoTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPrivateKeyInfo) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYPrivateKeyInfo,version) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
|
|
Packit |
40b132 |
offsetof(SECKEYPrivateKeyInfo,algorithm),
|
|
Packit |
40b132 |
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
|
|
Packit |
40b132 |
{ SEC_ASN1_OCTET_STRING, offsetof(SECKEYPrivateKeyInfo,privateKey) },
|
|
Packit |
40b132 |
{ SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
|
|
Packit |
40b132 |
offsetof(SECKEYPrivateKeyInfo,attributes),
|
|
Packit |
40b132 |
SECKEY_SetOfAttributeTemplate },
|
|
Packit |
40b132 |
{ 0 }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_PointerToPrivateKeyInfoTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_POINTER, 0, SECKEY_PrivateKeyInfoTemplate }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_RSAPrivateKeyExportTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYRawPrivateKey) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.version) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.modulus) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.publicExponent) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.privateExponent) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.prime1) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.prime2) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.exponent1) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.exponent2) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.rsa.coefficient) },
|
|
Packit |
40b132 |
{ 0 }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_DSAPrivateKeyExportTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.dsa.privateValue) },
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_DHPrivateKeyExportTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.dh.privateValue) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.dh.base) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey,u.dh.prime) },
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_EncryptedPrivateKeyInfoTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_SEQUENCE,
|
|
Packit |
40b132 |
0, NULL, sizeof(SECKEYEncryptedPrivateKeyInfo) },
|
|
Packit |
40b132 |
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
|
|
Packit |
40b132 |
offsetof(SECKEYEncryptedPrivateKeyInfo,algorithm),
|
|
Packit |
40b132 |
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
|
|
Packit |
40b132 |
{ SEC_ASN1_OCTET_STRING,
|
|
Packit |
40b132 |
offsetof(SECKEYEncryptedPrivateKeyInfo,encryptedData) },
|
|
Packit |
40b132 |
{ 0 }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
const SEC_ASN1Template SECKEY_PointerToEncryptedPrivateKeyInfoTemplate[] = {
|
|
Packit |
40b132 |
{ SEC_ASN1_POINTER, 0, SECKEY_EncryptedPrivateKeyInfoTemplate }
|
|
Packit |
40b132 |
};
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_EncryptedPrivateKeyInfoTemplate)
|
|
Packit |
40b132 |
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PointerToEncryptedPrivateKeyInfoTemplate)
|
|
Packit |
40b132 |
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PrivateKeyInfoTemplate)
|
|
Packit |
40b132 |
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PointerToPrivateKeyInfoTemplate)
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
* See bugzilla bug 125359
|
|
Packit |
40b132 |
* Since NSS (via PKCS#11) wants to handle big integers as unsigned ints,
|
|
Packit |
40b132 |
* all of the templates above that en/decode into integers must be converted
|
|
Packit |
40b132 |
* from ASN.1's signed integer type. This is done by marking either the
|
|
Packit |
40b132 |
* source or destination (encoding or decoding, respectively) type as
|
|
Packit |
40b132 |
* siUnsignedInteger.
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
static void
|
|
Packit |
40b132 |
prepare_rsa_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
key->u.rsa.modulus.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.publicExponent.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.privateExponent.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.prime1.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.prime2.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.exponent1.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.exponent2.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.rsa.coefficient.type = siUnsignedInteger;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
static void
|
|
Packit |
40b132 |
prepare_dsa_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
key->u.dsa.privateValue.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.dsa.params.prime.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.dsa.params.subPrime.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.dsa.params.base.type = siUnsignedInteger;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
static void
|
|
Packit |
40b132 |
prepare_dh_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
key->u.dh.privateValue.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.dh.prime.type = siUnsignedInteger;
|
|
Packit |
40b132 |
key->u.dh.base.type = siUnsignedInteger;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECStatus
|
|
Packit |
40b132 |
PK11_ImportDERPrivateKeyInfo(PK11SlotInfo *slot, SECItem *derPKI,
|
|
Packit |
40b132 |
SECItem *nickname, SECItem *publicValue, PRBool isPerm,
|
|
Packit |
40b132 |
PRBool isPrivate, unsigned int keyUsage, void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
return PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, derPKI,
|
|
Packit |
40b132 |
nickname, publicValue, isPerm, isPrivate, keyUsage, NULL, wincx);
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECStatus
|
|
Packit |
40b132 |
PK11_ImportDERPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot, SECItem *derPKI,
|
|
Packit |
40b132 |
SECItem *nickname, SECItem *publicValue, PRBool isPerm,
|
|
Packit |
40b132 |
PRBool isPrivate, unsigned int keyUsage, SECKEYPrivateKey** privk,
|
|
Packit |
40b132 |
void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
SECKEYPrivateKeyInfo *pki = NULL;
|
|
Packit |
40b132 |
PLArenaPool *temparena = NULL;
|
|
Packit |
40b132 |
SECStatus rv = SECFailure;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
temparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
Packit |
40b132 |
if (!temparena)
|
|
Packit |
40b132 |
return rv;
|
|
Packit |
40b132 |
pki = PORT_ArenaZNew(temparena, SECKEYPrivateKeyInfo);
|
|
Packit |
40b132 |
if (!pki) {
|
|
Packit |
40b132 |
PORT_FreeArena(temparena, PR_FALSE);
|
|
Packit |
40b132 |
return rv;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
pki->arena = temparena;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
rv = SEC_ASN1DecodeItem(pki->arena, pki, SECKEY_PrivateKeyInfoTemplate,
|
|
Packit |
40b132 |
derPKI);
|
|
Packit |
40b132 |
if( rv != SECSuccess ) {
|
|
Packit |
40b132 |
goto finish;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
rv = PK11_ImportPrivateKeyInfoAndReturnKey(slot, pki, nickname,
|
|
Packit |
40b132 |
publicValue, isPerm, isPrivate, keyUsage, privk, wincx);
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
finish:
|
|
Packit |
40b132 |
/* this zeroes the key and frees the arena */
|
|
Packit |
40b132 |
SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE /*freeit*/);
|
|
Packit |
40b132 |
return rv;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECStatus
|
|
Packit |
40b132 |
PK11_ImportAndReturnPrivateKey(PK11SlotInfo *slot, SECKEYRawPrivateKey *lpk,
|
|
Packit |
40b132 |
SECItem *nickname, SECItem *publicValue, PRBool isPerm,
|
|
Packit |
40b132 |
PRBool isPrivate, unsigned int keyUsage, SECKEYPrivateKey **privk,
|
|
Packit |
40b132 |
void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
CK_BBOOL cktrue = CK_TRUE;
|
|
Packit |
40b132 |
CK_BBOOL ckfalse = CK_FALSE;
|
|
Packit |
40b132 |
CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
|
|
Packit |
40b132 |
CK_KEY_TYPE keyType = CKK_RSA;
|
|
Packit |
40b132 |
CK_OBJECT_HANDLE objectID;
|
|
Packit |
40b132 |
CK_ATTRIBUTE theTemplate[20];
|
|
Packit |
40b132 |
int templateCount = 0;
|
|
Packit |
40b132 |
SECStatus rv = SECFailure;
|
|
Packit |
40b132 |
CK_ATTRIBUTE *attrs;
|
|
Packit |
40b132 |
CK_ATTRIBUTE *signedattr = NULL;
|
|
Packit |
40b132 |
int signedcount = 0;
|
|
Packit |
40b132 |
CK_ATTRIBUTE *ap;
|
|
Packit |
40b132 |
SECItem *ck_id = NULL;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
attrs = theTemplate;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_CLASS, &keyClass, sizeof(keyClass) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_KEY_TYPE, &keyType, sizeof(keyType) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_TOKEN, isPerm ? &cktrue : &ckfalse,
|
|
Packit |
40b132 |
sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_SENSITIVE, isPrivate ? &cktrue : &ckfalse,
|
|
Packit |
40b132 |
sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIVATE, isPrivate ? &cktrue : &ckfalse,
|
|
Packit |
40b132 |
sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
switch (lpk->keyType) {
|
|
Packit |
40b132 |
case rsaKey:
|
|
Packit |
40b132 |
keyType = CKK_RSA;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_UNWRAP, (keyUsage & KU_KEY_ENCIPHERMENT) ?
|
|
Packit |
40b132 |
&cktrue : &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_DECRYPT, (keyUsage & KU_DATA_ENCIPHERMENT) ?
|
|
Packit |
40b132 |
&cktrue : &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_SIGN, (keyUsage & KU_DIGITAL_SIGNATURE) ?
|
|
Packit |
40b132 |
&cktrue : &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_SIGN_RECOVER,
|
|
Packit |
40b132 |
(keyUsage & KU_DIGITAL_SIGNATURE) ?
|
|
Packit |
40b132 |
&cktrue : &ckfalse, sizeof(CK_BBOOL) ); attrs++;
|
|
Packit |
40b132 |
ck_id = PK11_MakeIDFromPubKey(&lpk->u.rsa.modulus);
|
|
Packit |
40b132 |
if (ck_id == NULL) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_ID, ck_id->data,ck_id->len); attrs++;
|
|
Packit |
40b132 |
if (nickname) {
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len); attrs++;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
signedattr = attrs;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_MODULUS, lpk->u.rsa.modulus.data,
|
|
Packit |
40b132 |
lpk->u.rsa.modulus.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PUBLIC_EXPONENT,
|
|
Packit |
40b132 |
lpk->u.rsa.publicExponent.data,
|
|
Packit |
40b132 |
lpk->u.rsa.publicExponent.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIVATE_EXPONENT,
|
|
Packit |
40b132 |
lpk->u.rsa.privateExponent.data,
|
|
Packit |
40b132 |
lpk->u.rsa.privateExponent.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIME_1,
|
|
Packit |
40b132 |
lpk->u.rsa.prime1.data,
|
|
Packit |
40b132 |
lpk->u.rsa.prime1.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIME_2,
|
|
Packit |
40b132 |
lpk->u.rsa.prime2.data,
|
|
Packit |
40b132 |
lpk->u.rsa.prime2.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_EXPONENT_1,
|
|
Packit |
40b132 |
lpk->u.rsa.exponent1.data,
|
|
Packit |
40b132 |
lpk->u.rsa.exponent1.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_EXPONENT_2,
|
|
Packit |
40b132 |
lpk->u.rsa.exponent2.data,
|
|
Packit |
40b132 |
lpk->u.rsa.exponent2.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_COEFFICIENT,
|
|
Packit |
40b132 |
lpk->u.rsa.coefficient.data,
|
|
Packit |
40b132 |
lpk->u.rsa.coefficient.len); attrs++;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
case dsaKey:
|
|
Packit |
40b132 |
keyType = CKK_DSA;
|
|
Packit |
40b132 |
/* To make our intenal PKCS #11 module work correctly with
|
|
Packit |
40b132 |
* our database, we need to pass in the public key value for
|
|
Packit |
40b132 |
* this dsa key. We have a netscape only CKA_ value to do this.
|
|
Packit |
40b132 |
* Only send it to internal slots */
|
|
Packit |
40b132 |
if( publicValue == NULL ) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
if (PK11_IsInternal(slot)) {
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_NETSCAPE_DB,
|
|
Packit |
40b132 |
publicValue->data, publicValue->len); attrs++;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_SIGN, &cktrue, sizeof(CK_BBOOL)); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_SIGN_RECOVER, &cktrue, sizeof(CK_BBOOL)); attrs++;
|
|
Packit |
40b132 |
if(nickname) {
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
|
|
Packit |
40b132 |
attrs++;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
ck_id = PK11_MakeIDFromPubKey(publicValue);
|
|
Packit |
40b132 |
if (ck_id == NULL) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_ID, ck_id->data,ck_id->len); attrs++;
|
|
Packit |
40b132 |
signedattr = attrs;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIME, lpk->u.dsa.params.prime.data,
|
|
Packit |
40b132 |
lpk->u.dsa.params.prime.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs,CKA_SUBPRIME,lpk->u.dsa.params.subPrime.data,
|
|
Packit |
40b132 |
lpk->u.dsa.params.subPrime.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_BASE, lpk->u.dsa.params.base.data,
|
|
Packit |
40b132 |
lpk->u.dsa.params.base.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.dsa.privateValue.data,
|
|
Packit |
40b132 |
lpk->u.dsa.privateValue.len); attrs++;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
case dhKey:
|
|
Packit |
40b132 |
keyType = CKK_DH;
|
|
Packit |
40b132 |
/* To make our intenal PKCS #11 module work correctly with
|
|
Packit |
40b132 |
* our database, we need to pass in the public key value for
|
|
Packit |
40b132 |
* this dh key. We have a netscape only CKA_ value to do this.
|
|
Packit |
40b132 |
* Only send it to internal slots */
|
|
Packit |
40b132 |
if (PK11_IsInternal(slot)) {
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_NETSCAPE_DB,
|
|
Packit |
40b132 |
publicValue->data, publicValue->len); attrs++;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_DERIVE, &cktrue, sizeof(CK_BBOOL)); attrs++;
|
|
Packit |
40b132 |
if(nickname) {
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
|
|
Packit |
40b132 |
attrs++;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
ck_id = PK11_MakeIDFromPubKey(publicValue);
|
|
Packit |
40b132 |
if (ck_id == NULL) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_ID, ck_id->data,ck_id->len); attrs++;
|
|
Packit |
40b132 |
signedattr = attrs;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_PRIME, lpk->u.dh.prime.data,
|
|
Packit |
40b132 |
lpk->u.dh.prime.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_BASE, lpk->u.dh.base.data,
|
|
Packit |
40b132 |
lpk->u.dh.base.len); attrs++;
|
|
Packit |
40b132 |
PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.dh.privateValue.data,
|
|
Packit |
40b132 |
lpk->u.dh.privateValue.len); attrs++;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
/* what about fortezza??? */
|
|
Packit |
40b132 |
default:
|
|
Packit |
40b132 |
PORT_SetError(SEC_ERROR_BAD_KEY);
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
templateCount = attrs - theTemplate;
|
|
Packit |
40b132 |
PORT_Assert(templateCount <= sizeof(theTemplate)/sizeof(CK_ATTRIBUTE));
|
|
Packit |
40b132 |
PORT_Assert(signedattr != NULL);
|
|
Packit |
40b132 |
signedcount = attrs - signedattr;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
for (ap=signedattr; signedcount; ap++, signedcount--) {
|
|
Packit |
40b132 |
pk11_SignedToUnsigned(ap);
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
rv = PK11_CreateNewObject(slot, CK_INVALID_SESSION,
|
|
Packit |
40b132 |
theTemplate, templateCount, isPerm, &objectID);
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* create and return a SECKEYPrivateKey */
|
|
Packit |
40b132 |
if( rv == SECSuccess && privk != NULL) {
|
|
Packit |
40b132 |
*privk = PK11_MakePrivKey(slot, lpk->keyType, !isPerm, objectID, wincx);
|
|
Packit |
40b132 |
if( *privk == NULL ) {
|
|
Packit |
40b132 |
rv = SECFailure;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
loser:
|
|
Packit |
40b132 |
if (ck_id) {
|
|
Packit |
40b132 |
SECITEM_ZfreeItem(ck_id, PR_TRUE);
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
return rv;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECStatus
|
|
Packit |
40b132 |
PK11_ImportPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot,
|
|
Packit |
40b132 |
SECKEYPrivateKeyInfo *pki, SECItem *nickname, SECItem *publicValue,
|
|
Packit |
40b132 |
PRBool isPerm, PRBool isPrivate, unsigned int keyUsage,
|
|
Packit |
40b132 |
SECKEYPrivateKey **privk, void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
CK_KEY_TYPE keyType = CKK_RSA;
|
|
Packit |
40b132 |
SECStatus rv = SECFailure;
|
|
Packit |
40b132 |
SECKEYRawPrivateKey *lpk = NULL;
|
|
Packit |
40b132 |
const SEC_ASN1Template *keyTemplate, *paramTemplate;
|
|
Packit |
40b132 |
void *paramDest = NULL;
|
|
Packit |
40b132 |
PLArenaPool *arena = NULL;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
arena = PORT_NewArena(2048);
|
|
Packit |
40b132 |
if(!arena) {
|
|
Packit |
40b132 |
return SECFailure;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* need to change this to use RSA/DSA keys */
|
|
Packit |
40b132 |
lpk = (SECKEYRawPrivateKey *)PORT_ArenaZAlloc(arena,
|
|
Packit |
40b132 |
sizeof(SECKEYRawPrivateKey));
|
|
Packit |
40b132 |
if(lpk == NULL) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
lpk->arena = arena;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
switch(SECOID_GetAlgorithmTag(&pki->algorithm)) {
|
|
Packit |
40b132 |
case SEC_OID_PKCS1_RSA_ENCRYPTION:
|
|
Packit |
40b132 |
prepare_rsa_priv_key_export_for_asn1(lpk);
|
|
Packit |
40b132 |
keyTemplate = SECKEY_RSAPrivateKeyExportTemplate;
|
|
Packit |
40b132 |
paramTemplate = NULL;
|
|
Packit |
40b132 |
paramDest = NULL;
|
|
Packit |
40b132 |
lpk->keyType = rsaKey;
|
|
Packit |
40b132 |
keyType = CKK_RSA;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
case SEC_OID_ANSIX9_DSA_SIGNATURE:
|
|
Packit |
40b132 |
prepare_dsa_priv_key_export_for_asn1(lpk);
|
|
Packit |
40b132 |
keyTemplate = SECKEY_DSAPrivateKeyExportTemplate;
|
|
Packit |
40b132 |
paramTemplate = SECKEY_PQGParamsTemplate;
|
|
Packit |
40b132 |
paramDest = &(lpk->u.dsa.params);
|
|
Packit |
40b132 |
lpk->keyType = dsaKey;
|
|
Packit |
40b132 |
keyType = CKK_DSA;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
case SEC_OID_X942_DIFFIE_HELMAN_KEY:
|
|
Packit |
40b132 |
if(!publicValue) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
prepare_dh_priv_key_export_for_asn1(lpk);
|
|
Packit |
40b132 |
keyTemplate = SECKEY_DHPrivateKeyExportTemplate;
|
|
Packit |
40b132 |
paramTemplate = NULL;
|
|
Packit |
40b132 |
paramDest = NULL;
|
|
Packit |
40b132 |
lpk->keyType = dhKey;
|
|
Packit |
40b132 |
keyType = CKK_DH;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
default:
|
|
Packit |
40b132 |
keyTemplate = NULL;
|
|
Packit |
40b132 |
paramTemplate = NULL;
|
|
Packit |
40b132 |
paramDest = NULL;
|
|
Packit |
40b132 |
break;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
if(!keyTemplate) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* decode the private key and any algorithm parameters */
|
|
Packit |
40b132 |
rv = SEC_ASN1DecodeItem(arena, lpk, keyTemplate, &pki->privateKey);
|
|
Packit |
40b132 |
if(rv != SECSuccess) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
if(paramDest && paramTemplate) {
|
|
Packit |
40b132 |
rv = SEC_ASN1DecodeItem(arena, paramDest, paramTemplate,
|
|
Packit |
40b132 |
&(pki->algorithm.parameters));
|
|
Packit |
40b132 |
if(rv != SECSuccess) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
rv = PK11_ImportAndReturnPrivateKey(slot,lpk,nickname,publicValue, isPerm,
|
|
Packit |
40b132 |
isPrivate, keyUsage, privk, wincx);
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
loser:
|
|
Packit |
40b132 |
if (arena != NULL) {
|
|
Packit |
40b132 |
PORT_FreeArena(arena, PR_TRUE);
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
return rv;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECStatus
|
|
Packit |
40b132 |
PK11_ImportPrivateKeyInfo(PK11SlotInfo *slot, SECKEYPrivateKeyInfo *pki,
|
|
Packit |
40b132 |
SECItem *nickname, SECItem *publicValue, PRBool isPerm,
|
|
Packit |
40b132 |
PRBool isPrivate, unsigned int keyUsage, void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
return PK11_ImportPrivateKeyInfoAndReturnKey(slot, pki, nickname,
|
|
Packit |
40b132 |
publicValue, isPerm, isPrivate, keyUsage, NULL, wincx);
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
SECItem *
|
|
Packit |
40b132 |
PK11_ExportDERPrivateKeyInfo(SECKEYPrivateKey *pk, void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
SECKEYPrivateKeyInfo *pki = PK11_ExportPrivKeyInfo(pk, wincx);
|
|
Packit |
40b132 |
SECItem *derPKI;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
if (!pki) {
|
|
Packit |
40b132 |
return NULL;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
derPKI = SEC_ASN1EncodeItem(NULL, NULL, pki,
|
|
Packit |
40b132 |
SECKEY_PrivateKeyInfoTemplate);
|
|
Packit |
40b132 |
SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE);
|
|
Packit |
40b132 |
return derPKI;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
static PRBool
|
|
Packit |
40b132 |
ReadAttribute(SECKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
|
|
Packit |
40b132 |
PLArenaPool *arena, SECItem *output)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
SECStatus rv = PK11_ReadAttribute(key->pkcs11Slot, key->pkcs11ID, type,
|
|
Packit |
40b132 |
arena, output);
|
|
Packit |
40b132 |
return rv == SECSuccess;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/*
|
|
Packit |
40b132 |
* The caller is responsible for freeing the return value by passing it to
|
|
Packit |
40b132 |
* SECKEY_DestroyPrivateKeyInfo(..., PR_TRUE).
|
|
Packit |
40b132 |
*/
|
|
Packit |
40b132 |
SECKEYPrivateKeyInfo *
|
|
Packit |
40b132 |
PK11_ExportPrivKeyInfo(SECKEYPrivateKey *pk, void *wincx)
|
|
Packit |
40b132 |
{
|
|
Packit |
40b132 |
/* PrivateKeyInfo version (always zero) */
|
|
Packit |
40b132 |
const unsigned char pkiVersion = 0;
|
|
Packit |
40b132 |
/* RSAPrivateKey version (always zero) */
|
|
Packit |
40b132 |
const unsigned char rsaVersion = 0;
|
|
Packit |
40b132 |
PLArenaPool *arena = NULL;
|
|
Packit |
40b132 |
SECKEYRawPrivateKey rawKey;
|
|
Packit |
40b132 |
SECKEYPrivateKeyInfo *pki;
|
|
Packit |
40b132 |
SECItem *encoded;
|
|
Packit |
40b132 |
SECStatus rv;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
if (pk->keyType != rsaKey) {
|
|
Packit |
40b132 |
PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
Packit |
40b132 |
if (!arena) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
memset(&rawKey, 0, sizeof(rawKey));
|
|
Packit |
40b132 |
rawKey.keyType = pk->keyType;
|
|
Packit |
40b132 |
rawKey.u.rsa.version.type = siUnsignedInteger;
|
|
Packit |
40b132 |
rawKey.u.rsa.version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
|
|
Packit |
40b132 |
if (!rawKey.u.rsa.version.data) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
rawKey.u.rsa.version.data[0] = rsaVersion;
|
|
Packit |
40b132 |
rawKey.u.rsa.version.len = 1;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
/* Read the component attributes of the private key */
|
|
Packit |
40b132 |
prepare_rsa_priv_key_export_for_asn1(&rawKey);
|
|
Packit |
40b132 |
if (!ReadAttribute(pk, CKA_MODULUS, arena, &rawKey.u.rsa.modulus) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_PUBLIC_EXPONENT, arena,
|
|
Packit |
40b132 |
&rawKey.u.rsa.publicExponent) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_PRIVATE_EXPONENT, arena,
|
|
Packit |
40b132 |
&rawKey.u.rsa.privateExponent) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_PRIME_1, arena, &rawKey.u.rsa.prime1) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_PRIME_2, arena, &rawKey.u.rsa.prime2) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_EXPONENT_1, arena,
|
|
Packit |
40b132 |
&rawKey.u.rsa.exponent1) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_EXPONENT_2, arena,
|
|
Packit |
40b132 |
&rawKey.u.rsa.exponent2) ||
|
|
Packit |
40b132 |
!ReadAttribute(pk, CKA_COEFFICIENT, arena,
|
|
Packit |
40b132 |
&rawKey.u.rsa.coefficient)) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
pki = PORT_ArenaZNew(arena, SECKEYPrivateKeyInfo);
|
|
Packit |
40b132 |
if (!pki) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
encoded = SEC_ASN1EncodeItem(arena, &pki->privateKey, &rawKey,
|
|
Packit |
40b132 |
SECKEY_RSAPrivateKeyExportTemplate);
|
|
Packit |
40b132 |
if (!encoded) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
rv = SECOID_SetAlgorithmID(arena, &pki->algorithm,
|
|
Packit |
40b132 |
SEC_OID_PKCS1_RSA_ENCRYPTION, NULL);
|
|
Packit |
40b132 |
if (rv != SECSuccess) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
pki->version.type = siUnsignedInteger;
|
|
Packit |
40b132 |
pki->version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
|
|
Packit |
40b132 |
if (!pki->version.data) {
|
|
Packit |
40b132 |
goto loser;
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
pki->version.data[0] = pkiVersion;
|
|
Packit |
40b132 |
pki->version.len = 1;
|
|
Packit |
40b132 |
pki->arena = arena;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
return pki;
|
|
Packit |
40b132 |
|
|
Packit |
40b132 |
loser:
|
|
Packit |
40b132 |
if (arena) {
|
|
Packit |
40b132 |
PORT_FreeArena(arena, PR_TRUE);
|
|
Packit |
40b132 |
}
|
|
Packit |
40b132 |
return NULL;
|
|
Packit |
40b132 |
}
|