├── LICENSE.md ├── README.md ├── contentscript.js ├── cs-square.png ├── hooker.js ├── key-green.png ├── key-orange.png ├── key-red.png ├── manifest.json ├── popup.css ├── popup.html └── popup.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, CRYPTOSENSE SAS 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Cryptosense Key Inspector 2 | (C) Cryptosense 2014, see LICENSE.md 3 | 4 | A very simple chromium extension that traces calls to the W3C WebCrypto API. Both keys and calls can be exported in JSON format. 5 | 6 | Send feedback, bugs, comments, questions to webcrypto@cryptosense.com 7 | 8 | 9 | ##Absence from Chrome Web Store 10 | 11 | We're releasing this little extension in source code only to find out 12 | if it corresponds to the expectations of the community. We will 13 | submit this extension to the Chrome Web store by ourselves when it is 14 | ready. Please don't do it yourself. 15 | 16 | Versions for other browsers will follow as their implementations of 17 | the API become available. 18 | 19 | ##Usage 20 | 21 | To install, go to "chrome://extensions" and choose 22 | "Load unpacked extension...", then select the extension directory. 23 | 24 | The extension will run on each page you visit. You can see the current 25 | keys by clicking the extension icon. A green key icon indicates a key 26 | using a secure algorithm (in the sense of "secure for future use" at 27 | http://cryptosense.com/choice-of-algorithms-in-the-w3c-crypto-api/ ), 28 | an orange key means an algorithm secure for the moment but not 29 | approved for future use, and a red keys means an algorithm considered 30 | insecure. Attention - this does not mean that the key value itself is 31 | secure, or that the applications use of the algroithm is secure, 32 | etc. etc. 33 | 34 | You can download the JSON of current keys or the JSON of all function 35 | calls by clicking the corresponding button. 36 | 37 | ##Extra notes and issues 38 | 39 | This extension adds some data in the key to allow them to be more 40 | easily identified (ID, usage and creation date). 41 | 42 | It is possible that the overwrite of the standard W3C webcrypto API 43 | functions breaks the promise mechanism somewhat. 44 | 45 | Occasionally, if a script calls generate key immediately this 46 | executes before the function has been redefined so it escapes tracing. 47 | 48 | When viewing keys, the popup doesn't resize itself when extending a 49 | node and reducing it afterwards. If you know how to do this, tell us, 50 | or submit a patch! 51 | 52 | ##How it works 53 | 54 | * popup.js : The page displayed when clicking the extension icon, it 55 | manages the displaying and downloading of the differents required 56 | datas. 57 | 58 | * contentscript.js : Code that runs with each page but not in the same 59 | environment. It injects hooker.js on each page and receives the 60 | messages from the popup. 61 | 62 | * hooker.js : The code that polyfills the standard W3C WebCrypto API 63 | functions. 64 | -------------------------------------------------------------------------------- /contentscript.js: -------------------------------------------------------------------------------- 1 | // Cryptosense Key Inspector (c) Cryptosense 2014 see LICENSE.md 2 | 3 | // add hook into each page 4 | 5 | sessionStorage.clear(); 6 | 7 | var b = document.createElement('script'); 8 | b.src = chrome.extension.getURL('hooker.js'); 9 | (document.head||document.documentElement).appendChild(b); 10 | b.onload = function() { 11 | b.parentNode.removeChild(b); 12 | }; 13 | 14 | 15 | function get_keys() { 16 | var cstContent = []; 17 | for(i = 0; i < sessionStorage.length && sessionStorage["key" +i]; ++i) 18 | { 19 | cstContent[i] = sessionStorage["key"+i]; 20 | } 21 | return cstContent; 22 | }; 23 | 24 | function save_cst() { 25 | var path = window.location.pathname; 26 | var sfile = path.substring(path.lastIndexOf("/")+1); 27 | var ffile = sfile.substring(0, sfile.lastIndexOf(".")) + ".cst"; 28 | var cstContent = ""; 29 | for(i = 0; i < sessionStorage.length && sessionStorage[i]; ++i) 30 | cstContent += sessionStorage[i.toString()]; 31 | var cstBlob = new Blob([cstContent],{type : 'text/json'}); 32 | var link = document.createElement("a"); 33 | var url = window.URL.createObjectURL(cstBlob); 34 | link.setAttribute("href", url); 35 | link.setAttribute("download", ffile); 36 | link.click(); 37 | window.URL.revokeObjectURL(url) 38 | return "ok"; 39 | }; 40 | 41 | 42 | chrome.runtime.onMessage.addListener( 43 | function(request, sender, senderResponse) 44 | { 45 | if (request == "get_keys") 46 | senderResponse(get_keys ()); 47 | else if (request == "save_cst") 48 | senderResponse(save_cst()); 49 | else 50 | senderResponse("error"); 51 | } 52 | ); 53 | -------------------------------------------------------------------------------- /cs-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptosense/w3c-crypto-inspector/02c0eba01b6867748e1651c9a59ed945d2d7241c/cs-square.png -------------------------------------------------------------------------------- /hooker.js: -------------------------------------------------------------------------------- 1 | // Cryptosense Key Inspector (C) Cryptosense 2014 see LICENSE.md 2 | 3 | // Hook the WebCryptoAPI 4 | 5 | 6 | // Array of the different functions, used to obtain the index of 7 | // the function in key.usage array. 8 | 9 | var keyFct = ["encrypt","decrypt","sign","verify", 10 | "deriveKey","deriveBits","exportKey","wrapKey", "unwrapKey"] 11 | 12 | var AtoHexa = function (a) { 13 | return ("0x" + 14 | Array.prototype.map.call( 15 | a, 16 | function(x){ 17 | x = x.toString(16); 18 | x = ("00"+x).substr(-2); 19 | return x; 20 | }).join('').toUpperCase()); 21 | }; 22 | 23 | var ABtoHexa = function (a){ 24 | var tmp = new Uint8Array(a); 25 | return AtoHexa(tmp) 26 | }; 27 | 28 | // Return unique ID for keys. 29 | var getKeyId = (function(){ 30 | var i = 0; 31 | return (function() {return (i++);}); 32 | })(); 33 | 34 | // Save each function call in sessionstorage. 35 | var saveElem = (function (){ 36 | var k = 0; 37 | return function(elem) 38 | { 39 | sessionStorage[k] = JSON.stringify(elem, null, ' '); 40 | k += 1; 41 | }; 42 | })(); 43 | 44 | // Formating algorithmIdentifier 45 | var getAlgorithmIdentifier = function(a) 46 | { 47 | if (a) 48 | { 49 | var algorithm = new Object(); 50 | if (a.name) 51 | algorithm.name = a.name.toUpperCase(); 52 | if (a.iv) 53 | algorithm.iv = ABtoHexa(a.iv); 54 | if(a.hash) 55 | algorithm.hash = a.hash; 56 | if (a.length) 57 | algorithm.length = a.length; 58 | return algorithm; 59 | } 60 | }; 61 | 62 | // Save keys in sessionStorage 63 | function saveKey(key) 64 | { 65 | sessionStorage["key" + key.id] = JSON.stringify(key, null, ' '); 66 | } 67 | 68 | // Edit the used field of a key. 69 | function editKey(key, action) 70 | { 71 | key.used[keyFct.indexOf(action)] += 1; 72 | saveKey(key); 73 | } 74 | 75 | // Add a new key, which could be a key pair. 76 | function addKey(elem) 77 | { 78 | function extendKey(key) 79 | { 80 | var date = (new Date()).toISOString(); 81 | key.creationDate = date; 82 | key.id = getKeyId(); 83 | key.used = [0,0,0,0,0,0,0,0,0]; 84 | saveKey(key); 85 | }; 86 | if (elem.publicKey != null) 87 | { 88 | extendKey(elem.privateKey); 89 | extendKey(elem.publicKey); 90 | } 91 | else 92 | extendKey(elem); 93 | } 94 | 95 | // Function called when an error occured in one of the overwrite functions 96 | function error(ret) 97 | { 98 | return function(err){ 99 | ret.error = err; 100 | saveElem(ret); 101 | return Promise.reject(err) 102 | }; 103 | } 104 | 105 | // Called on success of an overwrite function that returns standard data 106 | function output(ret) 107 | { 108 | return function(a){ 109 | ret.output = a; 110 | saveElem(ret); 111 | return Promise.resolve(a) 112 | }; 113 | } 114 | 115 | // Called on success of an overwrite function that returns raw data 116 | function outputABtoHexa(ret) 117 | { 118 | return function(a){ 119 | ret.output = ABtoHexa(a); 120 | saveElem(ret); 121 | return Promise.resolve(a) 122 | }; 123 | } 124 | 125 | // Called on success of an overwrite function that returns raw data 126 | function outputab2str(ret) 127 | { 128 | return function(a){ 129 | ret.output = ab2str(a); 130 | saveElem(ret); 131 | return Promise.resolve(a) 132 | }; 133 | } 134 | 135 | // Called on success of an overwrite function that returns a key 136 | function outputKey(ret) 137 | { 138 | return function(a){ 139 | addKey(a); 140 | ret.output = a; 141 | saveElem(ret); 142 | return Promise.resolve(a) 143 | }; 144 | }; 145 | 146 | // Overwrite getRandomValues 147 | (function() { 148 | var proxied = crypto.getRandomValues; 149 | crypto.getRandomValues = function() { 150 | var ret = new Object(); 151 | ret.command = "getRandomValues"; 152 | ret.input = arguments; 153 | ret.output = proxied.apply(this, arguments); 154 | saveElem(ret); 155 | return ret.output; 156 | }; 157 | })(); 158 | 159 | function ab2str(buf) { 160 | return String.fromCharCode.apply(null, new Uint16Array(buf)); 161 | } 162 | 163 | function str2ab(str) { 164 | var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char 165 | var bufView = new Uint16Array(buf); 166 | for (var i=0, strLen=str.length; i"], 7 | "js": ["contentscript.js"], 8 | "run_at": "document_start" 9 | }], 10 | "permissions": [ 11 | "tabs" 12 | ], 13 | "web_accessible_resources": ["hooker.js"], 14 | "browser_action" : 15 | { 16 | "default_icon" : "cs-square.png", 17 | "default_popup": "popup.html" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | /* Cryptosense Key Inspector (C) Cryptosense 2014 */ 2 | body { 3 | background-color: #ededed; 4 | margin: 0; 5 | padding: 0; 6 | font-family: 'Open Sans', sans-serif; 7 | width:25em; 8 | } 9 | 10 | h2 { 11 | text-align:center; 12 | margin:0.2em; 13 | } 14 | 15 | .save { 16 | margin:0; 17 | text-align:center; 18 | } 19 | 20 | .key-image { 21 | width :1em; 22 | height:1em; 23 | } 24 | 25 | .key { 26 | margin: 0.2em 0; 27 | border:solid; 28 | border-width:0.1em; 29 | border-color:brown; 30 | } 31 | 32 | .keyTitle { 33 | margin:0; 34 | display:block; 35 | font-size:1em; 36 | } 37 | 38 | .algorithm { 39 | font-weight:bold; 40 | } 41 | 42 | .keyContent { 43 | font-size:0.8em; 44 | } 45 | 46 | p { 47 | margin: 0; 48 | margin-left:2em; 49 | padding: 0; 50 | } 51 | 52 | ul { 53 | margin :0; 54 | padding:0; 55 | margin-left:2em; 56 | } 57 | 58 | li { 59 | margin-left:2em; 60 | } 61 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Cryptosense key inspector

8 |

9 | 10 | 11 |

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | //Cryptosense Key Inspector (C) Cryptosense 2014 2 | 3 | // The popup window for displaying the current keys 4 | 5 | // Algorithms deprecated even for legacy use 6 | var deprecated_legacy = ["RSAES-PKCS1-v1_5"] 7 | // Algorithms deprecated for future applications. 8 | var deprecated_future = ["RSASSA-PKCS1-v1_5","ECDSA","AES-KW"] 9 | 10 | var keyFct = ["encrypt","decrypt","sign","verify", 11 | "deriveKey","deriveBits","exportKey","wrapKey", "unwrapKey"] 12 | 13 | 14 | // Html format one key 15 | function get_formated_key(elem) { 16 | var key = document.createElement("div"); 17 | key.className = "key"; 18 | 19 | // The key icon 20 | var img; 21 | if (deprecated_legacy.indexOf(elem.algorithm.name) != -1) 22 | img = "key-red.png"; 23 | else if (deprecated_future.indexOf(elem.algorithm.name) != -1) 24 | img = "key-orange.png"; 25 | else 26 | img = "key-green.png"; 27 | 28 | // Extractable information 29 | var extractable = ", not extractable." 30 | if (elem.extractable) { 31 | extractable = ", extractable."; 32 | } 33 | 34 | // Usages information 35 | var usage = '

Usages :'; 36 | elem.usages.forEach( 37 | function (e) { 38 | usage += ' ' + e; 39 | } 40 | ); 41 | if (usage == '

Usages :') 42 | usage += " none" 43 | 44 | // Length 45 | var length; 46 | if (elem.algorithm.length != null) 47 | length = elem.algorithm.length; 48 | else 49 | length = elem.algorithm.modulusLength; 50 | 51 | // Hash information 52 | var h = ""; 53 | if (elem.algorithm.hash != null) 54 | hash = '

Hash name : '+ elem.algorithm.hash.name + '.

'; 55 | else 56 | hash = ""; 57 | var keyContent = 58 | '

' + 59 | ' ' + elem.algorithm.name + ' ' + 60 | elem.creationDate + '

' + '' + 61 | '

Type : ' + elem.type + ', length : ' + length + 62 | extractable + '

' + hash + usage + '
'; 63 | key.innerHTML = keyContent; 64 | 65 | // The used information. 66 | var used = document.createElement("span"); 67 | var usedTitle = document.createElement("p"); 68 | var usedContent = document.createElement("ul"); 69 | var totalUsed = elem.used.reduce(function (a, b) { 70 | return (a + b) 71 | }); 72 | if (totalUsed > 0) { 73 | var title; 74 | if (totalUsed == 1) 75 | title = " This key has been used once" 76 | else 77 | title = " This key has been used " + totalUsed + " times"; 78 | // Generating the complete usage information 79 | for (i = 0; i < elem.used.length; ++i) { 80 | if (elem.used[i] != 0) { 81 | usedContent.innerHTML += '
  • ' + keyFct[i] + ' : ' + elem.used[i] + 82 | ' times.
  • '; 83 | } 84 | } 85 | //display all usage information when clicking on the usage resume. 86 | usedTitle.innerText = String.fromCharCode(0x25b8) + title; 87 | usedContent.style.display = "none"; 88 | usedTitle.onclick = 89 | function (e) { 90 | if (usedContent.style.display == "none") { 91 | usedContent.style.display = "inline-block"; 92 | usedTitle.innerText = String.fromCharCode(0x25be) + title; 93 | } 94 | else { 95 | usedContent.style.display = "none"; 96 | usedTitle.innerText = String.fromCharCode(0x25b8) + title; 97 | } 98 | }; 99 | } 100 | else { 101 | usedTitle.innerText = "This key has not been used"; 102 | } 103 | used.appendChild(usedTitle); 104 | used.appendChild(usedContent); 105 | key.appendChild(used); 106 | return key; 107 | } 108 | 109 | // Generate and inject the html corresponding to each key 110 | function display_keys(response) { 111 | response.forEach(function (elem) { 112 | document.body.appendChild(get_formated_key(JSON.parse(elem))); 113 | }) 114 | } 115 | 116 | // Send message to the script running with the page. 117 | function send_to_page(str, fct) { 118 | chrome.tabs.query({ 119 | active: true, 120 | currentWindow: true 121 | }, 122 | function (tabs) { 123 | chrome.tabs.sendMessage(tabs[0].id, str, fct) 124 | }) 125 | } 126 | 127 | // Function to save the trace json 128 | function get_cst(fct) { 129 | send_to_page("get_keys", fct); 130 | } 131 | 132 | // Function to download a file, used for the key download. 133 | function download_file(name, t, content) { 134 | var cstBlob = new Blob([content], { 135 | type: t 136 | }); 137 | var link = document.createElement("a"); 138 | var url = window.URL.createObjectURL(cstBlob); 139 | link.setAttribute("href", url); 140 | link.setAttribute("download", name); 141 | link.click(); 142 | window.URL.revokeObjectURL(url) 143 | } 144 | 145 | // Function to download the key list. 146 | function save_json(response) { 147 | var content = ""; 148 | for (i = 0; i < response.length; ++i) { 149 | content += response[i]; 150 | } 151 | download_file("key.json", "text/json", content); 152 | } 153 | 154 | // Recover the key from page. 155 | window.onload = 156 | function (e) { 157 | get_cst(display_keys) 158 | }; 159 | 160 | // Bind the event save keys to its button 161 | window.document.body.querySelector(".key_save").onclick = 162 | function (e) { 163 | get_cst(save_json) 164 | }; 165 | 166 | // Bind the event save traces to its button 167 | window.document.body.querySelector(".trace_save").onclick = 168 | function (e) { 169 | send_to_page("save_cst", function (a) {}) 170 | }; 171 | --------------------------------------------------------------------------------