You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3411 lines
106 KiB
3411 lines
106 KiB
diff --git a/cmd/dbtool/Makefile b/cmd/dbtool/Makefile |
|
new file mode 100644 |
|
--- /dev/null |
|
+++ b/cmd/dbtool/Makefile |
|
@@ -0,0 +1,46 @@ |
|
+#! gmake |
|
+# |
|
+# This Source Code Form is subject to the terms of the Mozilla Public |
|
+# License, v. 2.0. If a copy of the MPL was not distributed with this |
|
+# file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
+ |
|
+####################################################################### |
|
+# (1) Include initial platform-independent assignments (MANDATORY). # |
|
+####################################################################### |
|
+ |
|
+include manifest.mn |
|
+ |
|
+####################################################################### |
|
+# (2) Include "global" configuration information. (OPTIONAL) # |
|
+####################################################################### |
|
+ |
|
+include $(CORE_DEPTH)/coreconf/config.mk |
|
+ |
|
+####################################################################### |
|
+# (3) Include "component" configuration information. (OPTIONAL) # |
|
+####################################################################### |
|
+ |
|
+####################################################################### |
|
+# (4) Include "local" platform-dependent assignments (OPTIONAL). # |
|
+####################################################################### |
|
+ |
|
+include ../platlibs.mk |
|
+ |
|
+####################################################################### |
|
+# (5) Execute "global" rules. (OPTIONAL) # |
|
+####################################################################### |
|
+ |
|
+include $(CORE_DEPTH)/coreconf/rules.mk |
|
+ |
|
+####################################################################### |
|
+# (6) Execute "component" rules. (OPTIONAL) # |
|
+####################################################################### |
|
+ |
|
+#include ../platlibs.mk |
|
+ |
|
+####################################################################### |
|
+# (7) Execute "local" rules. (OPTIONAL). # |
|
+####################################################################### |
|
+ |
|
+include ../platrules.mk |
|
+ |
|
diff --git a/cmd/dbtool/dbtool.c b/cmd/dbtool/dbtool.c |
|
new file mode 100644 |
|
--- /dev/null |
|
+++ b/cmd/dbtool/dbtool.c |
|
@@ -0,0 +1,806 @@ |
|
+/* This Source Code Form is subject to the terms of the Mozilla Public |
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
+ |
|
+/* |
|
+** dbtool.c |
|
+** |
|
+** tool to dump the underlying encoding of a database. This tool duplicates |
|
+** some private functions in softoken. It uses libsec and libutil, but no |
|
+** other portions of NSS. It currently only works on sqlite databases. For |
|
+** an even more primitive dump, use sqlite3 on the individual files. |
|
+** |
|
+** TODO: dump the meta data for the databases. |
|
+** optionally dump more PKCS5 information (KDF/salt/iterations) |
|
+** take a password and decode encrypted attributes/verify signed |
|
+** attributes. |
|
+*/ |
|
+#include <stdio.h> |
|
+#include <string.h> |
|
+ |
|
+#if defined(WIN32) |
|
+#include "fcntl.h" |
|
+#include "io.h" |
|
+#endif |
|
+ |
|
+#include "secutil.h" |
|
+#include "pk11pub.h" |
|
+ |
|
+#if defined(XP_UNIX) |
|
+#include <unistd.h> |
|
+#endif |
|
+ |
|
+#include "nspr.h" |
|
+#include "prtypes.h" |
|
+#include "certdb.h" |
|
+#include "nss.h" |
|
+#include "../modutil/modutil.h" |
|
+#include "pk11table.h" |
|
+#include "sftkdbt.h" |
|
+#include "sdb.h" |
|
+#include "secoid.h" |
|
+ |
|
+#include "plgetopt.h" |
|
+ |
|
+static char *progName; |
|
+ |
|
+char *dbDir = NULL; |
|
+ |
|
+static void |
|
+Usage() |
|
+{ |
|
+ printf("Usage: %s [-c certprefix] [-k keyprefix] " |
|
+ "[-V certversion] [-v keyversion]\n" |
|
+ " [-d dbdir]\n", |
|
+ progName); |
|
+ printf("%-20s Directory with cert database (default is .)\n", |
|
+ "-d certdir"); |
|
+ printf("%-20s prefix for the cert database (default is \"\")\n", |
|
+ "-c certprefix"); |
|
+ printf("%-20s prefix for the key database (default is \"\")\n", |
|
+ "-k keyprefix"); |
|
+ printf("%-20s version of the cert database (default is 9)\n", |
|
+ "-V certversion"); |
|
+ printf("%-20s version of the key database (default is 4)\n", |
|
+ "-v keyversion"); |
|
+ exit(1); |
|
+} |
|
+#define SFTK_KEYDB_TYPE 0x40000000 |
|
+#define SFTK_TOKEN_TYPE 0x80000000 |
|
+ |
|
+/* |
|
+ * known attributes |
|
+ */ |
|
+static const CK_ATTRIBUTE_TYPE known_attributes[] = { |
|
+ CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION, |
|
+ CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER, |
|
+ CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED, |
|
+ CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL, |
|
+ CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY, |
|
+ CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE, |
|
+ CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER, |
|
+ CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE, |
|
+ CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT, |
|
+ CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT, |
|
+ CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS, |
|
+ CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE, |
|
+ CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE, |
|
+ CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS, |
|
+ CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS, |
|
+ CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_WRAP_TEMPLATE, |
|
+ CKA_UNWRAP_TEMPLATE, CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT, |
|
+ CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, CKA_RESOLUTION, CKA_CHAR_ROWS, |
|
+ CKA_CHAR_COLUMNS, CKA_COLOR, CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, |
|
+ CKA_ENCODING_METHODS, CKA_MIME_TYPES, CKA_MECHANISM_TYPE, |
|
+ CKA_REQUIRED_CMS_ATTRIBUTES, CKA_DEFAULT_CMS_ATTRIBUTES, |
|
+ CKA_SUPPORTED_CMS_ATTRIBUTES, CKA_NSS_URL, CKA_NSS_EMAIL, |
|
+ CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP, |
|
+ CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES, |
|
+ CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED, |
|
+ CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC, |
|
+ CKA_TRUST_DIGITAL_SIGNATURE, CKA_TRUST_NON_REPUDIATION, |
|
+ CKA_TRUST_KEY_ENCIPHERMENT, CKA_TRUST_DATA_ENCIPHERMENT, |
|
+ CKA_TRUST_KEY_AGREEMENT, CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, |
|
+ CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, |
|
+ CKA_TRUST_EMAIL_PROTECTION, CKA_TRUST_IPSEC_END_SYSTEM, |
|
+ CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, CKA_TRUST_TIME_STAMPING, |
|
+ CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH, |
|
+ CKA_NSS_DB, CKA_NSS_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS, |
|
+ CKA_PUBLIC_KEY_INFO |
|
+}; |
|
+ |
|
+static unsigned int known_attributes_size = sizeof(known_attributes) / |
|
+ sizeof(known_attributes[0]); |
|
+ |
|
+PRBool |
|
+isULONGAttribute(CK_ATTRIBUTE_TYPE type) |
|
+{ |
|
+ switch (type) { |
|
+ case CKA_CERTIFICATE_CATEGORY: |
|
+ case CKA_CERTIFICATE_TYPE: |
|
+ case CKA_CLASS: |
|
+ case CKA_JAVA_MIDP_SECURITY_DOMAIN: |
|
+ case CKA_KEY_GEN_MECHANISM: |
|
+ case CKA_KEY_TYPE: |
|
+ case CKA_MECHANISM_TYPE: |
|
+ case CKA_MODULUS_BITS: |
|
+ case CKA_PRIME_BITS: |
|
+ case CKA_SUBPRIME_BITS: |
|
+ case CKA_VALUE_BITS: |
|
+ case CKA_VALUE_LEN: |
|
+ |
|
+ case CKA_TRUST_DIGITAL_SIGNATURE: |
|
+ case CKA_TRUST_NON_REPUDIATION: |
|
+ case CKA_TRUST_KEY_ENCIPHERMENT: |
|
+ case CKA_TRUST_DATA_ENCIPHERMENT: |
|
+ case CKA_TRUST_KEY_AGREEMENT: |
|
+ case CKA_TRUST_KEY_CERT_SIGN: |
|
+ case CKA_TRUST_CRL_SIGN: |
|
+ |
|
+ case CKA_TRUST_SERVER_AUTH: |
|
+ case CKA_TRUST_CLIENT_AUTH: |
|
+ case CKA_TRUST_CODE_SIGNING: |
|
+ case CKA_TRUST_EMAIL_PROTECTION: |
|
+ case CKA_TRUST_IPSEC_END_SYSTEM: |
|
+ case CKA_TRUST_IPSEC_TUNNEL: |
|
+ case CKA_TRUST_IPSEC_USER: |
|
+ case CKA_TRUST_TIME_STAMPING: |
|
+ case CKA_TRUST_STEP_UP_APPROVED: |
|
+ return PR_TRUE; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ return PR_FALSE; |
|
+} |
|
+ |
|
+/* are the attributes private? */ |
|
+static PRBool |
|
+isPrivateAttribute(CK_ATTRIBUTE_TYPE type) |
|
+{ |
|
+ switch (type) { |
|
+ case CKA_VALUE: |
|
+ case CKA_PRIVATE_EXPONENT: |
|
+ case CKA_PRIME_1: |
|
+ case CKA_PRIME_2: |
|
+ case CKA_EXPONENT_1: |
|
+ case CKA_EXPONENT_2: |
|
+ case CKA_COEFFICIENT: |
|
+ return PR_TRUE; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ return PR_FALSE; |
|
+} |
|
+ |
|
+/* These attributes must be authenticated with an hmac. */ |
|
+static PRBool |
|
+isAuthenticatedAttribute(CK_ATTRIBUTE_TYPE type) |
|
+{ |
|
+ switch (type) { |
|
+ case CKA_MODULUS: |
|
+ case CKA_PUBLIC_EXPONENT: |
|
+ case CKA_CERT_SHA1_HASH: |
|
+ case CKA_CERT_MD5_HASH: |
|
+ case CKA_TRUST_SERVER_AUTH: |
|
+ case CKA_TRUST_CLIENT_AUTH: |
|
+ case CKA_TRUST_EMAIL_PROTECTION: |
|
+ case CKA_TRUST_CODE_SIGNING: |
|
+ case CKA_TRUST_STEP_UP_APPROVED: |
|
+ case CKA_NSS_OVERRIDE_EXTENSIONS: |
|
+ return PR_TRUE; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ return PR_FALSE; |
|
+} |
|
+ |
|
+/* |
|
+ * convert a database ulong back to a native ULONG. (reverse of the above |
|
+ * function. |
|
+ */ |
|
+static CK_ULONG |
|
+sdbULong2ULong(unsigned char *data) |
|
+{ |
|
+ int i; |
|
+ CK_ULONG value = 0; |
|
+ |
|
+ for (i = 0; i < SDB_ULONG_SIZE; i++) { |
|
+ value |= (((CK_ULONG)data[i]) << (SDB_ULONG_SIZE - 1 - i) |
|
+ * PR_BITS_PER_BYTE); |
|
+ } |
|
+ return value; |
|
+} |
|
+ |
|
+/* PBE defines and functions */ |
|
+ |
|
+typedef struct EncryptedDataInfoStr { |
|
+ SECAlgorithmID algorithm; |
|
+ SECItem encryptedData; |
|
+} EncryptedDataInfo; |
|
+ |
|
+static const SEC_ASN1Template encryptedDataInfoTemplate[] = { |
|
+ { SEC_ASN1_SEQUENCE, |
|
+ 0, NULL, sizeof(EncryptedDataInfo) }, |
|
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
|
+ offsetof(EncryptedDataInfo, algorithm), |
|
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
|
+ { SEC_ASN1_OCTET_STRING, |
|
+ offsetof(EncryptedDataInfo, encryptedData) }, |
|
+ { 0 } |
|
+}; |
|
+ |
|
+typedef struct PBEParameterStr { |
|
+ SECAlgorithmID prfAlg; |
|
+ SECItem salt; |
|
+ SECItem iteration; |
|
+ SECItem keyLength; |
|
+} PBEParameter; |
|
+ |
|
+static const SEC_ASN1Template pkcs5V1PBEParameterTemplate[] = |
|
+ { |
|
+ { SEC_ASN1_SEQUENCE, |
|
+ 0, NULL, sizeof(PBEParameter) }, |
|
+ { SEC_ASN1_OCTET_STRING, |
|
+ offsetof(PBEParameter, salt) }, |
|
+ { SEC_ASN1_INTEGER, |
|
+ offsetof(PBEParameter, iteration) }, |
|
+ { 0 } |
|
+ }; |
|
+ |
|
+static const SEC_ASN1Template pkcs12V2PBEParameterTemplate[] = |
|
+ { |
|
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) }, |
|
+ { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) }, |
|
+ { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) }, |
|
+ { 0 } |
|
+ }; |
|
+ |
|
+ |
|
+static const SEC_ASN1Template pkcs5V2PBEParameterTemplate[] = |
|
+ { |
|
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) }, |
|
+ /* this is really a choice, but since we don't understand any other |
|
+ * choice, just inline it. */ |
|
+ { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) }, |
|
+ { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) }, |
|
+ { SEC_ASN1_INTEGER, offsetof(PBEParameter, keyLength) }, |
|
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
|
+ offsetof(PBEParameter, prfAlg), |
|
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
|
+ { 0 } |
|
+ }; |
|
+ |
|
+typedef struct Pkcs5v2PBEParameterStr { |
|
+ SECAlgorithmID keyParams; /* parameters of the key generation */ |
|
+ SECAlgorithmID algParams; /* parameters for the encryption or mac op */ |
|
+} Pkcs5v2PBEParameter; |
|
+ |
|
+static const SEC_ASN1Template pkcs5v2PBES2ParameterTemplate[] = { |
|
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(Pkcs5v2PBEParameter) }, |
|
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
|
+ offsetof(Pkcs5v2PBEParameter, keyParams), |
|
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
|
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, |
|
+ offsetof(Pkcs5v2PBEParameter, algParams), |
|
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, |
|
+ { 0 } |
|
+}; |
|
+ |
|
+static inline PRBool |
|
+isPKCS12PBE(SECOidTag alg) { |
|
+ switch (alg) { |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC: |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC: |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC: |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC: |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4: |
|
+ case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4: |
|
+ return PR_TRUE; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ return PR_FALSE; |
|
+} |
|
+ |
|
+ |
|
+/* helper functions */ |
|
+ |
|
+/* output an NSS specific attribute or name that wasn't found in our |
|
+ * pkcs #11 table */ |
|
+const char * |
|
+makeNSSVendorName(CK_ATTRIBUTE_TYPE attribute, const char *nameType) |
|
+{ |
|
+ static char nss_name[256]; |
|
+ const char *name = NULL; |
|
+ if ((attribute >= CKA_NSS) && (attribute <= 0xffffffff)) { |
|
+ sprintf(nss_name,"%s+%d", nameType, (int)(attribute-CKA_NSS)); |
|
+ name = nss_name; |
|
+ } |
|
+ return name; |
|
+} |
|
+ |
|
+/* turn and attribute into a name */ |
|
+const char * |
|
+AttributeName(CK_ATTRIBUTE_TYPE attribute) |
|
+{ |
|
+ const char *name = getNameFromAttribute(attribute); |
|
+ if (!name) { |
|
+ name = makeNSSVendorName(attribute, "CKA_NSS"); |
|
+ } |
|
+ |
|
+ return name ? name : "UNKNOWN_ATTRIBUTE_TYPE"; |
|
+} |
|
+ |
|
+/* turn and error code into a name */ |
|
+const char * |
|
+ErrorName(CK_RV crv) |
|
+{ |
|
+ const char *error = getName(crv, ConstResult); |
|
+ if (!error) { |
|
+ error = makeNSSVendorName(crv, "CKR_NSS"); |
|
+ } |
|
+ return error ? error : "UNKNOWN_ERROR"; |
|
+} |
|
+ |
|
+/* turn an oud tag into a string */ |
|
+const char * |
|
+oid2string(SECOidTag alg) |
|
+{ |
|
+ const char *oidstring = SECOID_FindOIDTagDescription(alg); |
|
+ const char *def="Invalid oid tag"; /* future build a dotted oid string value here */ |
|
+ return oidstring ? oidstring : def; |
|
+} |
|
+ |
|
+/* dump an arbitary data blob. Dump it has hex with ascii on the side */ |
|
+#define ASCCHAR(val) ((val) >= ' ' && (val) <= 0x7e ? (val) : '.') |
|
+#define LINE_LENGTH 16 |
|
+void |
|
+dumpValue(const unsigned char *v, int len) |
|
+{ |
|
+ int i, next = 0; |
|
+ char string[LINE_LENGTH+1]; |
|
+ char space[LINE_LENGTH*2+1]; |
|
+ char *nl = ""; |
|
+ char *sp = ""; |
|
+ PORT_Memset(string, 0, sizeof(string)); |
|
+ |
|
+ for (i=0; i < len; i++) { |
|
+ if ((i % LINE_LENGTH) == 0) { |
|
+ printf("%s%s%s ", sp, string, nl); |
|
+ PORT_Memset(string, 0, sizeof(string)); |
|
+ next = 0; |
|
+ nl = "\n"; |
|
+ sp = " "; |
|
+ } |
|
+ printf("%02x", v[i]); |
|
+ string[next++] = ASCCHAR(v[i]); |
|
+ } |
|
+ PORT_Memset(space, 0, sizeof(space)); |
|
+ i = LINE_LENGTH - (len % LINE_LENGTH); |
|
+ if (i != LINE_LENGTH) { |
|
+ int j; |
|
+ for (j=0 ; j < i; j++) { |
|
+ space[j*2] = ' '; |
|
+ space[j*2+1] = ' '; |
|
+ } |
|
+ } |
|
+ printf("%s%s%s%s", space, sp, string, nl); |
|
+} |
|
+ |
|
+/* dump a PKCS5/12 PBE blob */ |
|
+void |
|
+dumpPKCS(unsigned char *val, CK_ULONG len, PRBool *hasSig) |
|
+{ |
|
+ EncryptedDataInfo edi; |
|
+ SECStatus rv; |
|
+ SECItem data; |
|
+ PLArenaPool *arena; |
|
+ SECOidTag alg, prfAlg; |
|
+ PBEParameter pbeParam; |
|
+ unsigned char zero = 0; |
|
+ const SEC_ASN1Template *template = pkcs5V1PBEParameterTemplate; |
|
+ int iter, keyLen, i; |
|
+ |
|
+ if (hasSig) { *hasSig = PR_FALSE; } |
|
+ |
|
+ |
|
+ data.data = val; |
|
+ data.len = len; |
|
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
|
+ if (arena == NULL) { |
|
+ printf("Couldn't allocate arena\n"); |
|
+ return; |
|
+ } |
|
+ |
|
+ /* initialize default values */ |
|
+ PORT_Memset(&pbeParam, 0, sizeof(pbeParam)); |
|
+ pbeParam.keyLength.data = &zero; |
|
+ pbeParam.keyLength.len = sizeof(zero); |
|
+ SECOID_SetAlgorithmID(arena, &pbeParam.prfAlg, SEC_OID_SHA1, NULL); |
|
+ |
|
+ /* first crack the encrypted data from the PBE algorithm ID */ |
|
+ rv = SEC_QuickDERDecodeItem(arena, &edi, encryptedDataInfoTemplate, &data); |
|
+ if (rv != SECSuccess) { |
|
+ printf("Encrypted Data, failed to decode\n"); |
|
+ dumpValue(val,len); |
|
+ PORT_FreeArena(arena, PR_FALSE); |
|
+ return; |
|
+ } |
|
+ /* now use the pbe secalg to dump info on the pbe */ |
|
+ alg = SECOID_GetAlgorithmTag(&edi.algorithm); |
|
+ if ((alg == SEC_OID_PKCS5_PBES2) || (alg == SEC_OID_PKCS5_PBMAC1)){ |
|
+ Pkcs5v2PBEParameter param; |
|
+ SECOidTag palg; |
|
+ const char *typeName = (alg == SEC_OID_PKCS5_PBES2) ? |
|
+ "Encrypted Data PBES2" : |
|
+ "Mac Data PBMAC1"; |
|
+ |
|
+ rv = SEC_QuickDERDecodeItem(arena, ¶m, |
|
+ pkcs5v2PBES2ParameterTemplate, |
|
+ &edi.algorithm.parameters); |
|
+ if (rv != SECSuccess) { |
|
+ printf("%s, failed to decode\n", typeName); |
|
+ dumpValue(val,len); |
|
+ PORT_FreeArena(arena, PR_FALSE); |
|
+ return; |
|
+ } |
|
+ palg = SECOID_GetAlgorithmTag(¶m.algParams); |
|
+ printf("%s alg=%s ", typeName, oid2string(palg)); |
|
+ if (hasSig && palg == SEC_OID_AES_256_CBC) { |
|
+ *hasSig = PR_TRUE; |
|
+ } |
|
+ template = pkcs5V2PBEParameterTemplate; |
|
+ edi.algorithm.parameters = param.keyParams.parameters; |
|
+ } else { |
|
+ printf("Encrypted Data alg=%s ", oid2string(alg)); |
|
+ if (alg == SEC_OID_PKCS5_PBKDF2) { |
|
+ template = pkcs5V2PBEParameterTemplate; |
|
+ } else if (isPKCS12PBE(alg)) { |
|
+ template = pkcs12V2PBEParameterTemplate; |
|
+ } else { |
|
+ template = pkcs5V1PBEParameterTemplate; |
|
+ } |
|
+ } |
|
+ rv = SEC_QuickDERDecodeItem(arena, &pbeParam, |
|
+ template, |
|
+ &edi.algorithm.parameters); |
|
+ if (rv != SECSuccess) { |
|
+ printf("( failed to decode params)\n"); |
|
+ PORT_FreeArena(arena, PR_FALSE); |
|
+ return; |
|
+ } |
|
+ /* dump the pbe parmeters */ |
|
+ iter = DER_GetInteger(&pbeParam.iteration); |
|
+ keyLen = DER_GetInteger(&pbeParam.keyLength); |
|
+ prfAlg = SECOID_GetAlgorithmTag(&pbeParam.prfAlg); |
|
+ printf("(prf=%s iter=%d keyLen=%d salt=0x", |
|
+ oid2string(prfAlg), iter, keyLen); |
|
+ for(i=0;i < pbeParam.salt.len; i++) printf("%02x",pbeParam.salt.data[i]); |
|
+ printf(")\n"); |
|
+ /* finally dump the raw encrypted data */ |
|
+ dumpValue(edi.encryptedData.data, edi.encryptedData.len); |
|
+ PORT_FreeArena(arena, PR_FALSE); |
|
+} |
|
+ |
|
+/* dump a long attribute, convert to an unsigned long. PKCS #11 Longs are |
|
+ * limited to 32 bits by the spec, even if the CK_ULONG is longer */ |
|
+void |
|
+dumpLongAttribute(CK_ATTRIBUTE_TYPE type, CK_ULONG value) |
|
+{ |
|
+ const char *nameType = "CK_NSS"; |
|
+ ConstType constType = ConstNone; |
|
+ const char *valueName = NULL; |
|
+ |
|
+ switch (type) { |
|
+ case CKA_CLASS: |
|
+ nameType = "CKO_NSS"; |
|
+ constType = ConstObject; |
|
+ break; |
|
+ case CKA_CERTIFICATE_TYPE: |
|
+ nameType = "CKC_NSS"; |
|
+ constType = ConstCertType; |
|
+ break; |
|
+ case CKA_KEY_TYPE: |
|
+ nameType = "CKK_NSS"; |
|
+ constType = ConstKeyType; |
|
+ break; |
|
+ case CKA_MECHANISM_TYPE: |
|
+ nameType = "CKM_NSS"; |
|
+ constType = ConstMechanism; |
|
+ break; |
|
+ case CKA_TRUST_SERVER_AUTH: |
|
+ case CKA_TRUST_CLIENT_AUTH: |
|
+ case CKA_TRUST_CODE_SIGNING: |
|
+ case CKA_TRUST_EMAIL_PROTECTION: |
|
+ case CKA_TRUST_IPSEC_END_SYSTEM: |
|
+ case CKA_TRUST_IPSEC_TUNNEL: |
|
+ case CKA_TRUST_IPSEC_USER: |
|
+ case CKA_TRUST_TIME_STAMPING: |
|
+ nameType = "CKT_NSS"; |
|
+ constType = ConstTrust; |
|
+ break; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ /* if value has a symbolic name, use it */ |
|
+ if (constType != ConstNone) { |
|
+ valueName = getName(value, constType); |
|
+ } |
|
+ if (!valueName) { |
|
+ valueName = makeNSSVendorName(value, nameType); |
|
+ } |
|
+ if (!valueName) { |
|
+ printf("%d (0x%08x)\n", (int) value, (int)value); |
|
+ } else { |
|
+ printf("%s (0x%08x)\n", valueName, (int)value); |
|
+ } |
|
+} |
|
+ |
|
+/* dump a signature for an object */ |
|
+static const char META_SIG_TEMPLATE[] = "sig_%s_%08x_%08x"; |
|
+void |
|
+dumpSignature(CK_ATTRIBUTE_TYPE attribute, SDB *keydb, PRBool isKey, |
|
+ CK_OBJECT_HANDLE objectID, PRBool force) |
|
+{ |
|
+ char id[30]; |
|
+ CK_RV crv; |
|
+ SECItem signText; |
|
+ unsigned char signData[SDB_MAX_META_DATA_LEN]; |
|
+ |
|
+ if (!force && !isAuthenticatedAttribute(attribute)) { |
|
+ return; |
|
+ } |
|
+ sprintf(id, META_SIG_TEMPLATE, |
|
+ isKey ? "key" : "cert", |
|
+ (unsigned int)objectID, (unsigned int)attribute); |
|
+ printf(" Signature %s:",id); |
|
+ signText.data = signData; |
|
+ signText.len = sizeof(signData); |
|
+ |
|
+ |
|
+ crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL); |
|
+ if ((crv != CKR_OK) && isKey) { |
|
+ sprintf(id, META_SIG_TEMPLATE, |
|
+ isKey ? "key" : "cert", (unsigned int) |
|
+ (objectID | SFTK_KEYDB_TYPE | SFTK_TOKEN_TYPE), |
|
+ (unsigned int)attribute); |
|
+ crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL); |
|
+ } |
|
+ if (crv != CKR_OK) { |
|
+ printf(" FAILED %s with %s (0x%08x)\n", id, ErrorName(crv), (int) crv); |
|
+ return; |
|
+ } |
|
+ dumpPKCS(signText.data, signText.len, NULL); |
|
+ return; |
|
+} |
|
+ |
|
+/* dump an attribute. use the helper functions above */ |
|
+void |
|
+dumpAttribute(CK_ATTRIBUTE *template, SDB *keydb, PRBool isKey, |
|
+ CK_OBJECT_HANDLE id) |
|
+{ |
|
+ CK_ATTRIBUTE_TYPE attribute = template->type; |
|
+ printf(" %s(0x%08x): ", AttributeName(attribute), (int)attribute); |
|
+ if (template->pValue == NULL) { |
|
+ printf("NULL (%d)\n", (int)template->ulValueLen); |
|
+ return; |
|
+ } |
|
+ if (template->ulValueLen == SDB_ULONG_SIZE |
|
+ && isULONGAttribute(attribute)) { |
|
+ CK_ULONG value=sdbULong2ULong(template->pValue); |
|
+ dumpLongAttribute(attribute, value); |
|
+ return; |
|
+ } |
|
+ if (template->ulValueLen == 1) { |
|
+ unsigned char val = *(unsigned char *)template->pValue; |
|
+ switch (val) { |
|
+ case 0: |
|
+ printf("CK_FALSE\n"); |
|
+ break; |
|
+ case 1: |
|
+ printf("CK_TRUE\n"); |
|
+ break; |
|
+ default: |
|
+ printf("%d 0x%02x %c\n", val, val, ASCCHAR(val)); |
|
+ break; |
|
+ } |
|
+ return; |
|
+ } |
|
+ if (isKey && isPrivateAttribute(attribute)) { |
|
+ PRBool hasSig = PR_FALSE; |
|
+ dumpPKCS(template->pValue, template->ulValueLen, &hasSig); |
|
+ if (hasSig) { |
|
+ dumpSignature(attribute, keydb, isKey, id, PR_TRUE); |
|
+ } |
|
+ return; |
|
+ } |
|
+ if (template->ulValueLen == 0) { printf("empty"); } |
|
+ printf("\n"); |
|
+ dumpValue(template->pValue, template->ulValueLen); |
|
+} |
|
+ |
|
+/* dump all the attributes in an object */ |
|
+void |
|
+dumpObject(CK_OBJECT_HANDLE id, SDB *db, SDB *keydb, PRBool isKey) |
|
+{ |
|
+ CK_RV crv; |
|
+ int i; |
|
+ CK_ATTRIBUTE template; |
|
+ char buffer[2048]; |
|
+ char * alloc = NULL; |
|
+ |
|
+ printf(" Object 0x%08x:\n", (int)id); |
|
+ for (i = 0; i < known_attributes_size; i++) { |
|
+ CK_ATTRIBUTE_TYPE attribute = known_attributes[i]; |
|
+ template.type = attribute; |
|
+ template.pValue = NULL; |
|
+ template.ulValueLen = 0; |
|
+ crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1); |
|
+ |
|
+ if (crv != CKR_OK) { |
|
+ if (crv != CKR_ATTRIBUTE_TYPE_INVALID) { |
|
+ PR_fprintf(PR_STDERR, " " |
|
+ "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n", |
|
+ AttributeName(attribute), (int)attribute, |
|
+ ErrorName(crv), (int)crv); |
|
+ } |
|
+ continue; |
|
+ } |
|
+ |
|
+ if (template.ulValueLen < sizeof(buffer)) { |
|
+ template.pValue = buffer; |
|
+ } else { |
|
+ alloc = PORT_Alloc(template.ulValueLen); |
|
+ template.pValue = alloc; |
|
+ } |
|
+ if (template.pValue == NULL) { |
|
+ PR_fprintf(PR_STDERR, " " |
|
+ "Could allocate %d bytes for Attribute %s (0x%08x)\n", |
|
+ (int) template.ulValueLen, |
|
+ AttributeName(attribute), (int)attribute); |
|
+ continue; |
|
+ } |
|
+ crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1); |
|
+ |
|
+ if (crv != CKR_OK) { |
|
+ if (crv != CKR_ATTRIBUTE_TYPE_INVALID) { |
|
+ PR_fprintf(PR_STDERR, " " |
|
+ "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n", |
|
+ AttributeName(attribute), (int)attribute, |
|
+ ErrorName(crv), (int)crv); |
|
+ } |
|
+ if (alloc) { |
|
+ PORT_Free(alloc); |
|
+ alloc = NULL; |
|
+ } |
|
+ continue; |
|
+ } |
|
+ |
|
+ dumpAttribute(&template, keydb, isKey, id); |
|
+ dumpSignature(template.type, keydb, isKey, id, PR_FALSE); |
|
+ if (alloc) { |
|
+ PORT_Free(alloc); |
|
+ alloc = NULL; |
|
+ } |
|
+ } |
|
+} |
|
+ |
|
+/* dump all the objects in a database */ |
|
+void |
|
+dumpDB(SDB *db, const char *name, SDB *keydb, PRBool isKey) |
|
+{ |
|
+ SDBFind *findHandle= NULL; |
|
+ CK_BBOOL isTrue = 1; |
|
+ CK_ATTRIBUTE allObjectTemplate = {CKA_TOKEN, NULL, 1 }; |
|
+ CK_ULONG allObjectTemplateCount = 1; |
|
+ PRBool recordFound = PR_FALSE; |
|
+ CK_RV crv = CKR_OK; |
|
+ CK_ULONG objectCount = 0; |
|
+ printf("%s:\n",name); |
|
+ |
|
+ allObjectTemplate.pValue = &isTrue; |
|
+ crv = (*db->sdb_FindObjectsInit)(db, &allObjectTemplate, |
|
+ allObjectTemplateCount, &findHandle); |
|
+ do { |
|
+ CK_OBJECT_HANDLE id; |
|
+ recordFound = PR_FALSE; |
|
+ crv =(*db->sdb_FindObjects)(db, findHandle, &id, 1, &objectCount); |
|
+ if ((crv == CKR_OK) && (objectCount == 1)) { |
|
+ recordFound = PR_TRUE; |
|
+ dumpObject(id, db, keydb, isKey); |
|
+ } |
|
+ } while (recordFound); |
|
+ if (crv != CKR_OK) { |
|
+ PR_fprintf(PR_STDERR, |
|
+ "Last record return PKCS #11 error = %s (0x%08x)\n", |
|
+ ErrorName(crv), (int)crv); |
|
+ } |
|
+ (*db->sdb_FindObjectsFinal)(db,findHandle); |
|
+} |
|
+ |
|
+int |
|
+main(int argc, char **argv) |
|
+{ |
|
+ PLOptState *optstate; |
|
+ PLOptStatus optstatus; |
|
+ char *certPrefix="", *keyPrefix=""; |
|
+ int cert_version = 9; |
|
+ int key_version = 4; |
|
+ SDB *certdb = NULL; |
|
+ SDB *keydb = NULL; |
|
+ PRBool isNew = PR_FALSE; |
|
+ |
|
+ CK_RV crv; |
|
+ |
|
+ progName = strrchr(argv[0], '/'); |
|
+ if (!progName) |
|
+ progName = strrchr(argv[0], '\\'); |
|
+ progName = progName ? progName + 1 : argv[0]; |
|
+ |
|
+ optstate = PL_CreateOptState(argc, argv, "d:c:k:v:V:h"); |
|
+ |
|
+ while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) { |
|
+ switch (optstate->option) { |
|
+ case 'h': |
|
+ default: |
|
+ Usage(); |
|
+ break; |
|
+ |
|
+ case 'd': |
|
+ dbDir = PORT_Strdup(optstate->value); |
|
+ break; |
|
+ |
|
+ case 'c': |
|
+ certPrefix = PORT_Strdup(optstate->value); |
|
+ break; |
|
+ |
|
+ case 'k': |
|
+ keyPrefix = PORT_Strdup(optstate->value); |
|
+ break; |
|
+ |
|
+ case 'v': |
|
+ key_version = atoi(optstate->value); |
|
+ break; |
|
+ |
|
+ case 'V': |
|
+ cert_version = atoi(optstate->value); |
|
+ break; |
|
+ |
|
+ } |
|
+ } |
|
+ PL_DestroyOptState(optstate); |
|
+ if (optstatus == PL_OPT_BAD) |
|
+ Usage(); |
|
+ |
|
+ if (dbDir) { |
|
+ char *tmp = dbDir; |
|
+ dbDir = SECU_ConfigDirectory(tmp); |
|
+ PORT_Free(tmp); |
|
+ } else { |
|
+ /* Look in $SSL_DIR */ |
|
+ dbDir = SECU_ConfigDirectory(SECU_DefaultSSLDir()); |
|
+ } |
|
+ PR_fprintf(PR_STDERR, "dbdir selected is %s\n\n", dbDir); |
|
+ |
|
+ if (dbDir[0] == '\0') { |
|
+ PR_fprintf(PR_STDERR, errStrings[DIR_DOESNT_EXIST_ERR], dbDir); |
|
+ return 1; |
|
+ } |
|
+ |
|
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); |
|
+ SECOID_Init(); |
|
+ |
|
+ crv = s_open(dbDir, certPrefix, keyPrefix, cert_version, key_version, |
|
+ SDB_RDONLY, &certdb, &keydb, &isNew); |
|
+ if (crv != CKR_OK) { |
|
+ PR_fprintf(PR_STDERR, |
|
+ "Couldn't open databased in %s, error=%s (0x%08x)\n", |
|
+ dbDir, ErrorName(crv), (int)crv); |
|
+ return 1; |
|
+ } |
|
+ |
|
+ /* now dump the objects in the cert database */ |
|
+ dumpDB(certdb, "CertDB", keydb, PR_FALSE); |
|
+ dumpDB(keydb, "KeyDB", keydb, PR_TRUE); |
|
+ return 0; |
|
+} |
|
diff --git a/cmd/dbtool/dbtool.gyp b/cmd/dbtool/dbtool.gyp |
|
new file mode 100644 |
|
--- /dev/null |
|
+++ b/cmd/dbtool/dbtool.gyp |
|
@@ -0,0 +1,25 @@ |
|
+# This Source Code Form is subject to the terms of the Mozilla Public |
|
+# License, v. 2.0. If a copy of the MPL was not distributed with this |
|
+# file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
+{ |
|
+ 'includes': [ |
|
+ '../../coreconf/config.gypi', |
|
+ '../../cmd/platlibs.gypi' |
|
+ ], |
|
+ 'targets': [ |
|
+ { |
|
+ 'target_name': 'dbtest', |
|
+ 'type': 'executable', |
|
+ 'sources': [ |
|
+ 'dbtest.c' |
|
+ ], |
|
+ 'dependencies': [ |
|
+ '<(DEPTH)/exports.gyp:dbm_exports', |
|
+ '<(DEPTH)/exports.gyp:nss_exports' |
|
+ ] |
|
+ } |
|
+ ], |
|
+ 'variables': { |
|
+ 'module': 'nss' |
|
+ } |
|
+} |
|
\ No newline at end of file |
|
diff --git a/cmd/dbtool/manifest.mn b/cmd/dbtool/manifest.mn |
|
new file mode 100644 |
|
--- /dev/null |
|
+++ b/cmd/dbtool/manifest.mn |
|
@@ -0,0 +1,18 @@ |
|
+# |
|
+# This Source Code Form is subject to the terms of the Mozilla Public |
|
+# License, v. 2.0. If a copy of the MPL was not distributed with this |
|
+# file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
+ |
|
+CORE_DEPTH = ../.. |
|
+ |
|
+# MODULE public and private header directories are implicitly REQUIRED. |
|
+MODULE = nss |
|
+ |
|
+USE_STATIC_LIBS = 1 |
|
+ |
|
+# DIRS = |
|
+ |
|
+CSRCS = dbtool.c sdb.c |
|
+ |
|
+PROGRAM = dbtool |
|
+ |
|
diff --git a/cmd/dbtool/sdb.c b/cmd/dbtool/sdb.c |
|
new file mode 100644 |
|
--- /dev/null |
|
+++ b/cmd/dbtool/sdb.c |
|
@@ -0,0 +1,2469 @@ |
|
+/* This Source Code Form is subject to the terms of the Mozilla Public |
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
+/* |
|
+ * This file implements PKCS 11 on top of our existing security modules |
|
+ * |
|
+ * For more information about PKCS 11 See PKCS 11 Token Inteface Standard. |
|
+ * This implementation has two slots: |
|
+ * slot 1 is our generic crypto support. It does not require login. |
|
+ * It supports Public Key ops, and all they bulk ciphers and hashes. |
|
+ * It can also support Private Key ops for imported Private keys. It does |
|
+ * not have any token storage. |
|
+ * slot 2 is our private key support. It requires a login before use. It |
|
+ * can store Private Keys and Certs as token objects. Currently only private |
|
+ * keys and their associated Certificates are saved on the token. |
|
+ * |
|
+ * In this implementation, session objects are only visible to the session |
|
+ * that created or generated them. |
|
+ */ |
|
+ |
|
+#include "sdb.h" |
|
+#include "pkcs11t.h" |
|
+#include "seccomon.h" |
|
+#include <sqlite3.h> |
|
+#include "prthread.h" |
|
+#include "prio.h" |
|
+#include <stdio.h> |
|
+#include "secport.h" |
|
+#include "prmon.h" |
|
+#include "prenv.h" |
|
+#include "prprf.h" |
|
+#include "prsystem.h" /* for PR_GetDirectorySeparator() */ |
|
+#include <sys/stat.h> |
|
+#if defined(_WIN32) |
|
+#include <io.h> |
|
+#include <windows.h> |
|
+#elif defined(XP_UNIX) |
|
+#include <unistd.h> |
|
+#endif |
|
+#if defined(LINUX) && !defined(ANDROID) |
|
+#include <linux/magic.h> |
|
+#include <sys/vfs.h> |
|
+#endif |
|
+#include "utilpars.h" |
|
+ |
|
+#ifdef SQLITE_UNSAFE_THREADS |
|
+#include "prlock.h" |
|
+/* |
|
+ * SQLite can be compiled to be thread safe or not. |
|
+ * turn on SQLITE_UNSAFE_THREADS if the OS does not support |
|
+ * a thread safe version of sqlite. |
|
+ */ |
|
+static PRLock *sqlite_lock = NULL; |
|
+ |
|
+#define LOCK_SQLITE() PR_Lock(sqlite_lock); |
|
+#define UNLOCK_SQLITE() PR_Unlock(sqlite_lock); |
|
+#else |
|
+#define LOCK_SQLITE() |
|
+#define UNLOCK_SQLITE() |
|
+#endif |
|
+ |
|
+typedef enum { |
|
+ SDB_CERT = 1, |
|
+ SDB_KEY = 2 |
|
+} sdbDataType; |
|
+ |
|
+/* |
|
+ * defines controlling how long we wait to acquire locks. |
|
+ * |
|
+ * SDB_SQLITE_BUSY_TIMEOUT specifies how long (in milliseconds) |
|
+ * sqlite will wait on lock. If that timeout expires, sqlite will |
|
+ * return SQLITE_BUSY. |
|
+ * SDB_BUSY_RETRY_TIME specifies how many seconds the sdb_ code waits |
|
+ * after receiving a busy before retrying. |
|
+ * SDB_MAX_BUSY_RETRIES specifies how many times the sdb_ will retry on |
|
+ * a busy condition. |
|
+ * |
|
+ * SDB_SQLITE_BUSY_TIMEOUT affects all opertions, both manual |
|
+ * (prepare/step/reset/finalize) and automatic (sqlite3_exec()). |
|
+ * SDB_BUSY_RETRY_TIME and SDB_MAX_BUSY_RETRIES only affect manual operations |
|
+ * |
|
+ * total wait time for automatic operations: |
|
+ * 1 second (SDB_SQLITE_BUSY_TIMEOUT/1000). |
|
+ * total wait time for manual operations: |
|
+ * (1 second + SDB_BUSY_RETRY_TIME) * 30 = 30 seconds. |
|
+ * (SDB_SQLITE_BUSY_TIMEOUT/1000 + SDB_BUSY_RETRY_TIME)*SDB_MAX_BUSY_RETRIES |
|
+ */ |
|
+#define SDB_SQLITE_BUSY_TIMEOUT 1000 /* milliseconds */ |
|
+#define SDB_BUSY_RETRY_TIME 5 /* 'ticks', varies by platforms */ |
|
+#define SDB_MAX_BUSY_RETRIES 30 |
|
+ |
|
+/* |
|
+ * known attributes |
|
+ */ |
|
+static const CK_ATTRIBUTE_TYPE known_attributes[] = { |
|
+ CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION, |
|
+ CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER, |
|
+ CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED, |
|
+ CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL, |
|
+ CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY, |
|
+ CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE, |
|
+ CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER, |
|
+ CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE, |
|
+ CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT, |
|
+ CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT, |
|
+ CKA_PUBLIC_KEY_INFO, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS, |
|
+ CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE, |
|
+ CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE, |
|
+ CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS, |
|
+ CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS, |
|
+ CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_HW_FEATURE_TYPE, |
|
+ CKA_RESET_ON_INIT, CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, |
|
+ CKA_RESOLUTION, CKA_CHAR_ROWS, CKA_CHAR_COLUMNS, CKA_COLOR, |
|
+ CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, CKA_ENCODING_METHODS, CKA_MIME_TYPES, |
|
+ CKA_MECHANISM_TYPE, CKA_REQUIRED_CMS_ATTRIBUTES, |
|
+ CKA_DEFAULT_CMS_ATTRIBUTES, CKA_SUPPORTED_CMS_ATTRIBUTES, |
|
+ CKA_WRAP_TEMPLATE, CKA_UNWRAP_TEMPLATE, CKA_NSS_TRUST, CKA_NSS_URL, |
|
+ CKA_NSS_EMAIL, CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP, |
|
+ CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES, |
|
+ CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED, |
|
+ CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC, |
|
+ CKA_NSS_OVERRIDE_EXTENSIONS, CKA_NSS_SERVER_DISTRUST_AFTER, |
|
+ CKA_NSS_EMAIL_DISTRUST_AFTER, CKA_TRUST_DIGITAL_SIGNATURE, |
|
+ CKA_TRUST_NON_REPUDIATION, CKA_TRUST_KEY_ENCIPHERMENT, |
|
+ CKA_TRUST_DATA_ENCIPHERMENT, CKA_TRUST_KEY_AGREEMENT, |
|
+ CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, CKA_TRUST_SERVER_AUTH, |
|
+ CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION, |
|
+ CKA_TRUST_IPSEC_END_SYSTEM, CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, |
|
+ CKA_TRUST_TIME_STAMPING, CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, |
|
+ CKA_CERT_MD5_HASH, CKA_NSS_DB |
|
+}; |
|
+ |
|
+static const int known_attributes_size = PR_ARRAY_SIZE(known_attributes); |
|
+ |
|
+/* |
|
+ * Note on use of sqlReadDB: Only one thread at a time may have an actual |
|
+ * operation going on given sqlite3 * database. An operation is defined as |
|
+ * the time from a sqlite3_prepare() until the sqlite3_finalize(). |
|
+ * Multiple sqlite3 * databases can be open and have simultaneous operations |
|
+ * going. We use the sqlXactDB for all write operations. This database |
|
+ * is only opened when we first create a transaction and closed when the |
|
+ * transaction is complete. sqlReadDB is open when we first opened the database |
|
+ * and is used for all read operation. It's use is protected by a monitor. This |
|
+ * is because an operation can span the use of FindObjectsInit() through the |
|
+ * call to FindObjectsFinal(). In the intermediate time it is possible to call |
|
+ * other operations like NSC_GetAttributeValue */ |
|
+ |
|
+struct SDBPrivateStr { |
|
+ char *sqlDBName; /* invariant, path to this database */ |
|
+ sqlite3 *sqlXactDB; /* access protected by dbMon, use protected |
|
+ * by the transaction. Current transaction db*/ |
|
+ PRThread *sqlXactThread; /* protected by dbMon, |
|
+ * current transaction thread */ |
|
+ sqlite3 *sqlReadDB; /* use protected by dbMon, value invariant */ |
|
+ PRIntervalTime lastUpdateTime; /* last time the cache was updated */ |
|
+ PRIntervalTime updateInterval; /* how long the cache can go before it |
|
+ * must be updated again */ |
|
+ sdbDataType type; /* invariant, database type */ |
|
+ char *table; /* invariant, SQL table which contains the db */ |
|
+ char *cacheTable; /* invariant, SQL table cache of db */ |
|
+ PRMonitor *dbMon; /* invariant, monitor to protect |
|
+ * sqlXact* fields, and use of the sqlReadDB */ |
|
+ CK_ATTRIBUTE_TYPE *schemaAttrs; /* Attribute columns that exist in the table. */ |
|
+ unsigned int numSchemaAttrs; |
|
+}; |
|
+ |
|
+typedef struct SDBPrivateStr SDBPrivate; |
|
+ |
|
+/* Magic for an explicit NULL. NOTE: ideally this should be |
|
+ * out of band data. Since it's not completely out of band, pick |
|
+ * a value that has no meaning to any existing PKCS #11 attributes. |
|
+ * This value is 1) not a valid string (imbedded '\0'). 2) not a U_LONG |
|
+ * or a normal key (too short). 3) not a bool (too long). 4) not an RSA |
|
+ * public exponent (too many bits). |
|
+ */ |
|
+const unsigned char SQLITE_EXPLICIT_NULL[] = { 0xa5, 0x0, 0x5a }; |
|
+#define SQLITE_EXPLICIT_NULL_LEN 3 |
|
+ |
|
+/* |
|
+ * determine when we've completed our tasks |
|
+ */ |
|
+static int |
|
+sdb_done(int err, int *count) |
|
+{ |
|
+ /* allow as many rows as the database wants to give */ |
|
+ if (err == SQLITE_ROW) { |
|
+ *count = 0; |
|
+ return 0; |
|
+ } |
|
+ if (err != SQLITE_BUSY) { |
|
+ return 1; |
|
+ } |
|
+ /* err == SQLITE_BUSY, Dont' retry forever in this case */ |
|
+ if (++(*count) >= SDB_MAX_BUSY_RETRIES) { |
|
+ return 1; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+#if defined(_WIN32) |
|
+/* |
|
+ * NSPR functions and narrow CRT functions do not handle UTF-8 file paths that |
|
+ * sqlite3 expects. |
|
+ */ |
|
+ |
|
+static int |
|
+sdb_chmod(const char *filename, int pmode) |
|
+{ |
|
+ int result; |
|
+ |
|
+ if (!filename) { |
|
+ return -1; |
|
+ } |
|
+ |
|
+ wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename); |
|
+ if (!filenameWide) { |
|
+ return -1; |
|
+ } |
|
+ result = _wchmod(filenameWide, pmode); |
|
+ PORT_Free(filenameWide); |
|
+ |
|
+ return result; |
|
+} |
|
+#else |
|
+#define sdb_chmod(filename, pmode) chmod((filename), (pmode)) |
|
+#endif |
|
+ |
|
+/* |
|
+ * find out where sqlite stores the temp tables. We do this by replicating |
|
+ * the logic from sqlite. |
|
+ */ |
|
+#if defined(_WIN32) |
|
+static char * |
|
+sdb_getFallbackTempDir(void) |
|
+{ |
|
+ /* sqlite uses sqlite3_temp_directory if it is not NULL. We don't have |
|
+ * access to sqlite3_temp_directory because it is not exported from |
|
+ * sqlite3.dll. Assume sqlite3_win32_set_directory isn't called and |
|
+ * sqlite3_temp_directory is NULL. |
|
+ */ |
|
+ char path[MAX_PATH]; |
|
+ DWORD rv; |
|
+ size_t len; |
|
+ |
|
+ rv = GetTempPathA(MAX_PATH, path); |
|
+ if (rv > MAX_PATH || rv == 0) |
|
+ return NULL; |
|
+ len = strlen(path); |
|
+ if (len == 0) |
|
+ return NULL; |
|
+ /* The returned string ends with a backslash, for example, "C:\TEMP\". */ |
|
+ if (path[len - 1] == '\\') |
|
+ path[len - 1] = '\0'; |
|
+ return PORT_Strdup(path); |
|
+} |
|
+#elif defined(XP_UNIX) |
|
+static char * |
|
+sdb_getFallbackTempDir(void) |
|
+{ |
|
+ const char *azDirs[] = { |
|
+ NULL, |
|
+ NULL, |
|
+ "/var/tmp", |
|
+ "/usr/tmp", |
|
+ "/tmp", |
|
+ NULL /* List terminator */ |
|
+ }; |
|
+ unsigned int i; |
|
+ struct stat buf; |
|
+ const char *zDir = NULL; |
|
+ |
|
+ azDirs[0] = sqlite3_temp_directory; |
|
+ azDirs[1] = PR_GetEnvSecure("TMPDIR"); |
|
+ |
|
+ for (i = 0; i < PR_ARRAY_SIZE(azDirs); i++) { |
|
+ zDir = azDirs[i]; |
|
+ if (zDir == NULL) |
|
+ continue; |
|
+ if (stat(zDir, &buf)) |
|
+ continue; |
|
+ if (!S_ISDIR(buf.st_mode)) |
|
+ continue; |
|
+ if (access(zDir, 07)) |
|
+ continue; |
|
+ break; |
|
+ } |
|
+ |
|
+ if (zDir == NULL) |
|
+ return NULL; |
|
+ return PORT_Strdup(zDir); |
|
+} |
|
+#else |
|
+#error "sdb_getFallbackTempDir not implemented" |
|
+#endif |
|
+ |
|
+#ifndef SQLITE_FCNTL_TEMPFILENAME |
|
+/* SQLITE_FCNTL_TEMPFILENAME was added in SQLite 3.7.15 */ |
|
+#define SQLITE_FCNTL_TEMPFILENAME 16 |
|
+#endif |
|
+ |
|
+static char * |
|
+sdb_getTempDir(sqlite3 *sqlDB) |
|
+{ |
|
+ int sqlrv; |
|
+ char *result = NULL; |
|
+ char *tempName = NULL; |
|
+ char *foundSeparator = NULL; |
|
+ |
|
+ /* Obtain temporary filename in sqlite's directory for temporary tables */ |
|
+ sqlrv = sqlite3_file_control(sqlDB, 0, SQLITE_FCNTL_TEMPFILENAME, |
|
+ (void *)&tempName); |
|
+ if (sqlrv == SQLITE_NOTFOUND) { |
|
+ /* SQLITE_FCNTL_TEMPFILENAME not implemented because we are using |
|
+ * an older SQLite. */ |
|
+ return sdb_getFallbackTempDir(); |
|
+ } |
|
+ if (sqlrv != SQLITE_OK) { |
|
+ return NULL; |
|
+ } |
|
+ |
|
+ /* We'll extract the temporary directory from tempName */ |
|
+ foundSeparator = PORT_Strrchr(tempName, PR_GetDirectorySeparator()); |
|
+ if (foundSeparator) { |
|
+ /* We shorten the temp filename string to contain only |
|
+ * the directory name (including the trailing separator). |
|
+ * We know the byte after the foundSeparator position is |
|
+ * safe to use, in the shortest scenario it contains the |
|
+ * end-of-string byte. |
|
+ * By keeping the separator at the found position, it will |
|
+ * even work if tempDir consists of the separator, only. |
|
+ * (In this case the toplevel directory will be used for |
|
+ * access speed testing). */ |
|
+ ++foundSeparator; |
|
+ *foundSeparator = 0; |
|
+ |
|
+ /* Now we copy the directory name for our caller */ |
|
+ result = PORT_Strdup(tempName); |
|
+ } |
|
+ |
|
+ sqlite3_free(tempName); |
|
+ return result; |
|
+} |
|
+ |
|
+/* |
|
+ * Map SQL_LITE errors to PKCS #11 errors as best we can. |
|
+ */ |
|
+static CK_RV |
|
+sdb_mapSQLError(sdbDataType type, int sqlerr) |
|
+{ |
|
+ switch (sqlerr) { |
|
+ /* good matches */ |
|
+ case SQLITE_OK: |
|
+ case SQLITE_DONE: |
|
+ return CKR_OK; |
|
+ case SQLITE_NOMEM: |
|
+ return CKR_HOST_MEMORY; |
|
+ case SQLITE_READONLY: |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ /* close matches */ |
|
+ case SQLITE_AUTH: |
|
+ case SQLITE_PERM: |
|
+ /*return CKR_USER_NOT_LOGGED_IN; */ |
|
+ case SQLITE_CANTOPEN: |
|
+ case SQLITE_NOTFOUND: |
|
+ /* NSS distiguishes between failure to open the cert and the key db */ |
|
+ return type == SDB_CERT ? CKR_NSS_CERTDB_FAILED : CKR_NSS_KEYDB_FAILED; |
|
+ case SQLITE_IOERR: |
|
+ return CKR_DEVICE_ERROR; |
|
+ default: |
|
+ break; |
|
+ } |
|
+ return CKR_GENERAL_ERROR; |
|
+} |
|
+ |
|
+/* |
|
+ * build up database name from a directory, prefix, name, version and flags. |
|
+ */ |
|
+static char * |
|
+sdb_BuildFileName(const char *directory, |
|
+ const char *prefix, const char *type, |
|
+ int version) |
|
+{ |
|
+ char *dbname = NULL; |
|
+ /* build the full dbname */ |
|
+ dbname = sqlite3_mprintf("%s%c%s%s%d.db", directory, |
|
+ (int)(unsigned char)PR_GetDirectorySeparator(), |
|
+ prefix, type, version); |
|
+ return dbname; |
|
+} |
|
+ |
|
+/* |
|
+ * find out how expensive the access system call is for non-existant files |
|
+ * in the given directory. Return the number of operations done in 33 ms. |
|
+ */ |
|
+static PRUint32 |
|
+sdb_measureAccess(const char *directory) |
|
+{ |
|
+ PRUint32 i; |
|
+ PRIntervalTime time; |
|
+ PRIntervalTime delta; |
|
+ PRIntervalTime duration = PR_MillisecondsToInterval(33); |
|
+ const char *doesntExistName = "_dOeSnotExist_.db"; |
|
+ char *temp, *tempStartOfFilename; |
|
+ size_t maxTempLen, maxFileNameLen, directoryLength, tmpdirLength = 0; |
|
+#ifdef SDB_MEASURE_USE_TEMP_DIR |
|
+ /* |
|
+ * on some OS's and Filesystems, creating a bunch of files and deleting |
|
+ * them messes up the systems's caching, but if we create the files in |
|
+ * a temp directory which we later delete, then the cache gets cleared |
|
+ * up. This code uses several OS dependent calls, and it's not clear |
|
+ * that temp directory use won't mess up other filesystems and OS caching, |
|
+ * so if you need this for your OS, you can turn on the |
|
+ * 'SDB_MEASURE_USE_TEMP_DIR' define in coreconf |
|
+ */ |
|
+ const char template[] = "dbTemp.XXXXXX"; |
|
+ tmpdirLength = sizeof(template); |
|
+#endif |
|
+ /* no directory, just return one */ |
|
+ if (directory == NULL) { |
|
+ return 1; |
|
+ } |
|
+ |
|
+ /* our calculation assumes time is a 4 bytes == 32 bit integer */ |
|
+ PORT_Assert(sizeof(time) == 4); |
|
+ |
|
+ directoryLength = strlen(directory); |
|
+ |
|
+ maxTempLen = directoryLength + 1 /* dirname + / */ |
|
+ + tmpdirLength /* tmpdirname includes / */ |
|
+ + strlen(doesntExistName) /* filename base */ |
|
+ + 11 /* max chars for 32 bit int plus potential sign */ |
|
+ + 1; /* zero terminator */ |
|
+ |
|
+ temp = PORT_ZAlloc(maxTempLen); |
|
+ if (!temp) { |
|
+ return 1; |
|
+ } |
|
+ |
|
+ /* We'll copy directory into temp just once, then ensure it ends |
|
+ * with the directory separator. */ |
|
+ |
|
+ strcpy(temp, directory); |
|
+ if (directory[directoryLength - 1] != PR_GetDirectorySeparator()) { |
|
+ temp[directoryLength++] = PR_GetDirectorySeparator(); |
|
+ } |
|
+ |
|
+#ifdef SDB_MEASURE_USE_TEMP_DIR |
|
+ /* add the template for a temporary subdir, and create it */ |
|
+ strcat(temp, template); |
|
+ if (!mkdtemp(temp)) { |
|
+ PORT_Free(temp); |
|
+ return 1; |
|
+ } |
|
+ /* and terminate that tmp subdir with a / */ |
|
+ strcat(temp, "/"); |
|
+#endif |
|
+ |
|
+ /* Remember the position after the last separator, and calculate the |
|
+ * number of remaining bytes. */ |
|
+ tempStartOfFilename = temp + directoryLength + tmpdirLength; |
|
+ maxFileNameLen = maxTempLen - directoryLength; |
|
+ |
|
+ /* measure number of Access operations that can be done in 33 milliseconds |
|
+ * (1/30'th of a second), or 10000 operations, which ever comes first. |
|
+ */ |
|
+ time = PR_IntervalNow(); |
|
+ for (i = 0; i < 10000u; i++) { |
|
+ PRIntervalTime next; |
|
+ |
|
+ /* We'll use the variable part first in the filename string, just in |
|
+ * case it's longer than assumed, so if anything gets cut off, it |
|
+ * will be cut off from the constant part. |
|
+ * This code assumes the directory name at the beginning of |
|
+ * temp remains unchanged during our loop. */ |
|
+ PR_snprintf(tempStartOfFilename, maxFileNameLen, |
|
+ ".%lu%s", (PRUint32)(time + i), doesntExistName); |
|
+ PR_Access(temp, PR_ACCESS_EXISTS); |
|
+ next = PR_IntervalNow(); |
|
+ delta = next - time; |
|
+ if (delta >= duration) |
|
+ break; |
|
+ } |
|
+ |
|
+#ifdef SDB_MEASURE_USE_TEMP_DIR |
|
+ /* turn temp back into our tmpdir path by removing doesntExistName, and |
|
+ * remove the tmp dir */ |
|
+ *tempStartOfFilename = '\0'; |
|
+ (void)rmdir(temp); |
|
+#endif |
|
+ PORT_Free(temp); |
|
+ |
|
+ /* always return 1 or greater */ |
|
+ return i ? i : 1u; |
|
+} |
|
+ |
|
+/* |
|
+ * some file sytems are very slow to run sqlite3 on, particularly if the |
|
+ * access count is pretty high. On these filesystems is faster to create |
|
+ * a temporary database on the local filesystem and access that. This |
|
+ * code uses a temporary table to create that cache. Temp tables are |
|
+ * automatically cleared when the database handle it was created on |
|
+ * Is freed. |
|
+ */ |
|
+static const char DROP_CACHE_CMD[] = "DROP TABLE %s"; |
|
+static const char CREATE_CACHE_CMD[] = |
|
+ "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s"; |
|
+static const char CREATE_ISSUER_INDEX_CMD[] = |
|
+ "CREATE INDEX issuer ON %s (a81)"; |
|
+static const char CREATE_SUBJECT_INDEX_CMD[] = |
|
+ "CREATE INDEX subject ON %s (a101)"; |
|
+static const char CREATE_LABEL_INDEX_CMD[] = "CREATE INDEX label ON %s (a3)"; |
|
+static const char CREATE_ID_INDEX_CMD[] = "CREATE INDEX ckaid ON %s (a102)"; |
|
+ |
|
+static CK_RV |
|
+sdb_buildCache(sqlite3 *sqlDB, sdbDataType type, |
|
+ const char *cacheTable, const char *table) |
|
+{ |
|
+ char *newStr; |
|
+ int sqlerr = SQLITE_OK; |
|
+ |
|
+ newStr = sqlite3_mprintf(CREATE_CACHE_CMD, cacheTable, table); |
|
+ if (newStr == NULL) { |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ return sdb_mapSQLError(type, sqlerr); |
|
+ } |
|
+ /* failure to create the indexes is not an issue */ |
|
+ newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, cacheTable); |
|
+ if (newStr == NULL) { |
|
+ return CKR_OK; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, cacheTable); |
|
+ if (newStr == NULL) { |
|
+ return CKR_OK; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, cacheTable); |
|
+ if (newStr == NULL) { |
|
+ return CKR_OK; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, cacheTable); |
|
+ if (newStr == NULL) { |
|
+ return CKR_OK; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ return CKR_OK; |
|
+} |
|
+ |
|
+/* |
|
+ * update the cache and the data records describing it. |
|
+ * The cache is updated by dropping the temp database and recreating it. |
|
+ */ |
|
+static CK_RV |
|
+sdb_updateCache(SDBPrivate *sdb_p) |
|
+{ |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ char *newStr; |
|
+ |
|
+ /* drop the old table */ |
|
+ newStr = sqlite3_mprintf(DROP_CACHE_CMD, sdb_p->cacheTable); |
|
+ if (newStr == NULL) { |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sdb_p->sqlReadDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if ((sqlerr != SQLITE_OK) && (sqlerr != SQLITE_ERROR)) { |
|
+ /* something went wrong with the drop, don't try to refresh... |
|
+ * NOTE: SQLITE_ERROR is returned if the table doesn't exist. In |
|
+ * that case, we just continue on and try to reload it */ |
|
+ return sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ /* set up the new table */ |
|
+ error = sdb_buildCache(sdb_p->sqlReadDB, sdb_p->type, |
|
+ sdb_p->cacheTable, sdb_p->table); |
|
+ if (error == CKR_OK) { |
|
+ /* we have a new cache! */ |
|
+ sdb_p->lastUpdateTime = PR_IntervalNow(); |
|
+ } |
|
+ return error; |
|
+} |
|
+ |
|
+/* |
|
+ * The sharing of sqlite3 handles across threads is tricky. Older versions |
|
+ * couldn't at all, but newer ones can under strict conditions. Basically |
|
+ * no 2 threads can use the same handle while another thread has an open |
|
+ * stmt running. Once the sqlite3_stmt is finalized, another thread can then |
|
+ * use the database handle. |
|
+ * |
|
+ * We use monitors to protect against trying to use a database before |
|
+ * it's sqlite3_stmt is finalized. This is preferable to the opening and |
|
+ * closing the database each operation because there is significant overhead |
|
+ * in the open and close. Also continually opening and closing the database |
|
+ * defeats the cache code as the cache table is lost on close (thus |
|
+ * requiring us to have to reinitialize the cache every operation). |
|
+ * |
|
+ * An execption to the shared handle is transations. All writes happen |
|
+ * through a transaction. When we are in a transaction, we must use the |
|
+ * same database pointer for that entire transation. In this case we save |
|
+ * the transaction database and use it for all accesses on the transaction |
|
+ * thread. Other threads use the common database. |
|
+ * |
|
+ * There can only be once active transaction on the database at a time. |
|
+ * |
|
+ * sdb_openDBLocal() provides us with a valid database handle for whatever |
|
+ * state we are in (reading or in a transaction), and acquires any locks |
|
+ * appropriate to that state. It also decides when it's time to refresh |
|
+ * the cache before we start an operation. Any database handle returned |
|
+ * just eventually be closed with sdb_closeDBLocal(). |
|
+ * |
|
+ * The table returned either points to the database's physical table, or |
|
+ * to the cached shadow. Tranactions always return the physical table |
|
+ * and read operations return either the physical table or the cache |
|
+ * depending on whether or not the cache exists. |
|
+ */ |
|
+static CK_RV |
|
+sdb_openDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB, const char **table) |
|
+{ |
|
+ *sqlDB = NULL; |
|
+ |
|
+ PR_EnterMonitor(sdb_p->dbMon); |
|
+ |
|
+ if (table) { |
|
+ *table = sdb_p->table; |
|
+ } |
|
+ |
|
+ /* We're in a transaction, use the transaction DB */ |
|
+ if ((sdb_p->sqlXactDB) && (sdb_p->sqlXactThread == PR_GetCurrentThread())) { |
|
+ *sqlDB = sdb_p->sqlXactDB; |
|
+ /* only one thread can get here, safe to unlock */ |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ return CKR_OK; |
|
+ } |
|
+ |
|
+ /* |
|
+ * if we are just reading from the table, we may have the table |
|
+ * cached in a temporary table (especially if it's on a shared FS). |
|
+ * In that case we want to see updates to the table, the the granularity |
|
+ * is on order of human scale, not computer scale. |
|
+ */ |
|
+ if (table && sdb_p->cacheTable) { |
|
+ PRIntervalTime now = PR_IntervalNow(); |
|
+ if ((now - sdb_p->lastUpdateTime) > sdb_p->updateInterval) { |
|
+ sdb_updateCache(sdb_p); |
|
+ } |
|
+ *table = sdb_p->cacheTable; |
|
+ } |
|
+ |
|
+ *sqlDB = sdb_p->sqlReadDB; |
|
+ |
|
+ /* leave holding the lock. only one thread can actually use a given |
|
+ * database connection at once */ |
|
+ |
|
+ return CKR_OK; |
|
+} |
|
+ |
|
+/* closing the local database currenly means unlocking the monitor */ |
|
+static CK_RV |
|
+sdb_closeDBLocal(SDBPrivate *sdb_p, sqlite3 *sqlDB) |
|
+{ |
|
+ if (sdb_p->sqlXactDB != sqlDB) { |
|
+ /* if we weren't in a transaction, we got a lock */ |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ } |
|
+ return CKR_OK; |
|
+} |
|
+ |
|
+/* |
|
+ * wrapper to sqlite3_open which also sets the busy_timeout |
|
+ */ |
|
+static int |
|
+sdb_openDB(const char *name, sqlite3 **sqlDB, int flags) |
|
+{ |
|
+ int sqlerr; |
|
+ int openFlags; |
|
+ |
|
+ *sqlDB = NULL; |
|
+ |
|
+ if (flags & SDB_RDONLY) { |
|
+ openFlags = SQLITE_OPEN_READONLY; |
|
+ } else { |
|
+ openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; |
|
+ /* sqlite 3.34 seem to incorrectly open readwrite. |
|
+ * when the file is readonly. Explicitly reject that issue here */ |
|
+ if ((_NSSUTIL_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) && (_NSSUTIL_Access(name, PR_ACCESS_WRITE_OK) != PR_SUCCESS)) { |
|
+ return SQLITE_READONLY; |
|
+ } |
|
+ } |
|
+ |
|
+ /* Requires SQLite 3.5.0 or newer. */ |
|
+ sqlerr = sqlite3_open_v2(name, sqlDB, openFlags, NULL); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ return sqlerr; |
|
+ } |
|
+ |
|
+ sqlerr = sqlite3_busy_timeout(*sqlDB, SDB_SQLITE_BUSY_TIMEOUT); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ sqlite3_close(*sqlDB); |
|
+ *sqlDB = NULL; |
|
+ return sqlerr; |
|
+ } |
|
+ return SQLITE_OK; |
|
+} |
|
+ |
|
+/* Sigh, if we created a new table since we opened the database, |
|
+ * the database handle will not see the new table, we need to close this |
|
+ * database and reopen it. Caller must be in a transaction or holding |
|
+ * the dbMon. sqlDB is changed on success. */ |
|
+static int |
|
+sdb_reopenDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB) |
|
+{ |
|
+ sqlite3 *newDB; |
|
+ int sqlerr; |
|
+ |
|
+ /* open a new database */ |
|
+ sqlerr = sdb_openDB(sdb_p->sqlDBName, &newDB, SDB_RDONLY); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ return sqlerr; |
|
+ } |
|
+ |
|
+ /* if we are in a transaction, we may not be holding the monitor. |
|
+ * grab it before we update the transaction database. This is |
|
+ * safe since are using monitors. */ |
|
+ PR_EnterMonitor(sdb_p->dbMon); |
|
+ /* update our view of the database */ |
|
+ if (sdb_p->sqlReadDB == *sqlDB) { |
|
+ sdb_p->sqlReadDB = newDB; |
|
+ } else if (sdb_p->sqlXactDB == *sqlDB) { |
|
+ sdb_p->sqlXactDB = newDB; |
|
+ } |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ |
|
+ /* close the old one */ |
|
+ sqlite3_close(*sqlDB); |
|
+ |
|
+ *sqlDB = newDB; |
|
+ return SQLITE_OK; |
|
+} |
|
+ |
|
+struct SDBFindStr { |
|
+ sqlite3 *sqlDB; |
|
+ sqlite3_stmt *findstmt; |
|
+}; |
|
+ |
|
+static const char FIND_OBJECTS_CMD[] = "SELECT ALL id FROM %s WHERE %s;"; |
|
+static const char FIND_OBJECTS_ALL_CMD[] = "SELECT ALL id FROM %s;"; |
|
+CK_RV |
|
+sdb_FindObjectsInit(SDB *sdb, const CK_ATTRIBUTE *template, CK_ULONG count, |
|
+ SDBFind **find) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ const char *table; |
|
+ char *newStr, *findStr = NULL; |
|
+ sqlite3_stmt *findstmt = NULL; |
|
+ char *join = ""; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ unsigned int i; |
|
+ |
|
+ LOCK_SQLITE() |
|
+ *find = NULL; |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, &table); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ findStr = sqlite3_mprintf(""); |
|
+ for (i = 0; findStr && i < count; i++) { |
|
+ newStr = sqlite3_mprintf("%s%sa%x=$DATA%d", findStr, join, |
|
+ template[i].type, i); |
|
+ join = " AND "; |
|
+ sqlite3_free(findStr); |
|
+ findStr = newStr; |
|
+ } |
|
+ |
|
+ if (findStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ if (count == 0) { |
|
+ newStr = sqlite3_mprintf(FIND_OBJECTS_ALL_CMD, table); |
|
+ } else { |
|
+ newStr = sqlite3_mprintf(FIND_OBJECTS_CMD, table, findStr); |
|
+ } |
|
+ sqlite3_free(findStr); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &findstmt, NULL); |
|
+ sqlite3_free(newStr); |
|
+ for (i = 0; sqlerr == SQLITE_OK && i < count; i++) { |
|
+ const void *blobData = template[i].pValue; |
|
+ unsigned int blobSize = template[i].ulValueLen; |
|
+ if (blobSize == 0) { |
|
+ blobSize = SQLITE_EXPLICIT_NULL_LEN; |
|
+ blobData = SQLITE_EXPLICIT_NULL; |
|
+ } |
|
+ sqlerr = sqlite3_bind_blob(findstmt, i + 1, blobData, blobSize, |
|
+ SQLITE_TRANSIENT); |
|
+ } |
|
+ if (sqlerr == SQLITE_OK) { |
|
+ *find = PORT_New(SDBFind); |
|
+ if (*find == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ (*find)->findstmt = findstmt; |
|
+ (*find)->sqlDB = sqlDB; |
|
+ UNLOCK_SQLITE() |
|
+ return CKR_OK; |
|
+ } |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ |
|
+loser: |
|
+ if (findstmt) { |
|
+ sqlite3_reset(findstmt); |
|
+ sqlite3_finalize(findstmt); |
|
+ } |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ return error; |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_FindObjects(SDB *sdb, SDBFind *sdbFind, CK_OBJECT_HANDLE *object, |
|
+ CK_ULONG arraySize, CK_ULONG *count) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3_stmt *stmt = sdbFind->findstmt; |
|
+ int sqlerr = SQLITE_OK; |
|
+ int retry = 0; |
|
+ |
|
+ *count = 0; |
|
+ |
|
+ if (arraySize == 0) { |
|
+ return CKR_OK; |
|
+ } |
|
+ LOCK_SQLITE() |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ if (sqlerr == SQLITE_ROW) { |
|
+ /* only care about the id */ |
|
+ *object++ = sqlite3_column_int(stmt, 0); |
|
+ arraySize--; |
|
+ (*count)++; |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry) && (arraySize > 0)); |
|
+ |
|
+ /* we only have some of the objects, there is probably more, |
|
+ * set the sqlerr to an OK value so we return CKR_OK */ |
|
+ if (sqlerr == SQLITE_ROW && arraySize == 0) { |
|
+ sqlerr = SQLITE_DONE; |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ |
|
+ return sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_FindObjectsFinal(SDB *sdb, SDBFind *sdbFind) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3_stmt *stmt = sdbFind->findstmt; |
|
+ sqlite3 *sqlDB = sdbFind->sqlDB; |
|
+ int sqlerr = SQLITE_OK; |
|
+ |
|
+ LOCK_SQLITE() |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlerr = sqlite3_finalize(stmt); |
|
+ } |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ PORT_Free(sdbFind); |
|
+ |
|
+ UNLOCK_SQLITE() |
|
+ return sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+} |
|
+ |
|
+static CK_RV |
|
+sdb_GetValidAttributeValueNoLock(SDB *sdb, CK_OBJECT_HANDLE object_id, |
|
+ CK_ATTRIBUTE *template, CK_ULONG count) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ const char *table = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int found = 0; |
|
+ int retry = 0; |
|
+ unsigned int i; |
|
+ |
|
+ if (count == 0) { |
|
+ error = CKR_OBJECT_HANDLE_INVALID; |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ /* open a new db if necessary */ |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, &table); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ char *columns = NULL; |
|
+ for (i = 0; i < count; i++) { |
|
+ char *newColumns; |
|
+ if (columns) { |
|
+ newColumns = sqlite3_mprintf("%s, a%x", columns, template[i].type); |
|
+ sqlite3_free(columns); |
|
+ columns = NULL; |
|
+ } else { |
|
+ newColumns = sqlite3_mprintf("a%x", template[i].type); |
|
+ } |
|
+ if (!newColumns) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ columns = newColumns; |
|
+ } |
|
+ |
|
+ PORT_Assert(columns); |
|
+ |
|
+ char *statement = sqlite3_mprintf("SELECT DISTINCT %s FROM %s where id=$ID LIMIT 1;", |
|
+ columns, table); |
|
+ sqlite3_free(columns); |
|
+ columns = NULL; |
|
+ if (!statement) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, statement, -1, &stmt, NULL); |
|
+ sqlite3_free(statement); |
|
+ statement = NULL; |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ // NB: indices in sqlite3_bind_int are 1-indexed |
|
+ sqlerr = sqlite3_bind_int(stmt, 1, object_id); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ if (sqlerr == SQLITE_ROW) { |
|
+ PORT_Assert(!found); |
|
+ for (i = 0; i < count; i++) { |
|
+ unsigned int blobSize; |
|
+ const char *blobData; |
|
+ |
|
+ // NB: indices in sqlite_column_{bytes,blob} are 0-indexed |
|
+ blobSize = sqlite3_column_bytes(stmt, i); |
|
+ blobData = sqlite3_column_blob(stmt, i); |
|
+ if (blobData == NULL) { |
|
+ /* PKCS 11 requires that get attributes process all the |
|
+ * attributes in the template, marking the attributes with |
|
+ * issues with -1. Mark the error but continue */ |
|
+ template[i].ulValueLen = -1; |
|
+ error = CKR_ATTRIBUTE_TYPE_INVALID; |
|
+ continue; |
|
+ } |
|
+ /* If the blob equals our explicit NULL value, then the |
|
+ * attribute is a NULL. */ |
|
+ if ((blobSize == SQLITE_EXPLICIT_NULL_LEN) && |
|
+ (PORT_Memcmp(blobData, SQLITE_EXPLICIT_NULL, |
|
+ SQLITE_EXPLICIT_NULL_LEN) == 0)) { |
|
+ blobSize = 0; |
|
+ } |
|
+ if (template[i].pValue) { |
|
+ if (template[i].ulValueLen < blobSize) { |
|
+ /* like CKR_ATTRIBUTE_TYPE_INVALID, continue processing */ |
|
+ template[i].ulValueLen = -1; |
|
+ error = CKR_BUFFER_TOO_SMALL; |
|
+ continue; |
|
+ } |
|
+ PORT_Memcpy(template[i].pValue, blobData, blobSize); |
|
+ } |
|
+ template[i].ulValueLen = blobSize; |
|
+ } |
|
+ found = 1; |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ stmt = NULL; |
|
+ |
|
+loser: |
|
+ /* fix up the error if necessary */ |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ if (!found && error == CKR_OK) { |
|
+ error = CKR_OBJECT_HANDLE_INVALID; |
|
+ } |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ /* if we had to open a new database, free it now */ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ return error; |
|
+} |
|
+ |
|
+/* NOTE: requires sdb_p->schemaAttrs to be sorted asc. */ |
|
+inline static PRBool |
|
+sdb_attributeExists(SDB *sdb, CK_ATTRIBUTE_TYPE attr) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ int first = 0; |
|
+ int last = (int)sdb_p->numSchemaAttrs - 1; |
|
+ while (last >= first) { |
|
+ int mid = first + (last - first) / 2; |
|
+ if (sdb_p->schemaAttrs[mid] == attr) { |
|
+ return PR_TRUE; |
|
+ } |
|
+ if (attr > sdb_p->schemaAttrs[mid]) { |
|
+ first = mid + 1; |
|
+ } else { |
|
+ last = mid - 1; |
|
+ } |
|
+ } |
|
+ |
|
+ return PR_FALSE; |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_GetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id, |
|
+ CK_ATTRIBUTE *template, CK_ULONG count) |
|
+{ |
|
+ CK_RV crv = CKR_OK; |
|
+ unsigned int tmplIdx; |
|
+ unsigned int resIdx = 0; |
|
+ unsigned int validCount = 0; |
|
+ unsigned int i; |
|
+ |
|
+ if (count == 0) { |
|
+ return crv; |
|
+ } |
|
+ |
|
+ CK_ATTRIBUTE *validTemplate; |
|
+ PRBool invalidExists = PR_FALSE; |
|
+ for (tmplIdx = 0; tmplIdx < count; tmplIdx++) { |
|
+ if (!sdb_attributeExists(sdb, template[tmplIdx].type)) { |
|
+ template[tmplIdx].ulValueLen = -1; |
|
+ crv = CKR_ATTRIBUTE_TYPE_INVALID; |
|
+ invalidExists = PR_TRUE; |
|
+ break; |
|
+ } |
|
+ } |
|
+ |
|
+ if (!invalidExists) { |
|
+ validTemplate = template; |
|
+ validCount = count; |
|
+ } else { |
|
+ /* Create a new template containing only the valid subset of |
|
+ * input |template|, and query with that. */ |
|
+ validCount = tmplIdx; |
|
+ validTemplate = malloc(sizeof(CK_ATTRIBUTE) * count); |
|
+ if (!validTemplate) { |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ /* Copy in what we already know is valid. */ |
|
+ for (i = 0; i < validCount; i++) { |
|
+ validTemplate[i] = template[i]; |
|
+ } |
|
+ |
|
+ /* tmplIdx was left at the index of the first invalid |
|
+ * attribute, which has been handled. We only need to |
|
+ * deal with the remainder. */ |
|
+ tmplIdx++; |
|
+ for (; tmplIdx < count; tmplIdx++) { |
|
+ if (sdb_attributeExists(sdb, template[tmplIdx].type)) { |
|
+ validTemplate[validCount++] = template[tmplIdx]; |
|
+ } else { |
|
+ template[tmplIdx].ulValueLen = -1; |
|
+ } |
|
+ } |
|
+ } |
|
+ |
|
+ if (validCount) { |
|
+ LOCK_SQLITE() |
|
+ CK_RV crv2 = sdb_GetValidAttributeValueNoLock(sdb, object_id, validTemplate, validCount); |
|
+ UNLOCK_SQLITE() |
|
+ |
|
+ /* If an invalid attribute was removed above, let |
|
+ * the caller know. Any other error from the actual |
|
+ * query should propogate. */ |
|
+ crv = (crv2 == CKR_OK) ? crv : crv2; |
|
+ } |
|
+ |
|
+ if (invalidExists) { |
|
+ /* Copy out valid lengths. */ |
|
+ tmplIdx = 0; |
|
+ for (resIdx = 0; resIdx < validCount; resIdx++) { |
|
+ for (; tmplIdx < count; tmplIdx++) { |
|
+ if (template[tmplIdx].type != validTemplate[resIdx].type) { |
|
+ continue; |
|
+ } |
|
+ template[tmplIdx].ulValueLen = validTemplate[resIdx].ulValueLen; |
|
+ tmplIdx++; |
|
+ break; |
|
+ } |
|
+ } |
|
+ free(validTemplate); |
|
+ } |
|
+ |
|
+ return crv; |
|
+} |
|
+ |
|
+static const char SET_ATTRIBUTE_CMD[] = "UPDATE %s SET %s WHERE id=$ID;"; |
|
+CK_RV |
|
+sdb_SetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id, |
|
+ const CK_ATTRIBUTE *template, CK_ULONG count) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ char *setStr = NULL; |
|
+ char *newStr = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ int retry = 0; |
|
+ CK_RV error = CKR_OK; |
|
+ unsigned int i; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ if (count == 0) { |
|
+ return CKR_OK; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ setStr = sqlite3_mprintf(""); |
|
+ for (i = 0; setStr && i < count; i++) { |
|
+ if (i == 0) { |
|
+ sqlite3_free(setStr); |
|
+ setStr = sqlite3_mprintf("a%x=$VALUE%d", |
|
+ template[i].type, i); |
|
+ continue; |
|
+ } |
|
+ newStr = sqlite3_mprintf("%s,a%x=$VALUE%d", setStr, |
|
+ template[i].type, i); |
|
+ sqlite3_free(setStr); |
|
+ setStr = newStr; |
|
+ } |
|
+ newStr = NULL; |
|
+ |
|
+ if (setStr == NULL) { |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ newStr = sqlite3_mprintf(SET_ATTRIBUTE_CMD, sdb_p->table, setStr); |
|
+ sqlite3_free(setStr); |
|
+ if (newStr == NULL) { |
|
+ UNLOCK_SQLITE() |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ for (i = 0; i < count; i++) { |
|
+ if (template[i].ulValueLen != 0) { |
|
+ sqlerr = sqlite3_bind_blob(stmt, i + 1, template[i].pValue, |
|
+ template[i].ulValueLen, SQLITE_STATIC); |
|
+ } else { |
|
+ sqlerr = sqlite3_bind_blob(stmt, i + 1, SQLITE_EXPLICIT_NULL, |
|
+ SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC); |
|
+ } |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_bind_int(stmt, i + 1, object_id); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+loser: |
|
+ if (newStr) { |
|
+ sqlite3_free(newStr); |
|
+ } |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ |
|
+ UNLOCK_SQLITE() |
|
+ return error; |
|
+} |
|
+ |
|
+/* |
|
+ * check to see if a candidate object handle already exists. |
|
+ */ |
|
+static PRBool |
|
+sdb_objectExists(SDB *sdb, CK_OBJECT_HANDLE candidate) |
|
+{ |
|
+ CK_RV crv; |
|
+ CK_ATTRIBUTE template = { CKA_LABEL, NULL, 0 }; |
|
+ |
|
+ crv = sdb_GetValidAttributeValueNoLock(sdb, candidate, &template, 1); |
|
+ if (crv == CKR_OBJECT_HANDLE_INVALID) { |
|
+ return PR_FALSE; |
|
+ } |
|
+ return PR_TRUE; |
|
+} |
|
+ |
|
+/* |
|
+ * if we're here, we are in a transaction, so it's safe |
|
+ * to examine the current state of the database |
|
+ */ |
|
+static CK_OBJECT_HANDLE |
|
+sdb_getObjectId(SDB *sdb) |
|
+{ |
|
+ CK_OBJECT_HANDLE candidate; |
|
+ static CK_OBJECT_HANDLE next_obj = CK_INVALID_HANDLE; |
|
+ int count; |
|
+ /* |
|
+ * get an initial object handle to use |
|
+ */ |
|
+ if (next_obj == CK_INVALID_HANDLE) { |
|
+ PRTime time; |
|
+ time = PR_Now(); |
|
+ |
|
+ next_obj = (CK_OBJECT_HANDLE)(time & 0x3fffffffL); |
|
+ } |
|
+ candidate = next_obj++; |
|
+ /* detect that we've looped through all the handles... */ |
|
+ for (count = 0; count < 0x40000000; count++, candidate = next_obj++) { |
|
+ /* mask off excess bits */ |
|
+ candidate &= 0x3fffffff; |
|
+ /* if we hit zero, go to the next entry */ |
|
+ if (candidate == CK_INVALID_HANDLE) { |
|
+ continue; |
|
+ } |
|
+ /* make sure we aren't already using */ |
|
+ if (!sdb_objectExists(sdb, candidate)) { |
|
+ /* this one is free */ |
|
+ return candidate; |
|
+ } |
|
+ } |
|
+ |
|
+ /* no handle is free, fail */ |
|
+ return CK_INVALID_HANDLE; |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_GetNewObjectID(SDB *sdb, CK_OBJECT_HANDLE *object) |
|
+{ |
|
+ CK_OBJECT_HANDLE id; |
|
+ |
|
+ id = sdb_getObjectId(sdb); |
|
+ if (id == CK_INVALID_HANDLE) { |
|
+ return CKR_DEVICE_MEMORY; /* basically we ran out of resources */ |
|
+ } |
|
+ *object = id; |
|
+ return CKR_OK; |
|
+} |
|
+ |
|
+static const char CREATE_CMD[] = "INSERT INTO %s (id%s) VALUES($ID%s);"; |
|
+CK_RV |
|
+sdb_CreateObject(SDB *sdb, CK_OBJECT_HANDLE *object_id, |
|
+ const CK_ATTRIBUTE *template, CK_ULONG count) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ char *columnStr = NULL; |
|
+ char *valueStr = NULL; |
|
+ char *newStr = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ CK_OBJECT_HANDLE this_object = CK_INVALID_HANDLE; |
|
+ int retry = 0; |
|
+ unsigned int i; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ if ((*object_id != CK_INVALID_HANDLE) && |
|
+ !sdb_objectExists(sdb, *object_id)) { |
|
+ this_object = *object_id; |
|
+ } else { |
|
+ this_object = sdb_getObjectId(sdb); |
|
+ } |
|
+ if (this_object == CK_INVALID_HANDLE) { |
|
+ UNLOCK_SQLITE(); |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ columnStr = sqlite3_mprintf(""); |
|
+ valueStr = sqlite3_mprintf(""); |
|
+ *object_id = this_object; |
|
+ for (i = 0; columnStr && valueStr && i < count; i++) { |
|
+ newStr = sqlite3_mprintf("%s,a%x", columnStr, template[i].type); |
|
+ sqlite3_free(columnStr); |
|
+ columnStr = newStr; |
|
+ newStr = sqlite3_mprintf("%s,$VALUE%d", valueStr, i); |
|
+ sqlite3_free(valueStr); |
|
+ valueStr = newStr; |
|
+ } |
|
+ newStr = NULL; |
|
+ if ((columnStr == NULL) || (valueStr == NULL)) { |
|
+ if (columnStr) { |
|
+ sqlite3_free(columnStr); |
|
+ } |
|
+ if (valueStr) { |
|
+ sqlite3_free(valueStr); |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ return CKR_HOST_MEMORY; |
|
+ } |
|
+ newStr = sqlite3_mprintf(CREATE_CMD, sdb_p->table, columnStr, valueStr); |
|
+ sqlite3_free(columnStr); |
|
+ sqlite3_free(valueStr); |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ sqlerr = sqlite3_bind_int(stmt, 1, *object_id); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ for (i = 0; i < count; i++) { |
|
+ if (template[i].ulValueLen) { |
|
+ sqlerr = sqlite3_bind_blob(stmt, i + 2, template[i].pValue, |
|
+ template[i].ulValueLen, SQLITE_STATIC); |
|
+ } else { |
|
+ sqlerr = sqlite3_bind_blob(stmt, i + 2, SQLITE_EXPLICIT_NULL, |
|
+ SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC); |
|
+ } |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+loser: |
|
+ if (newStr) { |
|
+ sqlite3_free(newStr); |
|
+ } |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ |
|
+ return error; |
|
+} |
|
+ |
|
+/* |
|
+ * Generic destroy that can destroy metadata or objects |
|
+ */ |
|
+static const char DESTROY_CMD[] = "DELETE FROM %s WHERE (id=$ID);"; |
|
+CK_RV |
|
+sdb_destroyAnyObject(SDB *sdb, const char *table, |
|
+ CK_OBJECT_HANDLE object_id, const char *string_id) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ char *newStr = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int retry = 0; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ newStr = sqlite3_mprintf(DESTROY_CMD, table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ if (string_id == NULL) { |
|
+ sqlerr = sqlite3_bind_int(stmt, 1, object_id); |
|
+ } else { |
|
+ sqlerr = sqlite3_bind_text(stmt, 1, string_id, |
|
+ PORT_Strlen(string_id), SQLITE_STATIC); |
|
+ } |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+loser: |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ |
|
+ UNLOCK_SQLITE() |
|
+ return error; |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_DestroyObject(SDB *sdb, CK_OBJECT_HANDLE object_id) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ return sdb_destroyAnyObject(sdb, sdb_p->table, object_id, NULL); |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_DestroyMetaData(SDB *sdb, const char *id) |
|
+{ |
|
+ return sdb_destroyAnyObject(sdb, "metaData", 0, id); |
|
+} |
|
+ |
|
+static const char BEGIN_CMD[] = "BEGIN IMMEDIATE TRANSACTION;"; |
|
+ |
|
+/* |
|
+ * start a transaction. |
|
+ * |
|
+ * We need to open a new database, then store that new database into |
|
+ * the private data structure. We open the database first, then use locks |
|
+ * to protect storing the data to prevent deadlocks. |
|
+ */ |
|
+CK_RV |
|
+sdb_Begin(SDB *sdb) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int retry = 0; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ |
|
+ /* get a new version that we will use for the entire transaction */ |
|
+ sqlerr = sdb_openDB(sdb_p->sqlDBName, &sqlDB, SDB_RDWR); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, BEGIN_CMD, -1, &stmt, NULL); |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ /* don't retry BEGIN transaction*/ |
|
+ retry = 0; |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+loser: |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ |
|
+ /* we are starting a new transaction, |
|
+ * and if we succeeded, then save this database for the rest of |
|
+ * our transaction */ |
|
+ if (error == CKR_OK) { |
|
+ /* we hold a 'BEGIN TRANSACTION' and a sdb_p->lock. At this point |
|
+ * sdb_p->sqlXactDB MUST be null */ |
|
+ PR_EnterMonitor(sdb_p->dbMon); |
|
+ PORT_Assert(sdb_p->sqlXactDB == NULL); |
|
+ sdb_p->sqlXactDB = sqlDB; |
|
+ sdb_p->sqlXactThread = PR_GetCurrentThread(); |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ } else { |
|
+ /* we failed to start our transaction, |
|
+ * free any databases we opened. */ |
|
+ if (sqlDB) { |
|
+ sqlite3_close(sqlDB); |
|
+ } |
|
+ } |
|
+ |
|
+ UNLOCK_SQLITE() |
|
+ return error; |
|
+} |
|
+ |
|
+/* |
|
+ * Complete a transaction. Basically undo everything we did in begin. |
|
+ * There are 2 flavors Abort and Commit. Basically the only differerence between |
|
+ * these 2 are what the database will show. (no change in to former, change in |
|
+ * the latter). |
|
+ */ |
|
+static CK_RV |
|
+sdb_complete(SDB *sdb, const char *cmd) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int retry = 0; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ /* We must have a transation database, or we shouldn't have arrived here */ |
|
+ PR_EnterMonitor(sdb_p->dbMon); |
|
+ PORT_Assert(sdb_p->sqlXactDB); |
|
+ if (sdb_p->sqlXactDB == NULL) { |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ return CKR_GENERAL_ERROR; /* shouldn't happen */ |
|
+ } |
|
+ PORT_Assert(sdb_p->sqlXactThread == PR_GetCurrentThread()); |
|
+ if (sdb_p->sqlXactThread != PR_GetCurrentThread()) { |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ return CKR_GENERAL_ERROR; /* shouldn't happen */ |
|
+ } |
|
+ sqlDB = sdb_p->sqlXactDB; |
|
+ sdb_p->sqlXactDB = NULL; /* no one else can get to this DB, |
|
+ * safe to unlock */ |
|
+ sdb_p->sqlXactThread = NULL; |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL); |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+ /* Pending BEGIN TRANSACTIONS Can move forward at this point. */ |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ /* we we have a cached DB image, update it as well */ |
|
+ if (sdb_p->cacheTable) { |
|
+ PR_EnterMonitor(sdb_p->dbMon); |
|
+ sdb_updateCache(sdb_p); |
|
+ PR_ExitMonitor(sdb_p->dbMon); |
|
+ } |
|
+ |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ |
|
+ /* We just finished a transaction. |
|
+ * Free the database, and remove it from the list */ |
|
+ sqlite3_close(sqlDB); |
|
+ |
|
+ return error; |
|
+} |
|
+ |
|
+static const char COMMIT_CMD[] = "COMMIT TRANSACTION;"; |
|
+CK_RV |
|
+sdb_Commit(SDB *sdb) |
|
+{ |
|
+ CK_RV crv; |
|
+ LOCK_SQLITE() |
|
+ crv = sdb_complete(sdb, COMMIT_CMD); |
|
+ UNLOCK_SQLITE() |
|
+ return crv; |
|
+} |
|
+ |
|
+static const char ROLLBACK_CMD[] = "ROLLBACK TRANSACTION;"; |
|
+CK_RV |
|
+sdb_Abort(SDB *sdb) |
|
+{ |
|
+ CK_RV crv; |
|
+ LOCK_SQLITE() |
|
+ crv = sdb_complete(sdb, ROLLBACK_CMD); |
|
+ UNLOCK_SQLITE() |
|
+ return crv; |
|
+} |
|
+ |
|
+static int tableExists(sqlite3 *sqlDB, const char *tableName); |
|
+ |
|
+static const char GET_PW_CMD[] = "SELECT ALL * FROM metaData WHERE id=$ID;"; |
|
+CK_RV |
|
+sdb_GetMetaData(SDB *sdb, const char *id, SECItem *item1, SECItem *item2) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = sdb_p->sqlXactDB; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int found = 0; |
|
+ int retry = 0; |
|
+ |
|
+ LOCK_SQLITE() |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ /* handle 'test' versions of the sqlite db */ |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL); |
|
+ /* Sigh, if we created a new table since we opened the database, |
|
+ * the database handle will not see the new table, we need to close this |
|
+ * database and reopen it. This is safe because we are holding the lock |
|
+ * still. */ |
|
+ if (sqlerr == SQLITE_SCHEMA) { |
|
+ sqlerr = sdb_reopenDBLocal(sdb_p, &sqlDB); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL); |
|
+ } |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC); |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ if (sqlerr == SQLITE_ROW) { |
|
+ const char *blobData; |
|
+ unsigned int len = item1->len; |
|
+ item1->len = sqlite3_column_bytes(stmt, 1); |
|
+ if (item1->len > len) { |
|
+ error = CKR_BUFFER_TOO_SMALL; |
|
+ continue; |
|
+ } |
|
+ blobData = sqlite3_column_blob(stmt, 1); |
|
+ PORT_Memcpy(item1->data, blobData, item1->len); |
|
+ if (item2) { |
|
+ len = item2->len; |
|
+ item2->len = sqlite3_column_bytes(stmt, 2); |
|
+ if (item2->len > len) { |
|
+ error = CKR_BUFFER_TOO_SMALL; |
|
+ continue; |
|
+ } |
|
+ blobData = sqlite3_column_blob(stmt, 2); |
|
+ PORT_Memcpy(item2->data, blobData, item2->len); |
|
+ } |
|
+ found = 1; |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+loser: |
|
+ /* fix up the error if necessary */ |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ if (!found && error == CKR_OK) { |
|
+ error = CKR_OBJECT_HANDLE_INVALID; |
|
+ } |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ |
|
+ return error; |
|
+} |
|
+ |
|
+static const char PW_CREATE_TABLE_CMD[] = |
|
+ "CREATE TABLE metaData (id PRIMARY KEY UNIQUE ON CONFLICT REPLACE, item1, item2);"; |
|
+static const char PW_CREATE_CMD[] = |
|
+ "INSERT INTO metaData (id,item1,item2) VALUES($ID,$ITEM1,$ITEM2);"; |
|
+static const char MD_CREATE_CMD[] = |
|
+ "INSERT INTO metaData (id,item1) VALUES($ID,$ITEM1);"; |
|
+ |
|
+CK_RV |
|
+sdb_PutMetaData(SDB *sdb, const char *id, const SECItem *item1, |
|
+ const SECItem *item2) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = sdb_p->sqlXactDB; |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ int retry = 0; |
|
+ const char *cmd = PW_CREATE_CMD; |
|
+ |
|
+ if ((sdb->sdb_flags & SDB_RDONLY) != 0) { |
|
+ return CKR_TOKEN_WRITE_PROTECTED; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ if (!tableExists(sqlDB, "metaData")) { |
|
+ sqlerr = sqlite3_exec(sqlDB, PW_CREATE_TABLE_CMD, NULL, 0, NULL); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ } |
|
+ if (item2 == NULL) { |
|
+ cmd = MD_CREATE_CMD; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ sqlerr = sqlite3_bind_blob(stmt, 2, item1->data, item1->len, SQLITE_STATIC); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ if (item2) { |
|
+ sqlerr = sqlite3_bind_blob(stmt, 3, item2->data, |
|
+ item2->len, SQLITE_STATIC); |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+loser: |
|
+ /* fix up the error if necessary */ |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ if (stmt) { |
|
+ sqlite3_reset(stmt); |
|
+ sqlite3_finalize(stmt); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ UNLOCK_SQLITE() |
|
+ |
|
+ return error; |
|
+} |
|
+ |
|
+static const char RESET_CMD[] = "DELETE FROM %s;"; |
|
+CK_RV |
|
+sdb_Reset(SDB *sdb) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ char *newStr; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ |
|
+ /* only Key databases can be reset */ |
|
+ if (sdb_p->type != SDB_KEY) { |
|
+ return CKR_OBJECT_HANDLE_INVALID; |
|
+ } |
|
+ |
|
+ LOCK_SQLITE() |
|
+ error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ if (tableExists(sqlDB, sdb_p->table)) { |
|
+ /* delete the contents of the key table */ |
|
+ newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ |
|
+ if (sqlerr != SQLITE_OK) |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ /* delete the password entry table */ |
|
+ sqlerr = sqlite3_exec(sqlDB, "DROP TABLE IF EXISTS metaData;", |
|
+ NULL, 0, NULL); |
|
+ |
|
+loser: |
|
+ /* fix up the error if necessary */ |
|
+ if (error == CKR_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ } |
|
+ |
|
+ if (sqlDB) { |
|
+ sdb_closeDBLocal(sdb_p, sqlDB); |
|
+ } |
|
+ |
|
+ UNLOCK_SQLITE() |
|
+ return error; |
|
+} |
|
+ |
|
+CK_RV |
|
+sdb_Close(SDB *sdb) |
|
+{ |
|
+ SDBPrivate *sdb_p = sdb->private; |
|
+ int sqlerr = SQLITE_OK; |
|
+ sdbDataType type = sdb_p->type; |
|
+ |
|
+ sqlerr = sqlite3_close(sdb_p->sqlReadDB); |
|
+ PORT_Free(sdb_p->sqlDBName); |
|
+ if (sdb_p->cacheTable) { |
|
+ sqlite3_free(sdb_p->cacheTable); |
|
+ } |
|
+ if (sdb_p->dbMon) { |
|
+ PR_DestroyMonitor(sdb_p->dbMon); |
|
+ } |
|
+ free(sdb_p->schemaAttrs); |
|
+ free(sdb_p); |
|
+ free(sdb); |
|
+ return sdb_mapSQLError(type, sqlerr); |
|
+} |
|
+ |
|
+/* |
|
+ * functions to support open |
|
+ */ |
|
+ |
|
+static const char CHECK_TABLE_CMD[] = "SELECT ALL * FROM %s LIMIT 0;"; |
|
+ |
|
+/* return 1 if sqlDB contains table 'tableName */ |
|
+static int |
|
+tableExists(sqlite3 *sqlDB, const char *tableName) |
|
+{ |
|
+ char *cmd = sqlite3_mprintf(CHECK_TABLE_CMD, tableName); |
|
+ int sqlerr = SQLITE_OK; |
|
+ |
|
+ if (cmd == NULL) { |
|
+ return 0; |
|
+ } |
|
+ |
|
+ sqlerr = sqlite3_exec(sqlDB, cmd, NULL, 0, 0); |
|
+ sqlite3_free(cmd); |
|
+ |
|
+ return (sqlerr == SQLITE_OK) ? 1 : 0; |
|
+} |
|
+ |
|
+void |
|
+sdb_SetForkState(PRBool forked) |
|
+{ |
|
+ /* XXXright now this is a no-op. The global fork state in the softokn3 |
|
+ * shared library is already taken care of at the PKCS#11 level. |
|
+ * If and when we add fork state to the sqlite shared library and extern |
|
+ * interface, we will need to set it and reset it from here */ |
|
+} |
|
+ |
|
+static int |
|
+sdb_attributeComparator(const void *a, const void *b) |
|
+{ |
|
+ if (*(CK_ATTRIBUTE_TYPE *)a < *(CK_ATTRIBUTE_TYPE *)b) { |
|
+ return -1; |
|
+ } |
|
+ if (*(CK_ATTRIBUTE_TYPE *)a > *(CK_ATTRIBUTE_TYPE *)b) { |
|
+ return 1; |
|
+ } |
|
+ return 0; |
|
+} |
|
+ |
|
+/* |
|
+ * initialize a single database |
|
+ */ |
|
+static const char INIT_CMD[] = |
|
+ "CREATE TABLE %s (id PRIMARY KEY UNIQUE ON CONFLICT ABORT%s)"; |
|
+ |
|
+CK_RV |
|
+sdb_init(char *dbname, char *table, sdbDataType type, int *inUpdate, |
|
+ int *newInit, int inFlags, PRUint32 accessOps, SDB **pSdb) |
|
+{ |
|
+ int i; |
|
+ char *initStr = NULL; |
|
+ char *newStr; |
|
+ char *queryStr = NULL; |
|
+ int inTransaction = 0; |
|
+ SDB *sdb = NULL; |
|
+ SDBPrivate *sdb_p = NULL; |
|
+ sqlite3 *sqlDB = NULL; |
|
+ int sqlerr = SQLITE_OK; |
|
+ CK_RV error = CKR_OK; |
|
+ char *cacheTable = NULL; |
|
+ PRIntervalTime now = 0; |
|
+ char *env; |
|
+ PRBool enableCache = PR_FALSE; |
|
+ PRBool checkFSType = PR_FALSE; |
|
+ PRBool measureSpeed = PR_FALSE; |
|
+ PRBool create; |
|
+ int flags = inFlags & 0x7; |
|
+ |
|
+ *pSdb = NULL; |
|
+ *inUpdate = 0; |
|
+ |
|
+ /* sqlite3 doesn't have a flag to specify that we want to |
|
+ * open the database read only. If the db doesn't exist, |
|
+ * sqlite3 will always create it. |
|
+ */ |
|
+ LOCK_SQLITE(); |
|
+ create = (_NSSUTIL_Access(dbname, PR_ACCESS_EXISTS) != PR_SUCCESS); |
|
+ if ((flags == SDB_RDONLY) && create) { |
|
+ error = sdb_mapSQLError(type, SQLITE_CANTOPEN); |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sdb_openDB(dbname, &sqlDB, flags); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ /* |
|
+ * SQL created the file, but it doesn't set appropriate modes for |
|
+ * a database. |
|
+ * |
|
+ * NO NSPR call for chmod? :( |
|
+ */ |
|
+ if (create && sdb_chmod(dbname, 0600) != 0) { |
|
+ error = sdb_mapSQLError(type, SQLITE_CANTOPEN); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ if (flags != SDB_RDONLY) { |
|
+ sqlerr = sqlite3_exec(sqlDB, BEGIN_CMD, NULL, 0, NULL); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ inTransaction = 1; |
|
+ } |
|
+ if (!tableExists(sqlDB, table)) { |
|
+ *newInit = 1; |
|
+ if (flags != SDB_CREATE) { |
|
+ error = sdb_mapSQLError(type, SQLITE_CANTOPEN); |
|
+ goto loser; |
|
+ } |
|
+ initStr = sqlite3_mprintf(""); |
|
+ for (i = 0; initStr && i < known_attributes_size; i++) { |
|
+ newStr = sqlite3_mprintf("%s, a%x", initStr, known_attributes[i]); |
|
+ sqlite3_free(initStr); |
|
+ initStr = newStr; |
|
+ } |
|
+ if (initStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ newStr = sqlite3_mprintf(INIT_CMD, table, initStr); |
|
+ sqlite3_free(initStr); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, table); |
|
+ if (newStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); |
|
+ sqlite3_free(newStr); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ } |
|
+ /* |
|
+ * detect the case where we have created the database, but have |
|
+ * not yet updated it. |
|
+ * |
|
+ * We only check the Key database because only the key database has |
|
+ * a metaData table. The metaData table is created when a password |
|
+ * is set, or in the case of update, when a password is supplied. |
|
+ * If no key database exists, then the update would have happened immediately |
|
+ * on noticing that the cert database didn't exist (see newInit set above). |
|
+ */ |
|
+ if (type == SDB_KEY && !tableExists(sqlDB, "metaData")) { |
|
+ *newInit = 1; |
|
+ } |
|
+ |
|
+ /* access to network filesystems are significantly slower than local ones |
|
+ * for database operations. In those cases we need to create a cached copy |
|
+ * of the database in a temporary location on the local disk. SQLITE |
|
+ * already provides a way to create a temporary table and initialize it, |
|
+ * so we use it for the cache (see sdb_buildCache for how it's done).*/ |
|
+ |
|
+ /* |
|
+ * we decide whether or not to use the cache based on the following input. |
|
+ * |
|
+ * NSS_SDB_USE_CACHE environment variable is set to anything other than |
|
+ * "yes" or "no" (for instance, "auto"): NSS will measure the performance |
|
+ * of access to the temp database versus the access to the user's |
|
+ * passed-in database location. If the temp database location is |
|
+ * "significantly" faster we will use the cache. |
|
+ * |
|
+ * NSS_SDB_USE_CACHE environment variable is nonexistent or set to "no": |
|
+ * cache will not be used. |
|
+ * |
|
+ * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will |
|
+ * always be used. |
|
+ * |
|
+ * It is expected that most applications will not need this feature, and |
|
+ * thus it is disabled by default. |
|
+ */ |
|
+ |
|
+ env = PR_GetEnvSecure("NSS_SDB_USE_CACHE"); |
|
+ |
|
+ /* Variables enableCache, checkFSType, measureSpeed are PR_FALSE by default, |
|
+ * which is the expected behavior for NSS_SDB_USE_CACHE="no". |
|
+ * We don't need to check for "no" here. */ |
|
+ if (!env) { |
|
+ /* By default, with no variable set, we avoid expensive measuring for |
|
+ * most FS types. We start with inexpensive FS type checking, and |
|
+ * might perform measuring for some types. */ |
|
+ checkFSType = PR_TRUE; |
|
+ } else if (PORT_Strcasecmp(env, "yes") == 0) { |
|
+ enableCache = PR_TRUE; |
|
+ } else if (PORT_Strcasecmp(env, "no") != 0) { /* not "no" => "auto" */ |
|
+ measureSpeed = PR_TRUE; |
|
+ } |
|
+ |
|
+ if (checkFSType) { |
|
+#if defined(LINUX) && !defined(ANDROID) |
|
+ struct statfs statfs_s; |
|
+ if (statfs(dbname, &statfs_s) == 0) { |
|
+ switch (statfs_s.f_type) { |
|
+ case SMB_SUPER_MAGIC: |
|
+ case 0xff534d42: /* CIFS_MAGIC_NUMBER */ |
|
+ case NFS_SUPER_MAGIC: |
|
+ /* We assume these are slow. */ |
|
+ enableCache = PR_TRUE; |
|
+ break; |
|
+ case CODA_SUPER_MAGIC: |
|
+ case 0x65735546: /* FUSE_SUPER_MAGIC */ |
|
+ case NCP_SUPER_MAGIC: |
|
+ /* It's uncertain if this FS is fast or slow. |
|
+ * It seems reasonable to perform slow measuring for users |
|
+ * with questionable FS speed. */ |
|
+ measureSpeed = PR_TRUE; |
|
+ break; |
|
+ case AFS_SUPER_MAGIC: /* Already implements caching. */ |
|
+ default: |
|
+ break; |
|
+ } |
|
+ } |
|
+#endif |
|
+ } |
|
+ |
|
+ if (measureSpeed) { |
|
+ char *tempDir = NULL; |
|
+ PRUint32 tempOps = 0; |
|
+ /* |
|
+ * Use PR_Access to determine how expensive it |
|
+ * is to check for the existance of a local file compared to the same |
|
+ * check in the temp directory. If the temp directory is faster, cache |
|
+ * the database there. */ |
|
+ tempDir = sdb_getTempDir(sqlDB); |
|
+ if (tempDir) { |
|
+ tempOps = sdb_measureAccess(tempDir); |
|
+ PORT_Free(tempDir); |
|
+ |
|
+ /* There is a cost to continually copying the database. |
|
+ * Account for that cost with the arbitrary factor of 10 */ |
|
+ enableCache = (PRBool)(tempOps > accessOps * 10); |
|
+ } |
|
+ } |
|
+ |
|
+ if (enableCache) { |
|
+ /* try to set the temp store to memory.*/ |
|
+ sqlite3_exec(sqlDB, "PRAGMA temp_store=MEMORY", NULL, 0, NULL); |
|
+ /* Failure to set the temp store to memory is not fatal, |
|
+ * ignore the error */ |
|
+ |
|
+ cacheTable = sqlite3_mprintf("%sCache", table); |
|
+ if (cacheTable == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ /* build the cache table */ |
|
+ error = sdb_buildCache(sqlDB, type, cacheTable, table); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ /* initialize the last cache build time */ |
|
+ now = PR_IntervalNow(); |
|
+ } |
|
+ |
|
+ sdb = (SDB *)malloc(sizeof(SDB)); |
|
+ if (!sdb) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sdb_p = (SDBPrivate *)malloc(sizeof(SDBPrivate)); |
|
+ if (!sdb_p) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ /* Cache the attributes that are held in the table, so we can later check |
|
+ * that queried attributes actually exist. We don't assume the schema |
|
+ * to be exactly |known_attributes|, as it may change over time. */ |
|
+ sdb_p->schemaAttrs = NULL; |
|
+ if (!PORT_Strcmp("nssPublic", table) || |
|
+ !PORT_Strcmp("nssPrivate", table)) { |
|
+ sqlite3_stmt *stmt = NULL; |
|
+ int retry = 0; |
|
+ unsigned int backedAttrs = 0; |
|
+ |
|
+ /* Can't bind parameters to a PRAGMA. */ |
|
+ queryStr = sqlite3_mprintf("PRAGMA table_info(%s);", table); |
|
+ if (queryStr == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_prepare_v2(sqlDB, queryStr, -1, &stmt, NULL); |
|
+ sqlite3_free(queryStr); |
|
+ queryStr = NULL; |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ unsigned int schemaAttrsCapacity = known_attributes_size; |
|
+ sdb_p->schemaAttrs = malloc(schemaAttrsCapacity * sizeof(CK_ATTRIBUTE_TYPE)); |
|
+ if (!sdb_p->schemaAttrs) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ do { |
|
+ sqlerr = sqlite3_step(stmt); |
|
+ if (sqlerr == SQLITE_BUSY) { |
|
+ PR_Sleep(SDB_BUSY_RETRY_TIME); |
|
+ } |
|
+ if (sqlerr == SQLITE_ROW) { |
|
+ if (backedAttrs == schemaAttrsCapacity) { |
|
+ schemaAttrsCapacity += known_attributes_size; |
|
+ sdb_p->schemaAttrs = realloc(sdb_p->schemaAttrs, |
|
+ schemaAttrsCapacity * sizeof(CK_ATTRIBUTE_TYPE)); |
|
+ if (!sdb_p->schemaAttrs) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ } |
|
+ /* Record the ULONG attribute value. */ |
|
+ char *val = (char *)sqlite3_column_text(stmt, 1); |
|
+ if (val && val[0] == 'a') { |
|
+ CK_ATTRIBUTE_TYPE attr = strtoul(&val[1], NULL, 16); |
|
+ sdb_p->schemaAttrs[backedAttrs++] = attr; |
|
+ } |
|
+ } |
|
+ } while (!sdb_done(sqlerr, &retry)); |
|
+ |
|
+ if (sqlerr != SQLITE_DONE) { |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_reset(stmt); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ sqlerr = sqlite3_finalize(stmt); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ goto loser; |
|
+ } |
|
+ |
|
+ sdb_p->numSchemaAttrs = backedAttrs; |
|
+ |
|
+ /* Sort these once so we can shortcut invalid attribute searches. */ |
|
+ qsort(sdb_p->schemaAttrs, sdb_p->numSchemaAttrs, |
|
+ sizeof(CK_ATTRIBUTE_TYPE), sdb_attributeComparator); |
|
+ } |
|
+ |
|
+ /* invariant fields */ |
|
+ sdb_p->sqlDBName = PORT_Strdup(dbname); |
|
+ sdb_p->type = type; |
|
+ sdb_p->table = table; |
|
+ sdb_p->cacheTable = cacheTable; |
|
+ sdb_p->lastUpdateTime = now; |
|
+ /* set the cache delay time. This is how long we will wait before we |
|
+ * decide the existing cache is stale. Currently set to 10 sec */ |
|
+ sdb_p->updateInterval = PR_SecondsToInterval(10); |
|
+ sdb_p->dbMon = PR_NewMonitor(); |
|
+ /* these fields are protected by the lock */ |
|
+ sdb_p->sqlXactDB = NULL; |
|
+ sdb_p->sqlXactThread = NULL; |
|
+ sdb->private = sdb_p; |
|
+ sdb->version = 1; |
|
+ sdb->sdb_flags = inFlags | SDB_HAS_META; |
|
+ sdb->app_private = NULL; |
|
+ sdb->sdb_FindObjectsInit = sdb_FindObjectsInit; |
|
+ sdb->sdb_FindObjects = sdb_FindObjects; |
|
+ sdb->sdb_FindObjectsFinal = sdb_FindObjectsFinal; |
|
+ sdb->sdb_GetAttributeValue = sdb_GetAttributeValue; |
|
+ sdb->sdb_SetAttributeValue = sdb_SetAttributeValue; |
|
+ sdb->sdb_CreateObject = sdb_CreateObject; |
|
+ sdb->sdb_DestroyObject = sdb_DestroyObject; |
|
+ sdb->sdb_GetMetaData = sdb_GetMetaData; |
|
+ sdb->sdb_PutMetaData = sdb_PutMetaData; |
|
+ sdb->sdb_DestroyMetaData = sdb_DestroyMetaData; |
|
+ sdb->sdb_Begin = sdb_Begin; |
|
+ sdb->sdb_Commit = sdb_Commit; |
|
+ sdb->sdb_Abort = sdb_Abort; |
|
+ sdb->sdb_Reset = sdb_Reset; |
|
+ sdb->sdb_Close = sdb_Close; |
|
+ sdb->sdb_SetForkState = sdb_SetForkState; |
|
+ sdb->sdb_GetNewObjectID = sdb_GetNewObjectID; |
|
+ |
|
+ if (inTransaction) { |
|
+ sqlerr = sqlite3_exec(sqlDB, COMMIT_CMD, NULL, 0, NULL); |
|
+ if (sqlerr != SQLITE_OK) { |
|
+ error = sdb_mapSQLError(sdb_p->type, sqlerr); |
|
+ goto loser; |
|
+ } |
|
+ inTransaction = 0; |
|
+ } |
|
+ |
|
+ sdb_p->sqlReadDB = sqlDB; |
|
+ |
|
+ *pSdb = sdb; |
|
+ UNLOCK_SQLITE(); |
|
+ return CKR_OK; |
|
+ |
|
+loser: |
|
+ /* lots of stuff to do */ |
|
+ if (inTransaction) { |
|
+ sqlite3_exec(sqlDB, ROLLBACK_CMD, NULL, 0, NULL); |
|
+ } |
|
+ if (sdb) { |
|
+ free(sdb); |
|
+ } |
|
+ if (sdb_p) { |
|
+ if (sdb_p->schemaAttrs) { |
|
+ free(sdb_p->schemaAttrs); |
|
+ } |
|
+ free(sdb_p); |
|
+ } |
|
+ if (sqlDB) { |
|
+ sqlite3_close(sqlDB); |
|
+ } |
|
+ UNLOCK_SQLITE(); |
|
+ return error; |
|
+} |
|
+ |
|
+/* sdbopen */ |
|
+CK_RV |
|
+s_open(const char *directory, const char *certPrefix, const char *keyPrefix, |
|
+ int cert_version, int key_version, int flags, |
|
+ SDB **certdb, SDB **keydb, int *newInit) |
|
+{ |
|
+ char *cert = sdb_BuildFileName(directory, certPrefix, |
|
+ "cert", cert_version); |
|
+ char *key = sdb_BuildFileName(directory, keyPrefix, |
|
+ "key", key_version); |
|
+ CK_RV error = CKR_OK; |
|
+ int inUpdate; |
|
+ PRUint32 accessOps; |
|
+ |
|
+ if (certdb) |
|
+ *certdb = NULL; |
|
+ if (keydb) |
|
+ *keydb = NULL; |
|
+ *newInit = 0; |
|
+ |
|
+#ifdef SQLITE_UNSAFE_THREADS |
|
+ if (sqlite_lock == NULL) { |
|
+ sqlite_lock = PR_NewLock(); |
|
+ if (sqlite_lock == NULL) { |
|
+ error = CKR_HOST_MEMORY; |
|
+ goto loser; |
|
+ } |
|
+ } |
|
+#endif |
|
+ |
|
+ /* how long does it take to test for a non-existant file in our working |
|
+ * directory? Allows us to test if we may be on a network file system */ |
|
+ accessOps = 1; |
|
+ { |
|
+ char *env; |
|
+ env = PR_GetEnvSecure("NSS_SDB_USE_CACHE"); |
|
+ /* If the environment variable is undefined or set to yes or no, |
|
+ * sdb_init() will ignore the value of accessOps, and we can skip the |
|
+ * measuring.*/ |
|
+ if (env && PORT_Strcasecmp(env, "no") != 0 && |
|
+ PORT_Strcasecmp(env, "yes") != 0) { |
|
+ accessOps = sdb_measureAccess(directory); |
|
+ } |
|
+ } |
|
+ |
|
+ /* |
|
+ * open the cert data base |
|
+ */ |
|
+ if (certdb) { |
|
+ /* initialize Certificate database */ |
|
+ error = sdb_init(cert, "nssPublic", SDB_CERT, &inUpdate, |
|
+ newInit, flags, accessOps, certdb); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ } |
|
+ |
|
+ /* |
|
+ * open the key data base: |
|
+ * NOTE:if we want to implement a single database, we open |
|
+ * the same database file as the certificate here. |
|
+ * |
|
+ * cert an key db's have different tables, so they will not |
|
+ * conflict. |
|
+ */ |
|
+ if (keydb) { |
|
+ /* initialize the Key database */ |
|
+ error = sdb_init(key, "nssPrivate", SDB_KEY, &inUpdate, |
|
+ newInit, flags, accessOps, keydb); |
|
+ if (error != CKR_OK) { |
|
+ goto loser; |
|
+ } |
|
+ } |
|
+ |
|
+loser: |
|
+ if (cert) { |
|
+ sqlite3_free(cert); |
|
+ } |
|
+ if (key) { |
|
+ sqlite3_free(key); |
|
+ } |
|
+ |
|
+ if (error != CKR_OK) { |
|
+ /* currently redundant, but could be necessary if more code is added |
|
+ * just before loser */ |
|
+ if (keydb && *keydb) { |
|
+ sdb_Close(*keydb); |
|
+ } |
|
+ if (certdb && *certdb) { |
|
+ sdb_Close(*certdb); |
|
+ } |
|
+ } |
|
+ |
|
+ return error; |
|
+} |
|
+ |
|
+CK_RV |
|
+s_shutdown() |
|
+{ |
|
+#ifdef SQLITE_UNSAFE_THREADS |
|
+ if (sqlite_lock) { |
|
+ PR_DestroyLock(sqlite_lock); |
|
+ sqlite_lock = NULL; |
|
+ } |
|
+#endif |
|
+ return CKR_OK; |
|
+} |
|
diff --git a/cmd/manifest.mn b/cmd/manifest.mn |
|
--- a/cmd/manifest.mn |
|
+++ b/cmd/manifest.mn |
|
@@ -36,16 +36,17 @@ NSS_SRCDIRS = \ |
|
addbuiltin \ |
|
atob \ |
|
btoa \ |
|
certutil \ |
|
chktest \ |
|
crlutil \ |
|
crmftest \ |
|
dbtest \ |
|
+ dbtool \ |
|
derdump \ |
|
digest \ |
|
httpserv \ |
|
listsuites \ |
|
makepqg \ |
|
multinit \ |
|
nss-policy-check \ |
|
ocspclnt \
|
|
|