/* * Platform specific crypto wrappers * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1994-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ryan Sleevi <ryan.sleevi@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* $Id$ */ #include "certt.h" #include "cryptohi.h" #include "keythi.h" #include "nss.h" #include "secitem.h" #include "ssl.h" #include "sslimpl.h" #include "prerror.h" #include "prinit.h" #ifdef NSS_PLATFORM_CLIENT_AUTH #ifdef XP_WIN32 #include <NCrypt.h> #endif #endif #ifdef NSS_PLATFORM_CLIENT_AUTH CERTCertificateList* hack_NewCertificateListFromCertList(CERTCertList* list) { CERTCertificateList * chain = NULL; PLArenaPool * arena = NULL; CERTCertListNode * node; int len; if (CERT_LIST_EMPTY(list)) goto loser; arena = PORT_NewArena(4096); if (arena == NULL) goto loser; for (len = 0, node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); len++, node = CERT_LIST_NEXT(node)) { } chain = PORT_ArenaNew(arena, CERTCertificateList); if (chain == NULL) goto loser; chain->certs = PORT_ArenaNewArray(arena, SECItem, len); if (!chain->certs) goto loser; chain->len = len; for (len = 0, node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); len++, node = CERT_LIST_NEXT(node)) { // Check to see if the last cert to be sent is a self-signed cert, // and if so, omit it from the list of certificates. However, if // there is only one cert (len == 0), include the cert, as it means // the EE cert is self-signed. if (len > 0 && (len == chain->len - 1) && node->cert->isRoot) { chain->len = len; break; } SECITEM_CopyItem(arena, &chain->certs[len], &node->cert->derCert); } chain->arena = arena; return chain; loser: if (arena) { PORT_FreeArena(arena, PR_FALSE); } return NULL; } #if defined(XP_WIN32) typedef SECURITY_STATUS (WINAPI *NCryptFreeObjectFunc)(NCRYPT_HANDLE); typedef SECURITY_STATUS (WINAPI *NCryptSignHashFunc)( NCRYPT_KEY_HANDLE /* hKey */, VOID* /* pPaddingInfo */, PBYTE /* pbHashValue */, DWORD /* cbHashValue */, PBYTE /* pbSignature */, DWORD /* cbSignature */, DWORD* /* pcbResult */, DWORD /* dwFlags */); static PRCallOnceType cngFunctionsInitOnce; static const PRCallOnceType pristineCallOnce; static PRLibrary *ncrypt_library = NULL; static NCryptFreeObjectFunc pNCryptFreeObject = NULL; static NCryptSignHashFunc pNCryptSignHash = NULL; static SECStatus ssl_ShutdownCngFunctions(void *appData, void *nssData) { pNCryptSignHash = NULL; pNCryptFreeObject = NULL; if (ncrypt_library) { PR_UnloadLibrary(ncrypt_library); ncrypt_library = NULL; } cngFunctionsInitOnce = pristineCallOnce; return SECSuccess; } static PRStatus ssl_InitCngFunctions(void) { SECStatus rv; ncrypt_library = PR_LoadLibrary("ncrypt.dll"); if (ncrypt_library == NULL) goto loser; pNCryptFreeObject = (NCryptFreeObjectFunc)PR_FindFunctionSymbol( ncrypt_library, "NCryptFreeObject"); if (pNCryptFreeObject == NULL) goto loser; pNCryptSignHash = (NCryptSignHashFunc)PR_FindFunctionSymbol( ncrypt_library, "NCryptSignHash"); if (pNCryptSignHash == NULL) goto loser; rv = NSS_RegisterShutdown(ssl_ShutdownCngFunctions, NULL); if (rv != SECSuccess) goto loser; return PR_SUCCESS; loser: pNCryptSignHash = NULL; pNCryptFreeObject = NULL; if (ncrypt_library) { PR_UnloadLibrary(ncrypt_library); ncrypt_library = NULL; } return PR_FAILURE; } static SECStatus ssl_InitCng(void) { if (PR_CallOnce(&cngFunctionsInitOnce, ssl_InitCngFunctions) != PR_SUCCESS) return SECFailure; return SECSuccess; } void ssl_FreePlatformKey(PlatformKey key) { if (!key) return; if (key->dwKeySpec == CERT_NCRYPT_KEY_SPEC) { if (ssl_InitCng() == SECSuccess) { (*pNCryptFreeObject)(key->hNCryptKey); } } else { CryptReleaseContext(key->hCryptProv, 0); } PORT_Free(key); } static SECStatus ssl3_CngPlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf, PRBool isTLS, KeyType keyType) { SECStatus rv = SECFailure; SECURITY_STATUS ncrypt_status; PRBool doDerEncode = PR_FALSE; SECItem hashItem; DWORD signatureLen = 0; DWORD dwFlags = 0; VOID *pPaddingInfo = NULL; /* Always encode using PKCS#1 block type. */ BCRYPT_PKCS1_PADDING_INFO rsaPaddingInfo; if (key->dwKeySpec != CERT_NCRYPT_KEY_SPEC) { PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); return SECFailure; } if (ssl_InitCng() != SECSuccess) { PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); return SECFailure; } switch (keyType) { case rsaKey: switch (hash->hashAlg) { case SEC_OID_UNKNOWN: /* No OID/encoded DigestInfo. */ rsaPaddingInfo.pszAlgId = NULL; break; case SEC_OID_SHA1: rsaPaddingInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM; break; case SEC_OID_SHA256: rsaPaddingInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM; break; case SEC_OID_SHA384: rsaPaddingInfo.pszAlgId = BCRYPT_SHA384_ALGORITHM; break; case SEC_OID_SHA512: rsaPaddingInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM; break; default: PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM); return SECFailure; } hashItem.data = hash->u.raw; hashItem.len = hash->len; dwFlags = BCRYPT_PAD_PKCS1; pPaddingInfo = &rsaPaddingInfo; break; case dsaKey: case ecKey: if (keyType == ecKey) { doDerEncode = PR_TRUE; } else { doDerEncode = isTLS; } if (hash->hashAlg == SEC_OID_UNKNOWN) { hashItem.data = hash->u.s.sha; hashItem.len = sizeof(hash->u.s.sha); } else { hashItem.data = hash->u.raw; hashItem.len = hash->len; } break; default: PORT_SetError(SEC_ERROR_INVALID_KEY); goto done; } PRINT_BUF(60, (NULL, "hash(es) to be signed", hashItem.data, hashItem.len)); ncrypt_status = (*pNCryptSignHash)(key->hNCryptKey, pPaddingInfo, (PBYTE)hashItem.data, hashItem.len, NULL, 0, &signatureLen, dwFlags); if (FAILED(ncrypt_status) || signatureLen == 0) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, ncrypt_status); goto done; } buf->data = (unsigned char *)PORT_Alloc(signatureLen); if (!buf->data) { goto done; /* error code was set. */ } ncrypt_status = (*pNCryptSignHash)(key->hNCryptKey, pPaddingInfo, (PBYTE)hashItem.data, hashItem.len, (PBYTE)buf->data, signatureLen, &signatureLen, dwFlags); if (FAILED(ncrypt_status) || signatureLen == 0) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, ncrypt_status); goto done; } buf->len = signatureLen; if (doDerEncode) { SECItem derSig = {siBuffer, NULL, 0}; /* This also works for an ECDSA signature */ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len); if (rv == SECSuccess) { PORT_Free(buf->data); /* discard unencoded signature. */ *buf = derSig; /* give caller encoded signature. */ } else if (derSig.data) { PORT_Free(derSig.data); } } else { rv = SECSuccess; } PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len)); done: if (rv != SECSuccess && buf->data) { PORT_Free(buf->data); buf->data = NULL; buf->len = 0; } return rv; } static SECStatus ssl3_CAPIPlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf, PRBool isTLS, KeyType keyType) { SECStatus rv = SECFailure; PRBool doDerEncode = PR_FALSE; SECItem hashItem; DWORD argLen = 0; DWORD signatureLen = 0; ALG_ID hashAlg = 0; HCRYPTHASH hHash = 0; DWORD hashLen = 0; unsigned int i = 0; buf->data = NULL; switch (hash->hashAlg) { case SEC_OID_UNKNOWN: hashAlg = 0; break; case SEC_OID_SHA1: hashAlg = CALG_SHA1; break; case SEC_OID_SHA256: hashAlg = CALG_SHA_256; break; case SEC_OID_SHA384: hashAlg = CALG_SHA_384; break; case SEC_OID_SHA512: hashAlg = CALG_SHA_512; break; default: PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM); return SECFailure; } switch (keyType) { case rsaKey: if (hashAlg == 0) { hashAlg = CALG_SSL3_SHAMD5; } hashItem.data = hash->u.raw; hashItem.len = hash->len; break; case dsaKey: case ecKey: if (keyType == ecKey) { doDerEncode = PR_TRUE; } else { doDerEncode = isTLS; } if (hashAlg == 0) { hashAlg = CALG_SHA1; hashItem.data = hash->u.s.sha; hashItem.len = sizeof(hash->u.s.sha); } else { hashItem.data = hash->u.raw; hashItem.len = hash->len; } break; default: PORT_SetError(SEC_ERROR_INVALID_KEY); goto done; } PRINT_BUF(60, (NULL, "hash(es) to be signed", hashItem.data, hashItem.len)); if (!CryptCreateHash(key->hCryptProv, hashAlg, 0, 0, &hHash)) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError()); goto done; } argLen = sizeof(hashLen); if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&hashLen, &argLen, 0)) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError()); goto done; } if (hashLen != hashItem.len) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, 0); goto done; } if (!CryptSetHashParam(hHash, HP_HASHVAL, (BYTE*)hashItem.data, 0)) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError()); goto done; } if (!CryptSignHash(hHash, key->dwKeySpec, NULL, 0, NULL, &signatureLen) || signatureLen == 0) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError()); goto done; } buf->data = (unsigned char *)PORT_Alloc(signatureLen); if (!buf->data) goto done; /* error code was set. */ if (!CryptSignHash(hHash, key->dwKeySpec, NULL, 0, (BYTE*)buf->data, &signatureLen)) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError()); goto done; } buf->len = signatureLen; /* CryptoAPI signs in little-endian, so reverse */ for (i = 0; i < buf->len / 2; ++i) { unsigned char tmp = buf->data[i]; buf->data[i] = buf->data[buf->len - 1 - i]; buf->data[buf->len - 1 - i] = tmp; } if (doDerEncode) { SECItem derSig = {siBuffer, NULL, 0}; /* This also works for an ECDSA signature */ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len); if (rv == SECSuccess) { PORT_Free(buf->data); /* discard unencoded signature. */ *buf = derSig; /* give caller encoded signature. */ } else if (derSig.data) { PORT_Free(derSig.data); } } else { rv = SECSuccess; } PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len)); done: if (hHash) CryptDestroyHash(hHash); if (rv != SECSuccess && buf->data) { PORT_Free(buf->data); buf->data = NULL; } return rv; } SECStatus ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf, PRBool isTLS, KeyType keyType) { if (key->dwKeySpec == CERT_NCRYPT_KEY_SPEC) { return ssl3_CngPlatformSignHashes(hash, key, buf, isTLS, keyType); } return ssl3_CAPIPlatformSignHashes(hash, key, buf, isTLS, keyType); } #elif defined(XP_MACOSX) #include <Security/cssm.h> void ssl_FreePlatformKey(PlatformKey key) { CFRelease(key); } #define SSL_MAX_DIGEST_INFO_PREFIX 20 /* ssl3_GetDigestInfoPrefix sets |out| and |out_len| to point to a buffer that * contains ASN.1 data that should be prepended to a hash of the given type in * order to create a DigestInfo structure that is valid for use in a PKCS #1 * v1.5 RSA signature. |out_len| will not be set to a value greater than * SSL_MAX_DIGEST_INFO_PREFIX. */ static SECStatus ssl3_GetDigestInfoPrefix(SECOidTag hashAlg, const SSL3Opaque** out, unsigned int *out_len) { /* These are the DER encoding of ASN.1 DigestInfo structures: * DigestInfo ::= SEQUENCE { * digestAlgorithm AlgorithmIdentifier, * digest OCTET STRING * } * See PKCS #1 v2.2 Section 9.2, Note 1. */ static const unsigned char kSHA1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static const unsigned char kSHA224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c }; static const unsigned char kSHA256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; static const unsigned char kSHA384[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; static const unsigned char kSHA512[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; switch (hashAlg) { case SEC_OID_UNKNOWN: *out_len = 0; break; case SEC_OID_SHA1: *out = kSHA1; *out_len = sizeof(kSHA1); break; case SEC_OID_SHA224: *out = kSHA224; *out_len = sizeof(kSHA224); break; case SEC_OID_SHA256: *out = kSHA256; *out_len = sizeof(kSHA256); break; case SEC_OID_SHA384: *out = kSHA384; *out_len = sizeof(kSHA384); break; case SEC_OID_SHA512: *out = kSHA512; *out_len = sizeof(kSHA512); break; default: PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM); return SECFailure; } return SECSuccess; } /* Given the length of a raw DSA signature (consisting of two integers * r and s), returns the maximum length of the DER encoding of the * following structure: * * Dss-Sig-Value ::= SEQUENCE { * r INTEGER, * s INTEGER * } */ static unsigned int ssl3_DSAMaxDerEncodedLength(unsigned int rawDsaLen) { /* The length of one INTEGER. */ unsigned int integerDerLen = rawDsaLen/2 + /* the integer itself */ 1 + /* additional zero byte if high bit is 1 */ SEC_ASN1LengthLength(rawDsaLen/2 + 1) + /* length */ 1; /* INTEGER tag */ /* The length of two INTEGERs in a SEQUENCE */ return 2 * integerDerLen + /* two INTEGERs */ SEC_ASN1LengthLength(2 * integerDerLen) + /* length */ 1; /* SEQUENCE tag */ } SECStatus ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf, PRBool isTLS, KeyType keyType) { SECStatus rv = SECFailure; PRBool doDerDecode = PR_FALSE; unsigned int rawDsaLen; unsigned int signatureLen; OSStatus status = noErr; CSSM_CSP_HANDLE cspHandle = 0; const CSSM_KEY *cssmKey = NULL; CSSM_ALGORITHMS sigAlg; CSSM_ALGORITHMS digestAlg; const CSSM_ACCESS_CREDENTIALS * cssmCreds = NULL; CSSM_RETURN cssmRv; CSSM_DATA hashData; CSSM_DATA signatureData; CSSM_CC_HANDLE cssmSignature = 0; const SSL3Opaque* prefix; unsigned int prefixLen; SSL3Opaque prefixAndHash[SSL_MAX_DIGEST_INFO_PREFIX + HASH_LENGTH_MAX]; buf->data = NULL; status = SecKeyGetCSPHandle(key, &cspHandle); if (status != noErr) { PORT_SetError(SEC_ERROR_INVALID_KEY); goto done; } status = SecKeyGetCSSMKey(key, &cssmKey); if (status != noErr || !cssmKey) { PORT_SetError(SEC_ERROR_NO_KEY); goto done; } sigAlg = cssmKey->KeyHeader.AlgorithmId; digestAlg = CSSM_ALGID_NONE; switch (keyType) { case rsaKey: PORT_Assert(sigAlg == CSSM_ALGID_RSA); signatureLen = (cssmKey->KeyHeader.LogicalKeySizeInBits + 7) / 8; if (ssl3_GetDigestInfoPrefix(hash->hashAlg, &prefix, &prefixLen) != SECSuccess) { goto done; } if (prefixLen + hash->len > sizeof(prefixAndHash)) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); goto done; } memcpy(prefixAndHash, prefix, prefixLen); memcpy(prefixAndHash + prefixLen, hash->u.raw, hash->len); hashData.Data = prefixAndHash; hashData.Length = prefixLen + hash->len; break; case dsaKey: case ecKey: /* SSL3 DSA signatures are raw, not DER-encoded. CSSM gives back * DER-encoded signatures, so they must be decoded. */ doDerDecode = (keyType == dsaKey) && !isTLS; /* Compute the maximum size of a DER-encoded signature: */ if (keyType == ecKey) { PORT_Assert(sigAlg == CSSM_ALGID_ECDSA); /* LogicalKeySizeInBits is the size of an EC public key. But an * ECDSA signature length depends on the size of the base * point's order. For P-256, P-384, and P-521, these two sizes * are the same. */ rawDsaLen = (cssmKey->KeyHeader.LogicalKeySizeInBits + 7) / 8 * 2; } else { /* TODO(davidben): Get the size of the subprime out of CSSM. For * now, assume 160; Apple's implementation hardcodes it. */ PORT_Assert(sigAlg == CSSM_ALGID_DSA); rawDsaLen = 2 * (160 / 8); } signatureLen = ssl3_DSAMaxDerEncodedLength(rawDsaLen); /* SEC_OID_UNKNOWN is used to specify the MD5/SHA1 concatenated * hash. In that case, we use just the SHA1 part. */ if (hash->hashAlg == SEC_OID_UNKNOWN) { hashData.Data = hash->u.s.sha; hashData.Length = sizeof(hash->u.s.sha); } else { hashData.Data = hash->u.raw; hashData.Length = hash->len; } break; default: PORT_SetError(SEC_ERROR_INVALID_KEY); goto done; } PRINT_BUF(60, (NULL, "hash(es) to be signed", hashData.Data, hashData.Length)); if (signatureLen == 0) { PORT_SetError(SEC_ERROR_INVALID_KEY); goto done; } buf->data = (unsigned char *)PORT_Alloc(signatureLen); if (!buf->data) goto done; /* error code was set. */ /* TODO(rsleevi): Should it be kSecCredentialTypeNoUI? In Win32, at least, * you can prevent the UI by setting the provider handle on the * certificate to be opened with CRYPT_SILENT, but is there an equivalent? */ status = SecKeyGetCredentials(key, CSSM_ACL_AUTHORIZATION_SIGN, kSecCredentialTypeDefault, &cssmCreds); if (status != noErr) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, status); goto done; } signatureData.Length = signatureLen; signatureData.Data = (uint8*)buf->data; cssmRv = CSSM_CSP_CreateSignatureContext(cspHandle, sigAlg, cssmCreds, cssmKey, &cssmSignature); if (cssmRv) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv); goto done; } /* See "Apple Cryptographic Service Provider Functional Specification" */ if (cssmKey->KeyHeader.AlgorithmId == CSSM_ALGID_RSA) { /* To set RSA blinding for RSA keys */ CSSM_CONTEXT_ATTRIBUTE blindingAttr; blindingAttr.AttributeType = CSSM_ATTRIBUTE_RSA_BLINDING; blindingAttr.AttributeLength = sizeof(uint32); blindingAttr.Attribute.Uint32 = 1; cssmRv = CSSM_UpdateContextAttributes(cssmSignature, 1, &blindingAttr); if (cssmRv) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv); goto done; } } cssmRv = CSSM_SignData(cssmSignature, &hashData, 1, digestAlg, &signatureData); if (cssmRv) { PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv); goto done; } buf->len = signatureData.Length; if (doDerDecode) { SECItem* rawSig = DSAU_DecodeDerSigToLen(buf, rawDsaLen); if (rawSig != NULL) { PORT_Free(buf->data); /* discard encoded signature. */ *buf = *rawSig; /* give caller unencoded signature. */ PORT_Free(rawSig); rv = SECSuccess; } } else { rv = SECSuccess; } PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len)); done: /* cspHandle, cssmKey, and cssmCreds are owned by the SecKeyRef and * should not be freed. When the PlatformKey is freed, they will be * released. */ if (cssmSignature) CSSM_DeleteContext(cssmSignature); if (rv != SECSuccess && buf->data) { PORT_Free(buf->data); buf->data = NULL; } return rv; } #else void ssl_FreePlatformKey(PlatformKey key) { } SECStatus ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf, PRBool isTLS, KeyType keyType) { PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); return SECFailure; } #endif #endif /* NSS_PLATFORM_CLIENT_AUTH */