services/crypto/modules/WeaveCrypto.js

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 this.EXPORTED_SYMBOLS = ["WeaveCrypto"];
     7 const Cc = Components.classes;
     8 const Ci = Components.interfaces;
     9 const Cr = Components.results;
    11 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    12 Components.utils.import("resource://gre/modules/Services.jsm");
    13 Components.utils.import("resource://gre/modules/ctypes.jsm");
    15 /**
    16  * Shortcuts for some algorithm SEC OIDs.  Full list available here:
    17  * http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h
    18  */
    19 const DES_EDE3_CBC = 156;
    20 const AES_128_CBC  = 184;
    21 const AES_192_CBC  = 186;
    22 const AES_256_CBC  = 188;
    24 const ALGORITHM                 = AES_256_CBC;
    25 const KEYSIZE_AES_256           = 32;
    26 const KEY_DERIVATION_ITERATIONS = 4096;   // PKCS#5 recommends at least 1000.
    27 const INITIAL_BUFFER_SIZE       = 1024;
    29 this.WeaveCrypto = function WeaveCrypto() {
    30     this.init();
    31 }
    33 WeaveCrypto.prototype = {
    34     prefBranch : null,
    35     debug      : true,  // services.sync.log.cryptoDebug
    36     nss        : null,
    37     nss_t      : null,
    39     observer : {
    40         _self : null,
    42         QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
    43                                                 Ci.nsISupportsWeakReference]),
    45         observe : function (subject, topic, data) {
    46             let self = this._self;
    47             self.log("Observed " + topic + " topic.");
    48             if (topic == "nsPref:changed") {
    49                 self.debug = self.prefBranch.getBoolPref("cryptoDebug");
    50             }
    51         }
    52     },
    54     init : function() {
    55         try {
    56             // Preferences. Add observer so we get notified of changes.
    57             this.prefBranch = Services.prefs.getBranch("services.sync.log.");
    58             this.prefBranch.addObserver("cryptoDebug", this.observer, false);
    59             this.observer._self = this;
    60             try {
    61               this.debug = this.prefBranch.getBoolPref("cryptoDebug");
    62             } catch (x) {
    63               this.debug = false;
    64             }
    66             this.initNSS();
    67             this.initAlgorithmSettings();   // Depends on NSS.
    68             this.initIVSECItem();
    69             this.initSharedInts();
    70             this.initBuffers(INITIAL_BUFFER_SIZE);
    71         } catch (e) {
    72             this.log("init failed: " + e);
    73             throw e;
    74         }
    75     },
    77     // Avoid allocating new temporary ints on every run of _commonCrypt.
    78     _commonCryptSignedOutputSize:       null,
    79     _commonCryptSignedOutputSizeAddr:   null,
    80     _commonCryptUnsignedOutputSize:     null,
    81     _commonCryptUnsignedOutputSizeAddr: null,
    83     initSharedInts: function initSharedInts() {
    84         let signed   = new ctypes.int();
    85         let unsigned = new ctypes.unsigned_int();
    86         this._commonCryptSignedOutputSize       = signed;
    87         this._commonCryptUnsignedOutputSize     = unsigned;
    88         this._commonCryptSignedOutputSizeAddr   = signed.address();
    89         this._commonCryptUnsignedOutputSizeAddr = unsigned.address();
    90     },
    92     /**
    93      * Set a bunch of NSS values once, at init-time. These are:
    94      *   - .blockSize
    95      *   - .mechanism
    96      *   - .keygenMechanism
    97      *   - .padMechanism
    98      *   - .keySize
    99      *
   100      * See also the constant ALGORITHM.
   101      */
   102     initAlgorithmSettings: function() {
   103         this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM);
   104         this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null);
   105         this.ivLength  = this.nss.PK11_GetIVLength(this.mechanism);
   106         this.keySize   = KEYSIZE_AES_256;
   107         this.keygenMechanism = this.nss.CKM_AES_KEY_GEN;  // Always the same!
   109         // Determine which (padded) PKCS#11 mechanism to use.
   110         // E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
   111         this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism);
   112         if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM)
   113             throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
   114     },
   116     log : function (message) {
   117         if (!this.debug)
   118             return;
   119         dump("WeaveCrypto: " + message + "\n");
   120         Services.console.logStringMessage("WeaveCrypto: " + message);
   121     },
   123     initNSS : function() {
   124         // We use NSS for the crypto ops, which needs to be initialized before
   125         // use. By convention, PSM is required to be the module that
   126         // initializes NSS. So, make sure PSM is initialized in order to
   127         // implicitly initialize NSS.
   128         Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
   130         // Open the NSS library.
   131         let path = ctypes.libraryName("nss3");
   133         // XXX really want to be able to pass specific dlopen flags here.
   134         var nsslib;
   135         try {
   136             this.log("Trying NSS library without path");
   137             nsslib = ctypes.open(path);
   138         } catch(e) {
   139             // In case opening the library without a full path fails,
   140             // try again with a full path.
   141             let file = Services.dirsvc.get("GreD", Ci.nsILocalFile);
   142             file.append(path);
   143             this.log("Trying again with path " + file.path);
   144             nsslib = ctypes.open(file.path);
   145         }
   147         this.log("Initializing NSS types and function declarations...");
   149         this.nss = {};
   150         this.nss_t = {};
   152         // nsprpub/pr/include/prtypes.h#435
   153         // typedef PRIntn PRBool; --> int
   154         this.nss_t.PRBool = ctypes.int;
   155         // security/nss/lib/util/seccomon.h#91
   156         // typedef enum
   157         this.nss_t.SECStatus = ctypes.int;
   158         // security/nss/lib/softoken/secmodt.h#59
   159         // typedef struct PK11SlotInfoStr PK11SlotInfo; (defined in secmodti.h) 
   160         this.nss_t.PK11SlotInfo = ctypes.void_t;
   161         // security/nss/lib/util/pkcs11t.h
   162         this.nss_t.CK_MECHANISM_TYPE = ctypes.unsigned_long;
   163         this.nss_t.CK_ATTRIBUTE_TYPE = ctypes.unsigned_long;
   164         this.nss_t.CK_KEY_TYPE       = ctypes.unsigned_long;
   165         this.nss_t.CK_OBJECT_HANDLE  = ctypes.unsigned_long;
   166         // security/nss/lib/softoken/secmodt.h#359
   167         // typedef enum PK11Origin
   168         this.nss_t.PK11Origin = ctypes.int;
   169         // PK11Origin enum values...
   170         this.nss.PK11_OriginUnwrap = 4;
   171         // security/nss/lib/softoken/secmodt.h#61
   172         // typedef struct PK11SymKeyStr PK11SymKey; (defined in secmodti.h)
   173         this.nss_t.PK11SymKey = ctypes.void_t;
   174         // security/nss/lib/util/secoidt.h#454
   175         // typedef enum
   176         this.nss_t.SECOidTag = ctypes.int;
   177         // security/nss/lib/util/seccomon.h#64
   178         // typedef enum
   179         this.nss_t.SECItemType = ctypes.int;
   180         // SECItemType enum values...
   181         this.nss.SIBUFFER = 0;
   182         // security/nss/lib/softoken/secmodt.h#62 (defined in secmodti.h)
   183         // typedef struct PK11ContextStr PK11Context;
   184         this.nss_t.PK11Context = ctypes.void_t;
   185         // Needed for SECKEYPrivateKey struct def'n, but I don't think we need to actually access it.
   186         this.nss_t.PLArenaPool = ctypes.void_t;
   187         // security/nss/lib/cryptohi/keythi.h#45
   188         // typedef enum
   189         this.nss_t.KeyType = ctypes.int;
   190         // security/nss/lib/softoken/secmodt.h#201
   191         // typedef PRUint32 PK11AttrFlags;
   192         this.nss_t.PK11AttrFlags = ctypes.unsigned_int;
   193         // security/nss/lib/util/seccomon.h#83
   194         // typedef struct SECItemStr SECItem; --> SECItemStr defined right below it
   195         this.nss_t.SECItem = ctypes.StructType(
   196             "SECItem", [{ type: this.nss_t.SECItemType },
   197                         { data: ctypes.unsigned_char.ptr },
   198                         { len : ctypes.int }]);
   199         // security/nss/lib/util/secoidt.h#52
   200         // typedef struct SECAlgorithmIDStr --> def'n right below it
   201         this.nss_t.SECAlgorithmID = ctypes.StructType(
   202             "SECAlgorithmID", [{ algorithm:  this.nss_t.SECItem },
   203                                { parameters: this.nss_t.SECItem }]);
   206         // security/nss/lib/util/pkcs11t.h
   207         this.nss.CKK_RSA = 0x0;
   208         this.nss.CKM_RSA_PKCS_KEY_PAIR_GEN = 0x0000;
   209         this.nss.CKM_AES_KEY_GEN           = 0x1080; 
   210         this.nss.CKA_ENCRYPT = 0x104;
   211         this.nss.CKA_DECRYPT = 0x105;
   213         // security/nss/lib/softoken/secmodt.h
   214         this.nss.PK11_ATTR_SESSION   = 0x02;
   215         this.nss.PK11_ATTR_PUBLIC    = 0x08;
   216         this.nss.PK11_ATTR_SENSITIVE = 0x40;
   218         // security/nss/lib/util/secoidt.h
   219         this.nss.SEC_OID_PKCS5_PBKDF2         = 291;
   220         this.nss.SEC_OID_HMAC_SHA1            = 294;
   221         this.nss.SEC_OID_PKCS1_RSA_ENCRYPTION = 16;
   224         // security/nss/lib/pk11wrap/pk11pub.h#286
   225         // SECStatus PK11_GenerateRandom(unsigned char *data,int len);
   226         this.nss.PK11_GenerateRandom = nsslib.declare("PK11_GenerateRandom",
   227                                                       ctypes.default_abi, this.nss_t.SECStatus,
   228                                                       ctypes.unsigned_char.ptr, ctypes.int);
   229         // security/nss/lib/pk11wrap/pk11pub.h#74
   230         // PK11SlotInfo *PK11_GetInternalSlot(void);
   231         this.nss.PK11_GetInternalSlot = nsslib.declare("PK11_GetInternalSlot",
   232                                                        ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
   233         // security/nss/lib/pk11wrap/pk11pub.h#73
   234         // PK11SlotInfo *PK11_GetInternalKeySlot(void);
   235         this.nss.PK11_GetInternalKeySlot = nsslib.declare("PK11_GetInternalKeySlot",
   236                                                           ctypes.default_abi, this.nss_t.PK11SlotInfo.ptr);
   237         // security/nss/lib/pk11wrap/pk11pub.h#328
   238         // PK11SymKey *PK11_KeyGen(PK11SlotInfo *slot,CK_MECHANISM_TYPE type, SECItem *param, int keySize,void *wincx);
   239         this.nss.PK11_KeyGen = nsslib.declare("PK11_KeyGen",
   240                                               ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   241                                               this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE,
   242                                               this.nss_t.SECItem.ptr, ctypes.int, ctypes.voidptr_t);
   243         // security/nss/lib/pk11wrap/pk11pub.h#477
   244         // SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey);
   245         this.nss.PK11_ExtractKeyValue = nsslib.declare("PK11_ExtractKeyValue",
   246                                                        ctypes.default_abi, this.nss_t.SECStatus,
   247                                                        this.nss_t.PK11SymKey.ptr);
   248         // security/nss/lib/pk11wrap/pk11pub.h#478
   249         // SECItem * PK11_GetKeyData(PK11SymKey *symKey);
   250         this.nss.PK11_GetKeyData = nsslib.declare("PK11_GetKeyData",
   251                                                   ctypes.default_abi, this.nss_t.SECItem.ptr,
   252                                                   this.nss_t.PK11SymKey.ptr);
   253         // security/nss/lib/pk11wrap/pk11pub.h#278
   254         // CK_MECHANISM_TYPE PK11_AlgtagToMechanism(SECOidTag algTag);
   255         this.nss.PK11_AlgtagToMechanism = nsslib.declare("PK11_AlgtagToMechanism",
   256                                                          ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
   257                                                          this.nss_t.SECOidTag);
   258         // security/nss/lib/pk11wrap/pk11pub.h#270
   259         // int PK11_GetIVLength(CK_MECHANISM_TYPE type);
   260         this.nss.PK11_GetIVLength = nsslib.declare("PK11_GetIVLength",
   261                                                    ctypes.default_abi, ctypes.int,
   262                                                    this.nss_t.CK_MECHANISM_TYPE);
   263         // security/nss/lib/pk11wrap/pk11pub.h#269
   264         // int PK11_GetBlockSize(CK_MECHANISM_TYPE type,SECItem *params);
   265         this.nss.PK11_GetBlockSize = nsslib.declare("PK11_GetBlockSize",
   266                                                     ctypes.default_abi, ctypes.int,
   267                                                     this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
   268         // security/nss/lib/pk11wrap/pk11pub.h#293
   269         // CK_MECHANISM_TYPE PK11_GetPadMechanism(CK_MECHANISM_TYPE);
   270         this.nss.PK11_GetPadMechanism = nsslib.declare("PK11_GetPadMechanism",
   271                                                        ctypes.default_abi, this.nss_t.CK_MECHANISM_TYPE,
   272                                                        this.nss_t.CK_MECHANISM_TYPE);
   273         // security/nss/lib/pk11wrap/pk11pub.h#271
   274         // SECItem *PK11_ParamFromIV(CK_MECHANISM_TYPE type,SECItem *iv);
   275         this.nss.PK11_ParamFromIV = nsslib.declare("PK11_ParamFromIV",
   276                                                    ctypes.default_abi, this.nss_t.SECItem.ptr,
   277                                                    this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr);
   278         // security/nss/lib/pk11wrap/pk11pub.h#301
   279         // PK11SymKey *PK11_ImportSymKey(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, PK11Origin origin,
   280         //                               CK_ATTRIBUTE_TYPE operation, SECItem *key, void *wincx);
   281         this.nss.PK11_ImportSymKey = nsslib.declare("PK11_ImportSymKey",
   282                                                     ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   283                                                     this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, this.nss_t.PK11Origin,
   284                                                     this.nss_t.CK_ATTRIBUTE_TYPE, this.nss_t.SECItem.ptr, ctypes.voidptr_t);
   285         // security/nss/lib/pk11wrap/pk11pub.h#672
   286         // PK11Context *PK11_CreateContextBySymKey(CK_MECHANISM_TYPE type, CK_ATTRIBUTE_TYPE operation,
   287         //                                         PK11SymKey *symKey, SECItem *param);
   288         this.nss.PK11_CreateContextBySymKey = nsslib.declare("PK11_CreateContextBySymKey",
   289                                                              ctypes.default_abi, this.nss_t.PK11Context.ptr,
   290                                                              this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE,
   291                                                              this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr);
   292         // security/nss/lib/pk11wrap/pk11pub.h#685
   293         // SECStatus PK11_CipherOp(PK11Context *context, unsigned char *out
   294         //                         int *outlen, int maxout, unsigned char *in, int inlen);
   295         this.nss.PK11_CipherOp = nsslib.declare("PK11_CipherOp",
   296                                                 ctypes.default_abi, this.nss_t.SECStatus,
   297                                                 this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
   298                                                 ctypes.int.ptr, ctypes.int, ctypes.unsigned_char.ptr, ctypes.int);
   299         // security/nss/lib/pk11wrap/pk11pub.h#688
   300         // SECStatus PK11_DigestFinal(PK11Context *context, unsigned char *data,
   301         //                            unsigned int *outLen, unsigned int length);
   302         this.nss.PK11_DigestFinal = nsslib.declare("PK11_DigestFinal",
   303                                                    ctypes.default_abi, this.nss_t.SECStatus,
   304                                                    this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
   305                                                    ctypes.unsigned_int.ptr, ctypes.unsigned_int);
   306         // security/nss/lib/pk11wrap/pk11pub.h#731
   307         // SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
   308         //                                              SECOidTag prfAlgTag, int keyLength, int iteration,
   309         //                                              SECItem *salt);
   310         this.nss.PK11_CreatePBEV2AlgorithmID = nsslib.declare("PK11_CreatePBEV2AlgorithmID",
   311                                                               ctypes.default_abi, this.nss_t.SECAlgorithmID.ptr,
   312                                                               this.nss_t.SECOidTag, this.nss_t.SECOidTag, this.nss_t.SECOidTag, 
   313                                                               ctypes.int, ctypes.int, this.nss_t.SECItem.ptr);
   314         // security/nss/lib/pk11wrap/pk11pub.h#736
   315         // PK11SymKey * PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid,  SECItem *pwitem, PRBool faulty3DES, void *wincx);
   316         this.nss.PK11_PBEKeyGen = nsslib.declare("PK11_PBEKeyGen",
   317                                                  ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
   318                                                  this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr,
   319                                                  this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t);
   320         // security/nss/lib/pk11wrap/pk11pub.h#675
   321         // void PK11_DestroyContext(PK11Context *context, PRBool freeit);
   322         this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext",
   323                                                        ctypes.default_abi, ctypes.void_t,
   324                                                        this.nss_t.PK11Context.ptr, this.nss_t.PRBool);
   325         // security/nss/lib/pk11wrap/pk11pub.h#299
   326         // void PK11_FreeSymKey(PK11SymKey *key);
   327         this.nss.PK11_FreeSymKey = nsslib.declare("PK11_FreeSymKey",
   328                                                   ctypes.default_abi, ctypes.void_t,
   329                                                   this.nss_t.PK11SymKey.ptr);
   330         // security/nss/lib/pk11wrap/pk11pub.h#70
   331         // void PK11_FreeSlot(PK11SlotInfo *slot);
   332         this.nss.PK11_FreeSlot = nsslib.declare("PK11_FreeSlot",
   333                                                 ctypes.default_abi, ctypes.void_t,
   334                                                 this.nss_t.PK11SlotInfo.ptr);
   335         // security/nss/lib/util/secitem.h#49
   336         // extern SECItem *SECITEM_AllocItem(PRArenaPool *arena, SECItem *item, unsigned int len);
   337         this.nss.SECITEM_AllocItem = nsslib.declare("SECITEM_AllocItem",
   338                                                     ctypes.default_abi, this.nss_t.SECItem.ptr,
   339                                                     this.nss_t.PLArenaPool.ptr,     // Not used.
   340                                                     this.nss_t.SECItem.ptr, ctypes.unsigned_int);
   341         // security/nss/lib/util/secitem.h#274
   342         // extern void SECITEM_ZfreeItem(SECItem *zap, PRBool freeit);
   343         this.nss.SECITEM_ZfreeItem = nsslib.declare("SECITEM_ZfreeItem",
   344                                                     ctypes.default_abi, ctypes.void_t,
   345                                                     this.nss_t.SECItem.ptr, this.nss_t.PRBool);
   346         // security/nss/lib/util/secitem.h#114
   347         // extern void SECITEM_FreeItem(SECItem *zap, PRBool freeit);
   348         this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem",
   349                                                    ctypes.default_abi, ctypes.void_t,
   350                                                    this.nss_t.SECItem.ptr, this.nss_t.PRBool);
   351         // security/nss/lib/util/secoid.h#103
   352         // extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit);
   353         this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID",
   354                                                             ctypes.default_abi, ctypes.void_t,
   355                                                             this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
   356     },
   359     _sharedInputBuffer:      null,
   360     _sharedInputBufferInts:  null,
   361     _sharedInputBufferSize:  0,
   362     _sharedOutputBuffer:     null,
   363     _sharedOutputBufferSize: 0,
   364     _randomByteBuffer:       null,
   365     _randomByteBufferAddr:   null,
   366     _randomByteBufferSize:   0,
   368     _getInputBuffer: function _getInputBuffer(size) {
   369       if (size > this._sharedInputBufferSize) {
   370         let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   371         this._sharedInputBuffer     = b;
   372         this._sharedInputBufferInts = ctypes.cast(b, ctypes.uint8_t.array(size));
   373         this._sharedInputBufferSize = size;
   374       }
   375       return this._sharedInputBuffer;
   376     },
   378     _getOutputBuffer: function _getOutputBuffer(size) {
   379       if (size > this._sharedOutputBufferSize) {
   380         let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   381         this._sharedOutputBuffer     = b;
   382         this._sharedOutputBufferSize = size;
   383       }
   384       return this._sharedOutputBuffer;
   385     },
   387     _getRandomByteBuffer: function _getRandomByteBuffer(size) {
   388         if (size > this._randomByteBufferSize) {
   389           let b = new ctypes.ArrayType(ctypes.unsigned_char, size)();
   390           this._randomByteBuffer     = b;
   391           this._randomByteBufferAddr = b.address();
   392           this._randomByteBufferSize = size;
   393         }
   394         return this._randomByteBuffer;
   395     },
   397     initBuffers: function initBuffers(initialSize) {
   398         this._getInputBuffer(initialSize);
   399         this._getOutputBuffer(initialSize);
   401         this._getRandomByteBuffer(this.ivLength);
   402     },
   404     encrypt : function(clearTextUCS2, symmetricKey, iv) {
   405         this.log("encrypt() called");
   407         // js-ctypes autoconverts to a UTF8 buffer, but also includes a null
   408         // at the end which we don't want. Decrement length to skip it.
   409         let inputBuffer = new ctypes.ArrayType(ctypes.unsigned_char)(clearTextUCS2);
   410         let inputBufferSize = inputBuffer.length - 1;
   412         // When using CBC padding, the output size is the input size rounded
   413         // up to the nearest block. If the input size is exactly on a block
   414         // boundary, the output is 1 extra block long.
   415         let outputBufferSize = inputBufferSize + this.blockSize;
   416         let outputBuffer = this._getOutputBuffer(outputBufferSize);
   418         outputBuffer = this._commonCrypt(inputBuffer, inputBufferSize,
   419                                          outputBuffer, outputBufferSize,
   420                                          symmetricKey, iv, this.nss.CKA_ENCRYPT);
   422         return this.encodeBase64(outputBuffer.address(), outputBuffer.length);
   423     },
   426     decrypt : function(cipherText, symmetricKey, iv) {
   427         this.log("decrypt() called");
   429         let inputUCS2 = "";
   430         if (cipherText.length)
   431             inputUCS2 = atob(cipherText);
   433         // We can't have js-ctypes create the buffer directly from the string
   434         // (as in encrypt()), because we do _not_ want it to do UTF8
   435         // conversion... We've got random binary data in the input's low byte.
   436         //
   437         // Compress a JS string (2-byte chars) into a normal C string (1-byte chars).
   438         let len   = inputUCS2.length;
   439         let input = this._getInputBuffer(len);
   440         this.byteCompressInts(inputUCS2, this._sharedInputBufferInts, len);
   442         let outputBuffer = this._commonCrypt(input, len,
   443                                              this._getOutputBuffer(len), len,
   444                                              symmetricKey, iv, this.nss.CKA_DECRYPT);
   446         // outputBuffer contains UTF-8 data, let js-ctypes autoconvert that to a JS string.
   447         // XXX Bug 573842: wrap the string from ctypes to get a new string, so
   448         // we don't hit bug 573841.
   449         return "" + outputBuffer.readString() + "";
   450     },
   452     _commonCrypt : function (input, inputLength, output, outputLength, symmetricKey, iv, operation) {
   453         this.log("_commonCrypt() called");
   454         iv = atob(iv);
   456         // We never want an IV longer than the block size, which is 16 bytes
   457         // for AES. Neither do we want one smaller; throw in that case.
   458         if (iv.length < this.blockSize)
   459             throw "IV too short; must be " + this.blockSize + " bytes.";
   460         if (iv.length > this.blockSize)
   461             iv = iv.slice(0, this.blockSize);
   463         // We use a single IV SECItem for the sake of efficiency. Fill it here.
   464         this.byteCompressInts(iv, this._ivSECItemContents, iv.length);
   466         let ctx, symKey, ivParam;
   467         try {
   468             ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem);
   469             if (ivParam.isNull())
   470                 throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE);
   472             symKey = this.importSymKey(symmetricKey, operation);
   473             ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam);
   474             if (ctx.isNull())
   475                 throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
   477             let maxOutputSize = outputLength;
   478             if (this.nss.PK11_CipherOp(ctx, output, this._commonCryptSignedOutputSize.address(), maxOutputSize, input, inputLength))
   479                 throw Components.Exception("cipher operation failed", Cr.NS_ERROR_FAILURE);
   481             let actualOutputSize = this._commonCryptSignedOutputSize.value;
   482             let finalOutput = output.addressOfElement(actualOutputSize);
   483             maxOutputSize -= actualOutputSize;
   485             // PK11_DigestFinal sure sounds like the last step for *hashing*, but it
   486             // just seems to be an odd name -- NSS uses this to finish the current
   487             // cipher operation. You'd think it would be called PK11_CipherOpFinal...
   488             if (this.nss.PK11_DigestFinal(ctx, finalOutput, this._commonCryptUnsignedOutputSizeAddr, maxOutputSize))
   489                 throw Components.Exception("cipher finalize failed", Cr.NS_ERROR_FAILURE);
   491             actualOutputSize += this._commonCryptUnsignedOutputSize.value;
   492             let newOutput = ctypes.cast(output, ctypes.unsigned_char.array(actualOutputSize));
   493             return newOutput;
   494         } catch (e) {
   495             this.log("_commonCrypt: failed: " + e);
   496             throw e;
   497         } finally {
   498             if (ctx && !ctx.isNull())
   499                 this.nss.PK11_DestroyContext(ctx, true);
   500             if (ivParam && !ivParam.isNull())
   501                 this.nss.SECITEM_FreeItem(ivParam, true);
   503             // Note that we do not free the IV SECItem; we reuse it.
   504             // Neither do we free the symKey, because that's memoized.
   505         }
   506     },
   509     generateRandomKey : function() {
   510         this.log("generateRandomKey() called");
   511         let slot, randKey, keydata;
   512         try {
   513             slot = this.nss.PK11_GetInternalSlot();
   514             if (slot.isNull())
   515                 throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
   517             randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null);
   518             if (randKey.isNull())
   519                 throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE);
   521             // Slightly odd API, this call just prepares the key value for
   522             // extraction, we get the actual bits from the call to PK11_GetKeyData().
   523             if (this.nss.PK11_ExtractKeyValue(randKey))
   524                 throw Components.Exception("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
   526             keydata = this.nss.PK11_GetKeyData(randKey);
   527             if (keydata.isNull())
   528                 throw Components.Exception("PK11_GetKeyData failed.", Cr.NS_ERROR_FAILURE);
   530             return this.encodeBase64(keydata.contents.data, keydata.contents.len);
   531         } catch (e) {
   532             this.log("generateRandomKey: failed: " + e);
   533             throw e;
   534         } finally {
   535             if (randKey && !randKey.isNull())
   536                 this.nss.PK11_FreeSymKey(randKey);
   537             if (slot && !slot.isNull())
   538                 this.nss.PK11_FreeSlot(slot);
   539         }
   540     },
   542     generateRandomIV : function() this.generateRandomBytes(this.ivLength),
   544     generateRandomBytes : function(byteCount) {
   545         this.log("generateRandomBytes() called");
   547         // Temporary buffer to hold the generated data.
   548         let scratch = this._getRandomByteBuffer(byteCount);
   549         if (this.nss.PK11_GenerateRandom(scratch, byteCount))
   550             throw Components.Exception("PK11_GenrateRandom failed", Cr.NS_ERROR_FAILURE);
   552         return this.encodeBase64(this._randomByteBufferAddr, byteCount);
   553     },
   555     //
   556     // PK11SymKey memoization.
   557     //
   559     // Memoize the lookup of symmetric keys. We do this by using the base64
   560     // string itself as a key -- the overhead of SECItem creation during the
   561     // initial population is negligible, so that phase is not memoized.
   562     _encryptionSymKeyMemo: {},
   563     _decryptionSymKeyMemo: {},
   564     importSymKey: function importSymKey(encodedKeyString, operation) {
   565         let memo;
   567         // We use two separate memos for thoroughness: operation is an input to
   568         // key import.
   569         switch (operation) {
   570             case this.nss.CKA_ENCRYPT:
   571                 memo = this._encryptionSymKeyMemo;
   572                 break;
   573             case this.nss.CKA_DECRYPT:
   574                 memo = this._decryptionSymKeyMemo;
   575                 break;
   576             default:
   577                 throw "Unsupported operation in importSymKey.";
   578         }
   580         if (encodedKeyString in memo)
   581             return memo[encodedKeyString];
   583         let keyItem, slot;
   584         try {
   585             keyItem = this.makeSECItem(encodedKeyString, true);
   586             slot    = this.nss.PK11_GetInternalKeySlot();
   587             if (slot.isNull())
   588                 throw Components.Exception("can't get internal key slot",
   589                                            Cr.NS_ERROR_FAILURE);
   591             let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism,
   592                                                     this.nss.PK11_OriginUnwrap,
   593                                                     operation, keyItem, null);
   594             if (!symKey || symKey.isNull())
   595                 throw Components.Exception("symkey import failed",
   596                                            Cr.NS_ERROR_FAILURE);
   598             return memo[encodedKeyString] = symKey;
   599         } finally {
   600             if (slot && !slot.isNull())
   601                 this.nss.PK11_FreeSlot(slot);
   602             this.freeSECItem(keyItem);
   603         }
   604     },
   607     //
   608     // Utility functions
   609     //
   611     /**
   612      * Compress a JS string into a C uint8 array. count is the number of
   613      * elements in the destination array. If the array is smaller than the
   614      * string, the string is effectively truncated. If the string is smaller
   615      * than the array, the array is not 0-padded.
   616      */
   617     byteCompressInts : function byteCompressInts (jsString, intArray, count) {
   618         let len = jsString.length;
   619         let end = Math.min(len, count);
   620         for (let i = 0; i < end; i++)
   621             intArray[i] = jsString.charCodeAt(i) & 0xFF;  // convert to bytes.
   622     },
   624     // Expand a normal C string (1-byte chars) into a JS string (2-byte chars)
   625     // EG, for "ABC",  0x41, 0x42, 0x43 --> 0x0041, 0x0042, 0x0043
   626     byteExpand : function (charArray) {
   627         let expanded = "";
   628         let len = charArray.length;
   629         let intData = ctypes.cast(charArray, ctypes.uint8_t.array(len));
   630         for (let i = 0; i < len; i++)
   631             expanded += String.fromCharCode(intData[i]);
   632         return expanded;
   633     },
   635     expandData : function expandData(data, len) {
   636         // Byte-expand the buffer, so we can treat it as a UCS-2 string
   637         // consisting of u0000 - u00FF.
   638         let expanded = "";
   639         let intData = ctypes.cast(data, ctypes.uint8_t.array(len).ptr).contents;
   640         for (let i = 0; i < len; i++)
   641             expanded += String.fromCharCode(intData[i]);
   642       return expanded;
   643     },
   645     encodeBase64 : function (data, len) {
   646         return btoa(this.expandData(data, len));
   647     },
   649     // Returns a filled SECItem *, as returned by SECITEM_AllocItem.
   650     //
   651     // Note that this must be released with freeSECItem, which will also
   652     // deallocate the internal buffer.
   653     makeSECItem : function(input, isEncoded) {
   654         if (isEncoded)
   655             input = atob(input);
   657         let len = input.length;
   658         let item = this.nss.SECITEM_AllocItem(null, null, len);
   659         if (item.isNull())
   660             throw "SECITEM_AllocItem failed.";
   662         let ptr  = ctypes.cast(item.contents.data,
   663                                ctypes.unsigned_char.array(len).ptr);
   664         let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len));
   665         this.byteCompressInts(input, dest, len);
   666         return item;
   667     },
   669     freeSECItem : function(zap) {
   670         if (zap && !zap.isNull())
   671             this.nss.SECITEM_ZfreeItem(zap, true);
   672     },
   674     // We only ever handle one IV at a time, and they're always different.
   675     // Consequently, we maintain a single SECItem, and a handy pointer into its
   676     // contents to avoid repetitive and expensive casts.
   677     _ivSECItem: null,
   678     _ivSECItemContents: null,
   680     initIVSECItem: function initIVSECItem() {
   681         if (this._ivSECItem) {
   682             this._ivSECItemContents = null;
   683             this.freeSECItem(this._ivSECItem);
   684         }
   686         let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize);
   687         if (item.isNull())
   688             throw "SECITEM_AllocItem failed.";
   690         let ptr = ctypes.cast(item.contents.data,
   691                               ctypes.unsigned_char.array(this.blockSize).ptr);
   692         let contents = ctypes.cast(ptr.contents,
   693                                    ctypes.uint8_t.array(this.blockSize));
   694         this._ivSECItem = item;
   695         this._ivSECItemContents = contents;
   696     },
   698     /**
   699      * Returns the expanded data string for the derived key.
   700      */
   701     deriveKeyFromPassphrase : function deriveKeyFromPassphrase(passphrase, salt, keyLength) {
   702         this.log("deriveKeyFromPassphrase() called.");
   703         let passItem = this.makeSECItem(passphrase, false);
   704         let saltItem = this.makeSECItem(salt, true);
   706         let pbeAlg    = ALGORITHM;
   707         let cipherAlg = ALGORITHM;   // Ignored by callee when pbeAlg != a pkcs5 mech.
   709         // Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported.
   710         let prfAlg    = this.nss.SEC_OID_HMAC_SHA1;
   712         let keyLength  = keyLength || 0;    // 0 = Callee will pick.
   713         let iterations = KEY_DERIVATION_ITERATIONS;
   715         let algid, slot, symKey, keyData;
   716         try {
   717             algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
   718                                                          keyLength, iterations,
   719                                                          saltItem);
   720             if (algid.isNull())
   721                 throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
   723             slot = this.nss.PK11_GetInternalSlot();
   724             if (slot.isNull())
   725                 throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
   727             symKey = this.nss.PK11_PBEKeyGen(slot, algid, passItem, false, null);
   728             if (symKey.isNull())
   729                 throw Components.Exception("PK11_PBEKeyGen failed", Cr.NS_ERROR_FAILURE);
   731             // Take the PK11SymKeyStr, returning the extracted key data.
   732             if (this.nss.PK11_ExtractKeyValue(symKey)) {
   733                 throw this.makeException("PK11_ExtractKeyValue failed.", Cr.NS_ERROR_FAILURE);
   734             }
   736             keyData = this.nss.PK11_GetKeyData(symKey);
   738             if (keyData.isNull())
   739                 throw Components.Exception("PK11_GetKeyData failed", Cr.NS_ERROR_FAILURE);
   741             // This copies the key contents into a JS string, so we don't leak.
   742             // The `finally` block below will clean up.
   743             return this.expandData(keyData.contents.data, keyData.contents.len);
   745         } catch (e) {
   746             this.log("deriveKeyFromPassphrase: failed: " + e);
   747             throw e;
   748         } finally {
   749             if (algid && !algid.isNull())
   750                 this.nss.SECOID_DestroyAlgorithmID(algid, true);
   751             if (slot && !slot.isNull())
   752                 this.nss.PK11_FreeSlot(slot);
   753             if (symKey && !symKey.isNull())
   754                 this.nss.PK11_FreeSymKey(symKey);
   756             this.freeSECItem(passItem);
   757             this.freeSECItem(saltItem);
   758         }
   759     },
   760 };

mercurial