services/crypto/modules/WeaveCrypto.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/crypto/modules/WeaveCrypto.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,760 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +this.EXPORTED_SYMBOLS = ["WeaveCrypto"];
     1.9 +
    1.10 +const Cc = Components.classes;
    1.11 +const Ci = Components.interfaces;
    1.12 +const Cr = Components.results;
    1.13 +
    1.14 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.16 +Components.utils.import("resource://gre/modules/ctypes.jsm");
    1.17 +
    1.18 +/**
    1.19 + * Shortcuts for some algorithm SEC OIDs.  Full list available here:
    1.20 + * http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h
    1.21 + */
    1.22 +const DES_EDE3_CBC = 156;
    1.23 +const AES_128_CBC  = 184;
    1.24 +const AES_192_CBC  = 186;
    1.25 +const AES_256_CBC  = 188;
    1.26 +
    1.27 +const ALGORITHM                 = AES_256_CBC;
    1.28 +const KEYSIZE_AES_256           = 32;
    1.29 +const KEY_DERIVATION_ITERATIONS = 4096;   // PKCS#5 recommends at least 1000.
    1.30 +const INITIAL_BUFFER_SIZE       = 1024;
    1.31 +
    1.32 +this.WeaveCrypto = function WeaveCrypto() {
    1.33 +    this.init();
    1.34 +}
    1.35 +
    1.36 +WeaveCrypto.prototype = {
    1.37 +    prefBranch : null,
    1.38 +    debug      : true,  // services.sync.log.cryptoDebug
    1.39 +    nss        : null,
    1.40 +    nss_t      : null,
    1.41 +
    1.42 +    observer : {
    1.43 +        _self : null,
    1.44 +
    1.45 +        QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
    1.46 +                                                Ci.nsISupportsWeakReference]),
    1.47 +
    1.48 +        observe : function (subject, topic, data) {
    1.49 +            let self = this._self;
    1.50 +            self.log("Observed " + topic + " topic.");
    1.51 +            if (topic == "nsPref:changed") {
    1.52 +                self.debug = self.prefBranch.getBoolPref("cryptoDebug");
    1.53 +            }
    1.54 +        }
    1.55 +    },
    1.56 +
    1.57 +    init : function() {
    1.58 +        try {
    1.59 +            // Preferences. Add observer so we get notified of changes.
    1.60 +            this.prefBranch = Services.prefs.getBranch("services.sync.log.");
    1.61 +            this.prefBranch.addObserver("cryptoDebug", this.observer, false);
    1.62 +            this.observer._self = this;
    1.63 +            try {
    1.64 +              this.debug = this.prefBranch.getBoolPref("cryptoDebug");
    1.65 +            } catch (x) {
    1.66 +              this.debug = false;
    1.67 +            }
    1.68 +
    1.69 +            this.initNSS();
    1.70 +            this.initAlgorithmSettings();   // Depends on NSS.
    1.71 +            this.initIVSECItem();
    1.72 +            this.initSharedInts();
    1.73 +            this.initBuffers(INITIAL_BUFFER_SIZE);
    1.74 +        } catch (e) {
    1.75 +            this.log("init failed: " + e);
    1.76 +            throw e;
    1.77 +        }
    1.78 +    },
    1.79 +
    1.80 +    // Avoid allocating new temporary ints on every run of _commonCrypt.
    1.81 +    _commonCryptSignedOutputSize:       null,
    1.82 +    _commonCryptSignedOutputSizeAddr:   null,
    1.83 +    _commonCryptUnsignedOutputSize:     null,
    1.84 +    _commonCryptUnsignedOutputSizeAddr: null,
    1.85 +
    1.86 +    initSharedInts: function initSharedInts() {
    1.87 +        let signed   = new ctypes.int();
    1.88 +        let unsigned = new ctypes.unsigned_int();
    1.89 +        this._commonCryptSignedOutputSize       = signed;
    1.90 +        this._commonCryptUnsignedOutputSize     = unsigned;
    1.91 +        this._commonCryptSignedOutputSizeAddr   = signed.address();
    1.92 +        this._commonCryptUnsignedOutputSizeAddr = unsigned.address();
    1.93 +    },
    1.94 +
    1.95 +    /**
    1.96 +     * Set a bunch of NSS values once, at init-time. These are:
    1.97 +     *   - .blockSize
    1.98 +     *   - .mechanism
    1.99 +     *   - .keygenMechanism
   1.100 +     *   - .padMechanism
   1.101 +     *   - .keySize
   1.102 +     *
   1.103 +     * See also the constant ALGORITHM.
   1.104 +     */
   1.105 +    initAlgorithmSettings: function() {
   1.106 +        this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM);
   1.107 +        this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null);
   1.108 +        this.ivLength  = this.nss.PK11_GetIVLength(this.mechanism);
   1.109 +        this.keySize   = KEYSIZE_AES_256;
   1.110 +        this.keygenMechanism = this.nss.CKM_AES_KEY_GEN;  // Always the same!
   1.111 +
   1.112 +        // Determine which (padded) PKCS#11 mechanism to use.
   1.113 +        // E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
   1.114 +        this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism);
   1.115 +        if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM)
   1.116 +            throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
   1.117 +    },
   1.118 +
   1.119 +    log : function (message) {
   1.120 +        if (!this.debug)
   1.121 +            return;
   1.122 +        dump("WeaveCrypto: " + message + "\n");
   1.123 +        Services.console.logStringMessage("WeaveCrypto: " + message);
   1.124 +    },
   1.125 +
   1.126 +    initNSS : function() {
   1.127 +        // We use NSS for the crypto ops, which needs to be initialized before
   1.128 +        // use. By convention, PSM is required to be the module that
   1.129 +        // initializes NSS. So, make sure PSM is initialized in order to
   1.130 +        // implicitly initialize NSS.
   1.131 +        Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
   1.132 +
   1.133 +        // Open the NSS library.
   1.134 +        let path = ctypes.libraryName("nss3");
   1.135 +
   1.136 +        // XXX really want to be able to pass specific dlopen flags here.
   1.137 +        var nsslib;
   1.138 +        try {
   1.139 +            this.log("Trying NSS library without path");
   1.140 +            nsslib = ctypes.open(path);
   1.141 +        } catch(e) {
   1.142 +            // In case opening the library without a full path fails,
   1.143 +            // try again with a full path.
   1.144 +            let file = Services.dirsvc.get("GreD", Ci.nsILocalFile);
   1.145 +            file.append(path);
   1.146 +            this.log("Trying again with path " + file.path);
   1.147 +            nsslib = ctypes.open(file.path);
   1.148 +        }
   1.149 +
   1.150 +        this.log("Initializing NSS types and function declarations...");
   1.151 +
   1.152 +        this.nss = {};
   1.153 +        this.nss_t = {};
   1.154 +
   1.155 +        // nsprpub/pr/include/prtypes.h#435
   1.156 +        // typedef PRIntn PRBool; --> int
   1.157 +        this.nss_t.PRBool = ctypes.int;
   1.158 +        // security/nss/lib/util/seccomon.h#91
   1.159 +        // typedef enum
   1.160 +        this.nss_t.SECStatus = ctypes.int;
   1.161 +        // security/nss/lib/softoken/secmodt.h#59
   1.162 +        // typedef struct PK11SlotInfoStr PK11SlotInfo; (defined in secmodti.h) 
   1.163 +        this.nss_t.PK11SlotInfo = ctypes.void_t;
   1.164 +        // security/nss/lib/util/pkcs11t.h
   1.165 +        this.nss_t.CK_MECHANISM_TYPE = ctypes.unsigned_long;
   1.166 +        this.nss_t.CK_ATTRIBUTE_TYPE = ctypes.unsigned_long;
   1.167 +        this.nss_t.CK_KEY_TYPE       = ctypes.unsigned_long;
   1.168 +        this.nss_t.CK_OBJECT_HANDLE  = ctypes.unsigned_long;
   1.169 +        // security/nss/lib/softoken/secmodt.h#359
   1.170 +        // typedef enum PK11Origin
   1.171 +        this.nss_t.PK11Origin = ctypes.int;
   1.172 +        // PK11Origin enum values...
   1.173 +        this.nss.PK11_OriginUnwrap = 4;
   1.174 +        // security/nss/lib/softoken/secmodt.h#61
   1.175 +        // typedef struct PK11SymKeyStr PK11SymKey; (defined in secmodti.h)
   1.176 +        this.nss_t.PK11SymKey = ctypes.void_t;
   1.177 +        // security/nss/lib/util/secoidt.h#454
   1.178 +        // typedef enum
   1.179 +        this.nss_t.SECOidTag = ctypes.int;
   1.180 +        // security/nss/lib/util/seccomon.h#64
   1.181 +        // typedef enum
   1.182 +        this.nss_t.SECItemType = ctypes.int;
   1.183 +        // SECItemType enum values...
   1.184 +        this.nss.SIBUFFER = 0;
   1.185 +        // security/nss/lib/softoken/secmodt.h#62 (defined in secmodti.h)
   1.186 +        // typedef struct PK11ContextStr PK11Context;
   1.187 +        this.nss_t.PK11Context = ctypes.void_t;
   1.188 +        // Needed for SECKEYPrivateKey struct def'n, but I don't think we need to actually access it.
   1.189 +        this.nss_t.PLArenaPool = ctypes.void_t;
   1.190 +        // security/nss/lib/cryptohi/keythi.h#45
   1.191 +        // typedef enum
   1.192 +        this.nss_t.KeyType = ctypes.int;
   1.193 +        // security/nss/lib/softoken/secmodt.h#201
   1.194 +        // typedef PRUint32 PK11AttrFlags;
   1.195 +        this.nss_t.PK11AttrFlags = ctypes.unsigned_int;
   1.196 +        // security/nss/lib/util/seccomon.h#83
   1.197 +        // typedef struct SECItemStr SECItem; --> SECItemStr defined right below it
   1.198 +        this.nss_t.SECItem = ctypes.StructType(
   1.199 +            "SECItem", [{ type: this.nss_t.SECItemType },
   1.200 +                        { data: ctypes.unsigned_char.ptr },
   1.201 +                        { len : ctypes.int }]);
   1.202 +        // security/nss/lib/util/secoidt.h#52
   1.203 +        // typedef struct SECAlgorithmIDStr --> def'n right below it
   1.204 +        this.nss_t.SECAlgorithmID = ctypes.StructType(
   1.205 +            "SECAlgorithmID", [{ algorithm:  this.nss_t.SECItem },
   1.206 +                               { parameters: this.nss_t.SECItem }]);
   1.207 +
   1.208 +
   1.209 +        // security/nss/lib/util/pkcs11t.h
   1.210 +        this.nss.CKK_RSA = 0x0;
   1.211 +        this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN = 0x0000;
   1.212 +        this.nss.CKM_AES_KEY_GEN           = 0x1080; 
   1.213 +        this.nss.CKA_ENCRYPT = 0x104;
   1.214 +        this.nss.CKA_DECRYPT = 0x105;
   1.215 +
   1.216 +        // security/nss/lib/softoken/secmodt.h
   1.217 +        this.nss.PK11_ATTR_SESSION   = 0x02;
   1.218 +        this.nss.PK11_ATTR_PUBLIC    = 0x08;
   1.219 +        this.nss.PK11_ATTR_SENSITIVE = 0x40;
   1.220 +
   1.221 +        // security/nss/lib/util/secoidt.h
   1.222 +        this.nss.SEC_OID_PKCS5_PBKDF2         = 291;
   1.223 +        this.nss.SEC_OID_HMAC_SHA1            = 294;
   1.224 +        this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16;
   1.225 +
   1.226 +
   1.227 +        // security/nss/lib/pk11wrap/pk11pub.h#286
   1.228 +        // SECStatus PK11_GenerateRandom(unsigned char *data,int len);
   1.229 +        this.nss.PK11_GenerateRandom = nsslib.declare("PK11_GenerateRandom",
   1.230 +                                                      ctypes.default_abi, this.nss_t.SECStatus,
   1.231 +                                                      ctypes.unsigned_char.ptr, ctypes.int);
   1.232 +        // security/nss/lib/pk11wrap/pk11pub.h#74
   1.233 +        // PK11SlotInfo *PK11_GetInternalSlot(void);
   1.234 +        this.nss.PK11_GetInternalSlot = nsslib.declare("PK11_GetInternalSlot",
   1.235 +                                                       ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
   1.236 +        // security/nss/lib/pk11wrap/pk11pub.h#73
   1.237 +        // PK11SlotInfo *PK11_GetInternalKeySlot(void);
   1.238 +        this.nss.PK11_GetInternalKeySlot = nsslib.declare("PK11_GetInternalKeySlot",
   1.239 +                                                          ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
   1.240 +        // security/nss/lib/pk11wrap/pk11pub.h#328
   1.241 +        // PK11SymKey *PK11_KeyGen(PK11SlotInfo *slot,CK_MECHANISM_TYPE type, SECItem *param, int keySize,void *wincx);
   1.242 +        this.nss.PK11_KeyGen = nsslib.declare("PK11_KeyGen",
   1.243 +                                              ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   1.244 +                                              this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE,
   1.245 +                                              this.nss_t.SECItem.ptr, ctypes.int, ctypes.voidptr_t);
   1.246 +        // security/nss/lib/pk11wrap/pk11pub.h#477
   1.247 +        // SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey);
   1.248 +        this.nss.PK11_ExtractKeyValue = nsslib.declare("PK11_ExtractKeyValue",
   1.249 +                                                       ctypes.default_abi, this.nss_t.SECStatus,
   1.250 +                                                       this.nss_t.PK11SymKey.ptr);
   1.251 +        // security/nss/lib/pk11wrap/pk11pub.h#478
   1.252 +        // SECItem * PK11_GetKeyData(PK11SymKey *symKey);
   1.253 +        this.nss.PK11_GetKeyData = nsslib.declare("PK11_GetKeyData",
   1.254 +                                                  ctypes.default_abi, this.nss_t.SECItem.ptr,
   1.255 +                                                  this.nss_t.PK11SymKey.ptr);
   1.256 +        // security/nss/lib/pk11wrap/pk11pub.h#278
   1.257 +        // CK_MECHANISM_TYPE PK11_AlgtagToMechanism(SECOidTag algTag);
   1.258 +        this.nss.PK11_AlgtagToMechanism = nsslib.declare("PK11_AlgtagToMechanism",
   1.259 +                                                         ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
   1.260 +                                                         this.nss_t.SECOidTag);
   1.261 +        // security/nss/lib/pk11wrap/pk11pub.h#270
   1.262 +        // int PK11_GetIVLength(CK_MECHANISM_TYPE type);
   1.263 +        this.nss.PK11_GetIVLength = nsslib.declare("PK11_GetIVLength",
   1.264 +                                                   ctypes.default_abi, ctypes.int,
   1.265 +                                                   this.nss_t.CK_MECHANISM_TYPE);
   1.266 +        // security/nss/lib/pk11wrap/pk11pub.h#269
   1.267 +        // int PK11_GetBlockSize(CK_MECHANISM_TYPE type,SECItem *params);
   1.268 +        this.nss.PK11_GetBlockSize = nsslib.declare("PK11_GetBlockSize",
   1.269 +                                                    ctypes.default_abi, ctypes.int,
   1.270 +                                                    this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
   1.271 +        // security/nss/lib/pk11wrap/pk11pub.h#293
   1.272 +        // CK_MECHANISM_TYPE PK11_GetPadMechanism(CK_MECHANISM_TYPE);
   1.273 +        this.nss.PK11_GetPadMechanism = nsslib.declare("PK11_GetPadMechanism",
   1.274 +                                                       ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
   1.275 +                                                       this.nss_t.CK_MECHANISM_TYPE);
   1.276 +        // security/nss/lib/pk11wrap/pk11pub.h#271
   1.277 +        // SECItem *PK11_ParamFromIV(CK_MECHANISM_TYPE type,SECItem *iv);
   1.278 +        this.nss.PK11_ParamFromIV = nsslib.declare("PK11_ParamFromIV",
   1.279 +                                                   ctypes.default_abi, this.nss_t.SECItem.ptr,
   1.280 +                                                   this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
   1.281 +        // security/nss/lib/pk11wrap/pk11pub.h#301
   1.282 +        // PK11SymKey *PK11_ImportSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PK11Origin origin,
   1.283 +        //                               CK_ATTRIBUTE_TYPE operation, SECItem *key, void *wincx);
   1.284 +        this.nss.PK11_ImportSymKey = nsslib.declare("PK11_ImportSymKey",
   1.285 +                                                    ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   1.286 +                                                    this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, this.nss_t.PK11Origin,
   1.287 +                                                    this.nss_t.CK_ATTRIBUTE_TYPE, this.nss_t.SECItem.ptr, ctypes.voidptr_t);
   1.288 +        // security/nss/lib/pk11wrap/pk11pub.h#672
   1.289 +        // PK11Context *PK11_CreateContextBySymKey(CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation,
   1.290 +        //                                         PK11SymKey *symKey, SECItem *param);
   1.291 +        this.nss.PK11_CreateContextBySymKey = nsslib.declare("PK11_CreateContextBySymKey",
   1.292 +                                                             ctypes.default_abi, this.nss_t.PK11Context.ptr,
   1.293 +                                                             this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE,
   1.294 +                                                             this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr);
   1.295 +        // security/nss/lib/pk11wrap/pk11pub.h#685
   1.296 +        // SECStatus PK11_CipherOp(PK11Context *context, unsigned char *out
   1.297 +        //                         int *outlen, int maxout, unsigned char *in, int inlen);
   1.298 +        this.nss.PK11_CipherOp = nsslib.declare("PK11_CipherOp",
   1.299 +                                                ctypes.default_abi, this.nss_t.SECStatus,
   1.300 +                                                this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
   1.301 +                                                ctypes.int.ptr, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int);
   1.302 +        // security/nss/lib/pk11wrap/pk11pub.h#688
   1.303 +        // SECStatus PK11_DigestFinal(PK11Context *context, unsigned char *data,
   1.304 +        //                            unsigned int *outLen, unsigned int length);
   1.305 +        this.nss.PK11_DigestFinal = nsslib.declare("PK11_DigestFinal",
   1.306 +                                                   ctypes.default_abi, this.nss_t.SECStatus,
   1.307 +                                                   this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
   1.308 +                                                   ctypes.unsigned_int.ptr, ctypes.unsigned_int);
   1.309 +        // security/nss/lib/pk11wrap/pk11pub.h#731
   1.310 +        // SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
   1.311 +        //                                              SECOidTag prfAlgTag, int keyLength, int iteration,
   1.312 +        //                                              SECItem *salt);
   1.313 +        this.nss.PK11_CreatePBEV2AlgorithmID = nsslib.declare("PK11_CreatePBEV2AlgorithmID",
   1.314 +                                                              ctypes.default_abi, this.nss_t.SECAlgorithmID.ptr,
   1.315 +                                                              this.nss_t.SECOidTag, this.nss_t.SECOidTag, this.nss_t.SECOidTag, 
   1.316 +                                                              ctypes.int, ctypes.int, this.nss_t.SECItem.ptr);
   1.317 +        // security/nss/lib/pk11wrap/pk11pub.h#736
   1.318 +        // PK11SymKey * PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid,  SECItem *pwitem, PRBool faulty3DES, void *wincx);
   1.319 +        this.nss.PK11_PBEKeyGen = nsslib.declare("PK11_PBEKeyGen",
   1.320 +                                                 ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   1.321 +                                                 this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr,
   1.322 +                                                 this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t);
   1.323 +        // security/nss/lib/pk11wrap/pk11pub.h#675
   1.324 +        // void PK11_DestroyContext(PK11Context *context, PRBool freeit);
   1.325 +        this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext",
   1.326 +                                                       ctypes.default_abi, ctypes.void_t,
   1.327 +                                                       this.nss_t.PK11Context.ptr, this.nss_t.PRBool);
   1.328 +        // security/nss/lib/pk11wrap/pk11pub.h#299
   1.329 +        // void PK11_FreeSymKey(PK11SymKey *key);
   1.330 +        this.nss.PK11_FreeSymKey = nsslib.declare("PK11_FreeSymKey",
   1.331 +                                                  ctypes.default_abi, ctypes.void_t,
   1.332 +                                                  this.nss_t.PK11SymKey.ptr);
   1.333 +        // security/nss/lib/pk11wrap/pk11pub.h#70
   1.334 +        // void PK11_FreeSlot(PK11SlotInfo *slot);
   1.335 +        this.nss.PK11_FreeSlot = nsslib.declare("PK11_FreeSlot",
   1.336 +                                                ctypes.default_abi, ctypes.void_t,
   1.337 +                                                this.nss_t.PK11SlotInfo.ptr);
   1.338 +        // security/nss/lib/util/secitem.h#49
   1.339 +        // extern SECItem *SECITEM_AllocItem(PRArenaPool *arena, SECItem *item, unsigned int len);
   1.340 +        this.nss.SECITEM_AllocItem = nsslib.declare("SECITEM_AllocItem",
   1.341 +                                                    ctypes.default_abi, this.nss_t.SECItem.ptr,
   1.342 +                                                    this.nss_t.PLArenaPool.ptr,     // Not used.
   1.343 +                                                    this.nss_t.SECItem.ptr, ctypes.unsigned_int);
   1.344 +        // security/nss/lib/util/secitem.h#274
   1.345 +        // extern void SECITEM_ZfreeItem(SECItem *zap, PRBool freeit);
   1.346 +        this.nss.SECITEM_ZfreeItem = nsslib.declare("SECITEM_ZfreeItem",
   1.347 +                                                    ctypes.default_abi, ctypes.void_t,
   1.348 +                                                    this.nss_t.SECItem.ptr, this.nss_t.PRBool);
   1.349 +        // security/nss/lib/util/secitem.h#114
   1.350 +        // extern void SECITEM_FreeItem(SECItem *zap, PRBool freeit);
   1.351 +        this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem",
   1.352 +                                                   ctypes.default_abi, ctypes.void_t,
   1.353 +                                                   this.nss_t.SECItem.ptr, this.nss_t.PRBool);
   1.354 +        // security/nss/lib/util/secoid.h#103
   1.355 +        // extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit);
   1.356 +        this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID",
   1.357 +                                                            ctypes.default_abi, ctypes.void_t,
   1.358 +                                                            this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
   1.359 +    },
   1.360 +
   1.361 +
   1.362 +    _sharedInputBuffer:      null,
   1.363 +    _sharedInputBufferInts:  null,
   1.364 +    _sharedInputBufferSize:  0,
   1.365 +    _sharedOutputBuffer:     null,
   1.366 +    _sharedOutputBufferSize: 0,
   1.367 +    _randomByteBuffer:       null,
   1.368 +    _randomByteBufferAddr:   null,
   1.369 +    _randomByteBufferSize:   0,
   1.370 +
   1.371 +    _getInputBuffer: function _getInputBuffer(size) {
   1.372 +      if (size > this._sharedInputBufferSize) {
   1.373 +        let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   1.374 +        this._sharedInputBuffer     = b;
   1.375 +        this._sharedInputBufferInts = ctypes.cast(b, ctypes.uint8_t.array(size));
   1.376 +        this._sharedInputBufferSize = size;
   1.377 +      }
   1.378 +      return this._sharedInputBuffer;
   1.379 +    },
   1.380 +
   1.381 +    _getOutputBuffer: function _getOutputBuffer(size) {
   1.382 +      if (size > this._sharedOutputBufferSize) {
   1.383 +        let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   1.384 +        this._sharedOutputBuffer     = b;
   1.385 +        this._sharedOutputBufferSize = size;
   1.386 +      }
   1.387 +      return this._sharedOutputBuffer;
   1.388 +    },
   1.389 +
   1.390 +    _getRandomByteBuffer: function _getRandomByteBuffer(size) {
   1.391 +        if (size > this._randomByteBufferSize) {
   1.392 +          let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   1.393 +          this._randomByteBuffer     = b;
   1.394 +          this._randomByteBufferAddr = b.address();
   1.395 +          this._randomByteBufferSize = size;
   1.396 +        }
   1.397 +        return this._randomByteBuffer;
   1.398 +    },
   1.399 +
   1.400 +    initBuffers: function initBuffers(initialSize) {
   1.401 +        this._getInputBuffer(initialSize);
   1.402 +        this._getOutputBuffer(initialSize);
   1.403 +
   1.404 +        this._getRandomByteBuffer(this.ivLength);
   1.405 +    },
   1.406 +
   1.407 +    encrypt : function(clearTextUCS2, symmetricKey, iv) {
   1.408 +        this.log("encrypt() called");
   1.409 +
   1.410 +        // js-ctypes autoconverts to a UTF8 buffer, but also includes a null
   1.411 +        // at the end which we don't want. Decrement length to skip it.
   1.412 +        let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2);
   1.413 +        let inputBufferSize = inputBuffer.length - 1;
   1.414 +
   1.415 +        // When using CBC padding, the output size is the input size rounded
   1.416 +        // up to the nearest block. If the input size is exactly on a block
   1.417 +        // boundary, the output is 1 extra block long.
   1.418 +        let outputBufferSize = inputBufferSize + this.blockSize;
   1.419 +        let outputBuffer = this._getOutputBuffer(outputBufferSize);
   1.420 +
   1.421 +        outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize,
   1.422 +                                         outputBuffer, outputBufferSize,
   1.423 +                                         symmetricKey, iv, this.nss.CKA_ENCRYPT);
   1.424 +
   1.425 +        return this.encodeBase64(outputBuffer.address(), outputBuffer.length);
   1.426 +    },
   1.427 +
   1.428 +
   1.429 +    decrypt : function(cipherText, symmetricKey, iv) {
   1.430 +        this.log("decrypt() called");
   1.431 +
   1.432 +        let inputUCS2 = "";
   1.433 +        if (cipherText.length)
   1.434 +            inputUCS2 = atob(cipherText);
   1.435 +
   1.436 +        // We can't have js-ctypes create the buffer directly from the string
   1.437 +        // (as in encrypt()), because we do _not_ want it to do UTF8
   1.438 +        // conversion... We've got random binary data in the input's low byte.
   1.439 +        //
   1.440 +        // Compress a JS string (2-byte chars) into a normal C string (1-byte chars).
   1.441 +        let len   = inputUCS2.length;
   1.442 +        let input = this._getInputBuffer(len);
   1.443 +        this.byteCompressInts(inputUCS2, this._sharedInputBufferInts, len);
   1.444 +
   1.445 +        let outputBuffer = this._commonCrypt(input, len,
   1.446 +                                             this._getOutputBuffer(len), len,
   1.447 +                                             symmetricKey, iv, this.nss.CKA_DECRYPT);
   1.448 +
   1.449 +        // outputBuffer contains UTF-8 data, let js-ctypes autoconvert that to a JS string.
   1.450 +        // XXX Bug 573842: wrap the string from ctypes to get a new string, so
   1.451 +        // we don't hit bug 573841.
   1.452 +        return "" + outputBuffer.readString() + "";
   1.453 +    },
   1.454 +
   1.455 +    _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) {
   1.456 +        this.log("_commonCrypt() called");
   1.457 +        iv = atob(iv);
   1.458 +
   1.459 +        // We never want an IV longer than the block size, which is 16 bytes
   1.460 +        // for AES. Neither do we want one smaller; throw in that case.
   1.461 +        if (iv.length < this.blockSize)
   1.462 +            throw "IV too short; must be " + this.blockSize + " bytes.";
   1.463 +        if (iv.length > this.blockSize)
   1.464 +            iv = iv.slice(0, this.blockSize);
   1.465 +
   1.466 +        // We use a single IV SECItem for the sake of efficiency. Fill it here.
   1.467 +        this.byteCompressInts(iv, this._ivSECItemContents, iv.length);
   1.468 +
   1.469 +        let ctx, symKey, ivParam;
   1.470 +        try {
   1.471 +            ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem);
   1.472 +            if (ivParam.isNull())
   1.473 +                throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE);
   1.474 +
   1.475 +            symKey = this.importSymKey(symmetricKey, operation);
   1.476 +            ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam);
   1.477 +            if (ctx.isNull())
   1.478 +                throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
   1.479 +
   1.480 +            let maxOutputSize = outputLength;
   1.481 +            if (this.nss.PK11_CipherOp(ctx, output, this._commonCryptSignedOutputSize.address(), maxOutputSize, input, inputLength))
   1.482 +                throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE);
   1.483 +
   1.484 +            let actualOutputSize = this._commonCryptSignedOutputSize.value;
   1.485 +            let finalOutput = output.addressOfElement(actualOutputSize);
   1.486 +            maxOutputSize -= actualOutputSize;
   1.487 +
   1.488 +            // PK11_DigestFinal sure sounds like the last step for *hashing*, but it
   1.489 +            // just seems to be an odd name -- NSS uses this to finish the current
   1.490 +            // cipher operation. You'd think it would be called PK11_CipherOpFinal...
   1.491 +            if (this.nss.PK11_DigestFinal(ctx, finalOutput, this._commonCryptUnsignedOutputSizeAddr, maxOutputSize))
   1.492 +                throw Components.Exception("cipher finalize failed", Cr.NS_ERROR_FAILURE);
   1.493 +
   1.494 +            actualOutputSize += this._commonCryptUnsignedOutputSize.value;
   1.495 +            let newOutput = ctypes.cast(output, ctypes.unsigned_char.array(actualOutputSize));
   1.496 +            return newOutput;
   1.497 +        } catch (e) {
   1.498 +            this.log("_commonCrypt: failed: " + e);
   1.499 +            throw e;
   1.500 +        } finally {
   1.501 +            if (ctx && !ctx.isNull())
   1.502 +                this.nss.PK11_DestroyContext(ctx, true);
   1.503 +            if (ivParam && !ivParam.isNull())
   1.504 +                this.nss.SECITEM_FreeItem(ivParam, true);
   1.505 +
   1.506 +            // Note that we do not free the IV SECItem; we reuse it.
   1.507 +            // Neither do we free the symKey, because that's memoized.
   1.508 +        }
   1.509 +    },
   1.510 +
   1.511 +
   1.512 +    generateRandomKey : function() {
   1.513 +        this.log("generateRandomKey() called");
   1.514 +        let slot, randKey, keydata;
   1.515 +        try {
   1.516 +            slot = this.nss.PK11_GetInternalSlot();
   1.517 +            if (slot.isNull())
   1.518 +                throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
   1.519 +
   1.520 +            randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null);
   1.521 +            if (randKey.isNull())
   1.522 +                throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE);
   1.523 +
   1.524 +            // Slightly odd API, this call just prepares the key value for
   1.525 +            // extraction, we get the actual bits from the call to PK11_GetKeyData().
   1.526 +            if (this.nss.PK11_ExtractKeyValue(randKey))
   1.527 +                throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
   1.528 +
   1.529 +            keydata = this.nss.PK11_GetKeyData(randKey);
   1.530 +            if (keydata.isNull())
   1.531 +                throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE);
   1.532 +
   1.533 +            return this.encodeBase64(keydata.contents.data, keydata.contents.len);
   1.534 +        } catch (e) {
   1.535 +            this.log("generateRandomKey: failed: " + e);
   1.536 +            throw e;
   1.537 +        } finally {
   1.538 +            if (randKey && !randKey.isNull())
   1.539 +                this.nss.PK11_FreeSymKey(randKey);
   1.540 +            if (slot && !slot.isNull())
   1.541 +                this.nss.PK11_FreeSlot(slot);
   1.542 +        }
   1.543 +    },
   1.544 +
   1.545 +    generateRandomIV : function() this.generateRandomBytes(this.ivLength),
   1.546 +
   1.547 +    generateRandomBytes : function(byteCount) {
   1.548 +        this.log("generateRandomBytes() called");
   1.549 +
   1.550 +        // Temporary buffer to hold the generated data.
   1.551 +        let scratch = this._getRandomByteBuffer(byteCount);
   1.552 +        if (this.nss.PK11_GenerateRandom(scratch, byteCount))
   1.553 +            throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE);
   1.554 +
   1.555 +        return this.encodeBase64(this._randomByteBufferAddr, byteCount);
   1.556 +    },
   1.557 +
   1.558 +    //
   1.559 +    // PK11SymKey memoization.
   1.560 +    //
   1.561 +
   1.562 +    // Memoize the lookup of symmetric keys. We do this by using the base64
   1.563 +    // string itself as a key -- the overhead of SECItem creation during the
   1.564 +    // initial population is negligible, so that phase is not memoized.
   1.565 +    _encryptionSymKeyMemo: {},
   1.566 +    _decryptionSymKeyMemo: {},
   1.567 +    importSymKey: function importSymKey(encodedKeyString, operation) {
   1.568 +        let memo;
   1.569 +
   1.570 +        // We use two separate memos for thoroughness: operation is an input to
   1.571 +        // key import.
   1.572 +        switch (operation) {
   1.573 +            case this.nss.CKA_ENCRYPT:
   1.574 +                memo = this._encryptionSymKeyMemo;
   1.575 +                break;
   1.576 +            case this.nss.CKA_DECRYPT:
   1.577 +                memo = this._decryptionSymKeyMemo;
   1.578 +                break;
   1.579 +            default:
   1.580 +                throw "Unsupported operation in importSymKey.";
   1.581 +        }
   1.582 +
   1.583 +        if (encodedKeyString in memo)
   1.584 +            return memo[encodedKeyString];
   1.585 +
   1.586 +        let keyItem, slot;
   1.587 +        try {
   1.588 +            keyItem = this.makeSECItem(encodedKeyString, true);
   1.589 +            slot    = this.nss.PK11_GetInternalKeySlot();
   1.590 +            if (slot.isNull())
   1.591 +                throw Components.Exception("can't get internal key slot",
   1.592 +                                           Cr.NS_ERROR_FAILURE);
   1.593 +
   1.594 +            let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism,
   1.595 +                                                    this.nss.PK11_OriginUnwrap,
   1.596 +                                                    operation, keyItem, null);
   1.597 +            if (!symKey || symKey.isNull())
   1.598 +                throw Components.Exception("symkey import failed",
   1.599 +                                           Cr.NS_ERROR_FAILURE);
   1.600 +
   1.601 +            return memo[encodedKeyString] = symKey;
   1.602 +        } finally {
   1.603 +            if (slot && !slot.isNull())
   1.604 +                this.nss.PK11_FreeSlot(slot);
   1.605 +            this.freeSECItem(keyItem);
   1.606 +        }
   1.607 +    },
   1.608 +
   1.609 +
   1.610 +    //
   1.611 +    // Utility functions
   1.612 +    //
   1.613 +
   1.614 +    /**
   1.615 +     * Compress a JS string into a C uint8 array. count is the number of
   1.616 +     * elements in the destination array. If the array is smaller than the
   1.617 +     * string, the string is effectively truncated. If the string is smaller
   1.618 +     * than the array, the array is not 0-padded.
   1.619 +     */
   1.620 +    byteCompressInts : function byteCompressInts (jsString, intArray, count) {
   1.621 +        let len = jsString.length;
   1.622 +        let end = Math.min(len, count);
   1.623 +        for (let i = 0; i < end; i++)
   1.624 +            intArray[i] = jsString.charCodeAt(i) & 0xFF;  // convert to bytes.
   1.625 +    },
   1.626 +
   1.627 +    // Expand a normal C string (1-byte chars) into a JS string (2-byte chars)
   1.628 +    // EG, for "ABC",  0x41, 0x42, 0x43 --> 0x0041, 0x0042, 0x0043
   1.629 +    byteExpand : function (charArray) {
   1.630 +        let expanded = "";
   1.631 +        let len = charArray.length;
   1.632 +        let intData = ctypes.cast(charArray, ctypes.uint8_t.array(len));
   1.633 +        for (let i = 0; i < len; i++)
   1.634 +            expanded += String.fromCharCode(intData[i]);
   1.635 +        return expanded;
   1.636 +    },
   1.637 +
   1.638 +    expandData : function expandData(data, len) {
   1.639 +        // Byte-expand the buffer, so we can treat it as a UCS-2 string
   1.640 +        // consisting of u0000 - u00FF.
   1.641 +        let expanded = "";
   1.642 +        let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents;
   1.643 +        for (let i = 0; i < len; i++)
   1.644 +            expanded += String.fromCharCode(intData[i]);
   1.645 +      return expanded;
   1.646 +    },
   1.647 +
   1.648 +    encodeBase64 : function (data, len) {
   1.649 +        return btoa(this.expandData(data, len));
   1.650 +    },
   1.651 +
   1.652 +    // Returns a filled SECItem *, as returned by SECITEM_AllocItem.
   1.653 +    //
   1.654 +    // Note that this must be released with freeSECItem, which will also
   1.655 +    // deallocate the internal buffer.
   1.656 +    makeSECItem : function(input, isEncoded) {
   1.657 +        if (isEncoded)
   1.658 +            input = atob(input);
   1.659 +
   1.660 +        let len = input.length;
   1.661 +        let item = this.nss.SECITEM_AllocItem(null, null, len);
   1.662 +        if (item.isNull())
   1.663 +            throw "SECITEM_AllocItem failed.";
   1.664 +
   1.665 +        let ptr  = ctypes.cast(item.contents.data,
   1.666 +                               ctypes.unsigned_char.array(len).ptr);
   1.667 +        let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len));
   1.668 +        this.byteCompressInts(input, dest, len);
   1.669 +        return item;
   1.670 +    },
   1.671 +
   1.672 +    freeSECItem : function(zap) {
   1.673 +        if (zap && !zap.isNull())
   1.674 +            this.nss.SECITEM_ZfreeItem(zap, true);
   1.675 +    },
   1.676 +
   1.677 +    // We only ever handle one IV at a time, and they're always different.
   1.678 +    // Consequently, we maintain a single SECItem, and a handy pointer into its
   1.679 +    // contents to avoid repetitive and expensive casts.
   1.680 +    _ivSECItem: null,
   1.681 +    _ivSECItemContents: null,
   1.682 +
   1.683 +    initIVSECItem: function initIVSECItem() {
   1.684 +        if (this._ivSECItem) {
   1.685 +            this._ivSECItemContents = null;
   1.686 +            this.freeSECItem(this._ivSECItem);
   1.687 +        }
   1.688 +
   1.689 +        let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize);
   1.690 +        if (item.isNull())
   1.691 +            throw "SECITEM_AllocItem failed.";
   1.692 +
   1.693 +        let ptr = ctypes.cast(item.contents.data,
   1.694 +                              ctypes.unsigned_char.array(this.blockSize).ptr);
   1.695 +        let contents = ctypes.cast(ptr.contents,
   1.696 +                                   ctypes.uint8_t.array(this.blockSize));
   1.697 +        this._ivSECItem = item;
   1.698 +        this._ivSECItemContents = contents;
   1.699 +    },
   1.700 +
   1.701 +    /**
   1.702 +     * Returns the expanded data string for the derived key.
   1.703 +     */
   1.704 +    deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) {
   1.705 +        this.log("deriveKeyFromPassphrase() called.");
   1.706 +        let passItem = this.makeSECItem(passphrase, false);
   1.707 +        let saltItem = this.makeSECItem(salt, true);
   1.708 +
   1.709 +        let pbeAlg    = ALGORITHM;
   1.710 +        let cipherAlg = ALGORITHM;   // Ignored by callee when pbeAlg != a pkcs5 mech.
   1.711 +
   1.712 +        // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported.
   1.713 +        let prfAlg    = this.nss.SEC_OID_HMAC_SHA1;
   1.714 +
   1.715 +        let keyLength  = keyLength || 0;    // 0 = Callee will pick.
   1.716 +        let iterations = KEY_DERIVATION_ITERATIONS;
   1.717 +
   1.718 +        let algid, slot, symKey, keyData;
   1.719 +        try {
   1.720 +            algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
   1.721 +                                                         keyLength, iterations,
   1.722 +                                                         saltItem);
   1.723 +            if (algid.isNull())
   1.724 +                throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
   1.725 +
   1.726 +            slot = this.nss.PK11_GetInternalSlot();
   1.727 +            if (slot.isNull())
   1.728 +                throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
   1.729 +
   1.730 +            symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem, false, null);
   1.731 +            if (symKey.isNull())
   1.732 +                throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE);
   1.733 +
   1.734 +            // Take the PK11SymKeyStr, returning the extracted key data.
   1.735 +            if (this.nss.PK11_ExtractKeyValue(symKey)) {
   1.736 +                throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
   1.737 +            }
   1.738 +
   1.739 +            keyData = this.nss.PK11_GetKeyData(symKey);
   1.740 +
   1.741 +            if (keyData.isNull())
   1.742 +                throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE);
   1.743 +
   1.744 +            // This copies the key contents into a JS string, so we don't leak.
   1.745 +            // The `finally` block below will clean up.
   1.746 +            return this.expandData(keyData.contents.data, keyData.contents.len);
   1.747 +
   1.748 +        } catch (e) {
   1.749 +            this.log("deriveKeyFromPassphrase: failed: " + e);
   1.750 +            throw e;
   1.751 +        } finally {
   1.752 +            if (algid && !algid.isNull())
   1.753 +                this.nss.SECOID_DestroyAlgorithmID(algid, true);
   1.754 +            if (slot && !slot.isNull())
   1.755 +                this.nss.PK11_FreeSlot(slot);
   1.756 +            if (symKey && !symKey.isNull())
   1.757 +                this.nss.PK11_FreeSymKey(symKey);
   1.758 +
   1.759 +            this.freeSECItem(passItem);
   1.760 +            this.freeSECItem(saltItem);
   1.761 +        }
   1.762 +    },
   1.763 +};

mercurial