├── test ├── api.html ├── okbrowser.html ├── okbrowser.js ├── hwcrypto.test.js ├── backend.html ├── backend.test.js └── api.js ├── .gitignore ├── CONTRIBUTING.md ├── .travis.yml ├── package.json ├── snippets ├── toolbar.html └── test.html ├── Makefile ├── bower.json ├── LICENSE ├── hex2base.js ├── README.md ├── Gruntfile.js ├── demo └── sign.html ├── hwcrypto.js └── src └── hwcrypto.js /test/api.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/okbrowser.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /bower_components 3 | /node_modules 4 | /gh-pages 5 | /dist 6 | /build 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please read the [common contributing guidelines](https://github.com/open-eid/org/blob/master/CONTRIBUTING.md) before you continue! 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.12" 5 | 6 | install: 7 | - npm install 8 | 9 | before_script: 10 | - bower install 11 | 12 | script: 13 | - grunt 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hwcrypto", 3 | "main": "hwcrypto.js", 4 | "version": "0.0.9", 5 | "repository": { 6 | "type": "git", 7 | "url": "http://github.com/open-eid/hwcrypto.js.git" 8 | }, 9 | "devDependencies": { 10 | "bower": "^1.3.12", 11 | "grunt": "^0.4.5", 12 | "grunt-bower": "^0.18.0", 13 | "grunt-cli": "^0.1.13", 14 | "grunt-contrib-clean": "^0.6.0", 15 | "grunt-contrib-connect": "^0.9.0", 16 | "grunt-contrib-jshint": "^0.11.0", 17 | "grunt-contrib-uglify": "^0.8.0", 18 | "grunt-include-replace": "^3.0.0", 19 | "grunt-mocha": "^0.4.12", 20 | "grunt-sync-pkg": "^0.1.2", 21 | "mocha-phantomjs": "^3.5.3", 22 | "phantomjs": "^1.9.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /snippets/toolbar.html: -------------------------------------------------------------------------------- 1 |

This is a 2 | test page for 3 | hwcrypto.js (more information) 4 | implementing API v0.1 5 |

6 |

7 | Switch to: 8 | HTTP | HTTPS 9 | | browser with module | browser without module | success 10 |

11 |
12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | buildit: 2 | npm install 3 | bower install 4 | grunt 5 | 6 | publish: 7 | # make sure versions are in sync 8 | grunt sync 9 | # only publish commited code 10 | test -z "`git status -s`" 11 | # check out Github pages if not already 12 | test -d gh-pages || git clone git@github.com:open-eid/hwcrypto.js.git -b gh-pages gh-pages 13 | # make sure it is clean 14 | (cd gh-pages && git reset --hard && git clean -dfx && git rm -rf *) 15 | # run Grunt (includes tests) 16 | grunt 17 | # Have Git version available in a JS file 18 | echo "var publishedGitVersion='`git describe --tags --always`';" > dist/gitversion.js 19 | # copy built pages 20 | mv dist/* gh-pages 21 | # push to github pages 22 | (cd gh-pages && git add * && git commit --amend -m "publish" && git push -f origin gh-pages) 23 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hwcrypto", 3 | "main": [ 4 | "hwcrypto.js" 5 | ], 6 | "version": "0.0.9", 7 | "authors": [ 8 | "Martin Paljak " 9 | ], 10 | "description": "Polyfill for window.hwcrypto for signing with smart card extensions", 11 | "moduleType": [ 12 | "globals" 13 | ], 14 | "keywords": [ 15 | "smartcard" 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/open-eid/hwcrypto.js.git", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/open-eid/hwcrypto.js.git" 22 | }, 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "test", 28 | "tests" 29 | ], 30 | "devDependencies": { 31 | "mocha": "~2.1.0", 32 | "chai": "~2.1.0", 33 | "jquery": "~1.11.2", 34 | "should": "~5.2.0", 35 | "chai-as-promised": "~4.3.0", 36 | "bind-polyfill": "~1.0.0" 37 | }, 38 | "dependencies": { 39 | "native-promise-only": "~0.7.6-a", 40 | "js-polyfills": "~0.1.2" 41 | } 42 | } -------------------------------------------------------------------------------- /snippets/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Paljak, Estonian Information System Authority 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/okbrowser.js: -------------------------------------------------------------------------------- 1 | describe('window.hwcrypto', function(){ 2 | mocha.ui('bdd'); 3 | var expect = chai.expect; 4 | var should = chai.should(); 5 | 6 | it('should exist after script inclusion', function () { 7 | expect(window.hwcrypto).to.be.a('object'); 8 | }); 9 | it('should not have extra properties', function() { 10 | var okprops = ["use", "debug", "getCertificate", "sign", "NO_IMPLEMENTATION", "USER_CANCEL", "NOT_ALLOWED", "NO_CERTIFICATES", "TECHNICAL_ERROR", "INVALID_ARGUMENT"].sort(); 11 | var props = Object.keys(window.hwcrypto).sort(); 12 | expect(props).to.have.members(okprops); 13 | }); 14 | 15 | describe('debugging capabilities', function(){ 16 | it('should have debug() method', function() { 17 | expect(window.hwcrypto).itself.to.respondTo('debug'); 18 | }); 19 | it('should always succeed', function() { 20 | return window.hwcrypto.debug().should.eventually.be.a('string'); 21 | }); 22 | it('should always contain "hwcrypto"', function() { 23 | return window.hwcrypto.debug().should.eventually.contain('hwcrypto'); 24 | }); 25 | }); 26 | 27 | describe('.getCertificate()', function(){ 28 | it('should be rejected without backend', function(){ 29 | return window.hwcrypto.getCertificate({}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 30 | }); 31 | }); 32 | describe('.sign()', function(){ 33 | it('should be rejected without backend', function(){ 34 | return window.hwcrypto.sign({}, {}, {}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/hwcrypto.test.js: -------------------------------------------------------------------------------- 1 | describe('window.hwcrypto', function(){ 2 | var expect = chai.expect; 3 | it('should exist after script inclusion', function () { 4 | expect(window.hwcrypto).to.be.a('object'); 5 | }); 6 | it('should not have a version property', function() { 7 | expect(window.hwcrypto).not.have.a.property('version'); 8 | }); 9 | it('should not have a getVersion() method', function() { 10 | expect(window.hwcrypto).itself.not.to.respondTo('getVersion'); 11 | }); 12 | it('should have a getCertificate() method', function() { 13 | expect(window.hwcrypto).itself.to.respondTo('getCertificate'); 14 | }); 15 | it('should have a sign() method', function() { 16 | expect(window.hwcrypto).itself.to.respondTo('sign'); 17 | }); 18 | 19 | describe('backend selection', function() { 20 | it('should have use() method', function() { 21 | expect(window.hwcrypto).itself.to.respondTo('use'); 22 | }); 23 | }); 24 | 25 | describe('debugging capabilities', function(){ 26 | it('should have debug() method', function() { 27 | expect(window.hwcrypto).itself.to.respondTo('debug'); 28 | }); 29 | it('should always succeed', function() { 30 | return window.hwcrypto.debug().should.eventually.be.a('string'); 31 | }); 32 | it('should always contain "hwcrypto"', function() { 33 | return window.hwcrypto.debug().should.eventually.contain('hwcrypto'); 34 | }); 35 | }); 36 | 37 | describe('.getCertificate()', function(){ 38 | it('should be rejected without backend', function(){ 39 | return window.hwcrypto.getCertificate({}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 40 | }); 41 | }); 42 | describe('.sign()', function(){ 43 | it('should be rejected without backend', function(){ 44 | return window.hwcrypto.sign({}, {}, {}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 45 | }); 46 | }); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /test/backend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 37 | 38 | 39 | 45 | 46 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /hex2base.js: -------------------------------------------------------------------------------- 1 | // FIXME: origin unknown 2 | if (!window.atob) { 3 | var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 4 | var table = tableStr.split(""); 5 | 6 | window.atob = function (base64) { 7 | if (/(=[^=]+|={3,})$/.test(base64)) throw new Error("String contains an invalid character"); 8 | base64 = base64.replace(/=/g, ""); 9 | var n = base64.length & 3; 10 | if (n === 1) throw new Error("String contains an invalid character"); 11 | for (var i = 0, j = 0, len = base64.length / 4, bin = []; i < len; ++i) { 12 | var a = tableStr.indexOf(base64[j++] || "A"), b = tableStr.indexOf(base64[j++] || "A"); 13 | var c = tableStr.indexOf(base64[j++] || "A"), d = tableStr.indexOf(base64[j++] || "A"); 14 | if ((a | b | c | d) < 0) throw new Error("String contains an invalid character"); 15 | bin[bin.length] = ((a << 2) | (b >> 4)) & 255; 16 | bin[bin.length] = ((b << 4) | (c >> 2)) & 255; 17 | bin[bin.length] = ((c << 6) | d) & 255; 18 | }; 19 | return String.fromCharCode.apply(null, bin).substr(0, bin.length + n - 4); 20 | }; 21 | 22 | window.btoa = function (bin) { 23 | for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) { 24 | var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++); 25 | if ((a | b | c) > 255) throw new Error("String contains an invalid character"); 26 | base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] + 27 | (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) + 28 | (isNaN(b + c) ? "=" : table[c & 63]); 29 | } 30 | return base64.join(""); 31 | }; 32 | 33 | } 34 | 35 | function hexToBase64(str) { 36 | return btoa(String.fromCharCode.apply(null, 37 | str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")) 38 | ); 39 | } 40 | 41 | function hexToPem(s) { 42 | var b = hexToBase64(s); 43 | var pem = b.match(/.{1,64}/g).join("\n"); 44 | return "-----BEGIN CERTIFICATE-----\n" + pem + "\n-----END CERTIFICATE-----"; 45 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hwcrypto.js 2 | > Browser JavaScript interface for signing with hardware tokens 3 | 4 | * [![Bower version](https://badge.fury.io/bo/hwcrypto.svg)](http://bower.io/search/?q=hwcrypto) 5 | * [![Build Status](https://travis-ci.org/open-eid/hwcrypto.js.svg?branch=master)](https://travis-ci.org/open-eid/hwcrypto.js) 6 | 7 | 8 | ## Get started 9 | 10 | The easiest way for managing JavaScript-s on a website is with [Bower](http://bower.io/): 11 | 12 | $ bower install --save hwcrypto 13 | 14 | Alternatively you can download the files from [release area](https://github.com/open-eid/hwcrypto.js/releases). 15 | 16 | `hwcrypto.js` itself does not do much, it depends on trusted platform code (installed separately and often running outside of the browser) to do the heavy lifting. 17 | 18 | ## Features 19 | 20 | Version 0.0.9 has built-in support for: 21 | - [NPAPI style synchronous plugins for Firefox, Safari and IE](https://github.com/open-eid/browser-token-signing) 22 | - [Chrome messaging extension](https://github.com/open-eid/chrome-token-signing) 23 | 24 | Supports all [latest browsers](http://browsehappy.com/): 25 | - Chrome 40+ 26 | - Firefox 27 | - IE 8+* 28 | - Safari 29 | 30 | *Support for IE8-IE10 requires a Promises polyfill; IE8 and IE9 also require TypedArray polyfill. Complimentary code is bundled into `hwcrypto-legacy.js`: 31 | - https://github.com/inexorabletash/polyfill (license: Public Domain / MIT) 32 | - https://github.com/getify/native-promise-only/ (license: MIT) 33 | 34 | Distribution and installation of the necessary platform components is out of scope of this project. 35 | 36 | For further instructions on how to use the interface please have a look at [API specification](https://github.com/open-eid/hwcrypto.js/wiki/ModernAPI) 37 | 38 | For background information about the project and the eID web task force, please head to the [wiki](https://github.com/open-eid/hwcrypto.js/wiki#eid-web-tf) 39 | 40 | ## Support 41 | 42 | For any bugs in the JavaScript component, please open an issue on Github. 43 | 44 | ## ChangeLog 45 | - 0.0.9 46 | - Have only typed arrays and promises in the legacy helper 47 | - 0.0.8 48 | - Make internal API also asynchronous, to work with old IE-s 49 | - Have a convenience-bundle `hwcrypto-legacy.js` 50 | -------------------------------------------------------------------------------- /test/backend.test.js: -------------------------------------------------------------------------------- 1 | describe('window.hwcrypto', function() { 2 | // Do automatic detection. 3 | // window.hwcrypto.autodetect(); 4 | 5 | describe('.getVersion()', function() { 6 | it('getVersion should always be fulfilled as string that contains a dot', function () { 7 | this.timeout(0); 8 | return window.hwcrypto.getVersion().should.eventually.contain('.'); 9 | }); 10 | }); 11 | 12 | if (location.protocol === 'http:') { 13 | describe('.getCertficate()', function() { 14 | it('should be rejected with "not_allowed" if run from http site', function(){ 15 | return window.hwcrypto.getCertificate().should.be.rejectedWith(Error, "not_allowed"); 16 | }); 17 | }); 18 | describe('.sign()', function () { 19 | it('should reject invocation with arbitrary parameters with "not_allowed" if run from http site', function() { 20 | return window.hwcrypto.sign("test", "test").should.be.rejectedWith(Error, "not_allowed"); 21 | }); 22 | }); 23 | } else { 24 | var chosenOne = null; 25 | describe('.getCertficate()', function() { 26 | it('should be rejected with no_certificates if none listed by extension', function() { 27 | this.timeout(0); 28 | window.alert('Make sure no card is inserted and press "OK" (Windows) or "Select" in an empty dialog without certificates if asked'); 29 | return window.hwcrypto.getCertificate().should.be.rejectedWith(Error, "no_certificates"); 30 | }); 31 | it('should be rejected with user_cancel if cancelled by user', function() { 32 | this.timeout(0); 33 | window.alert('Insert card and press "cancel" if/when presented with certificate selection dialog'); 34 | return window.hwcrypto.getCertificate().should.be.rejectedWith(Error, "user_cancel"); 35 | }); 36 | it('should be resolved with a certificate', function() { 37 | this.timeout(0); 38 | window.alert('Select your certificate and press "OK"'); 39 | return window.hwcrypto.getCertificate().should.be.fulfilled.then(function(result){ 40 | chosenOne = result; 41 | result.should.have.length.above(200); 42 | }); 43 | }); 44 | 45 | }); 46 | describe('.sign()', function() { 47 | it('should be rejected with INVALID_ARGUMENT if called with no arguments', function() { 48 | return window.hwcrypto.sign().should.be.rejectedWith(Error, "invalid_argument"); 49 | }); 50 | it('should be rejected with INVALID_ARGUMENT if called with null/null', function() { 51 | return window.hwcrypto.sign(null, null).should.be.rejectedWith(Error, "invalid_argument"); 52 | }); 53 | }); 54 | } 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | /* jshint expr: true */ 2 | describe('window.hwcrypto', function(){ 3 | mocha.ui('bdd'); 4 | var expect = chai.expect; 5 | var should = chai.should(); 6 | 7 | it('should exist after script inclusion', function () { 8 | expect(window.hwcrypto).to.be.a('object'); 9 | }); 10 | it('should not have a version property', function() { 11 | expect(window.hwcrypto).not.have.a.property('version'); 12 | }); 13 | it('should not have a getVersion() method', function() { 14 | expect(window.hwcrypto).itself.not.to.respondTo('getVersion'); 15 | }); 16 | it('should have a getCertificate() method', function() { 17 | expect(window.hwcrypto).itself.to.respondTo('getCertificate'); 18 | }); 19 | it('should have a sign() method', function() { 20 | expect(window.hwcrypto).itself.to.respondTo('sign'); 21 | }); 22 | it('should have NO_IMPLEMENTATION constant', function() { 23 | expect(window.hwcrypto.NO_IMPLEMENTATION).to.equal('no_implementation'); 24 | }); 25 | it('should have INVALID_ARGUMENT constant', function() { 26 | expect(window.hwcrypto.INVALID_ARGUMENT).to.equal('invalid_argument'); 27 | }); 28 | it('should have NO_CERTIFICATES constant', function() { 29 | expect(window.hwcrypto.NO_CERTIFICATES).to.equal('no_certificates'); 30 | }); 31 | it('should have NOT_ALLOWED constant', function() { 32 | expect(window.hwcrypto.NOT_ALLOWED).to.equal('not_allowed'); 33 | }); 34 | it('should have USER_CANCEL constant', function() { 35 | expect(window.hwcrypto.USER_CANCEL).to.equal('user_cancel'); 36 | }); 37 | it('should have TECHNICAL_ERROR constant', function() { 38 | expect(window.hwcrypto.TECHNICAL_ERROR).to.equal('technical_error'); 39 | }); 40 | it('should not have extra properties', function() { 41 | var okprops = ["use", "debug", "getCertificate", "sign", "NO_IMPLEMENTATION", "USER_CANCEL", "NOT_ALLOWED", "NO_CERTIFICATES", "TECHNICAL_ERROR", "INVALID_ARGUMENT"].sort(); 42 | var props = Object.keys(window.hwcrypto).sort(); 43 | expect(props).to.have.members(okprops); 44 | }); 45 | describe('backend selection', function() { 46 | it('should have use() method', function() { 47 | expect(window.hwcrypto).itself.to.respondTo('use'); 48 | }); 49 | it('use() method should always return true with auto argument', function() { 50 | return window.hwcrypto.use('auto').should.eventually.be.true; 51 | }); 52 | }); 53 | 54 | describe('debugging capabilities', function(){ 55 | it('should have debug() method', function() { 56 | expect(window.hwcrypto).itself.to.respondTo('debug'); 57 | }); 58 | it('should always succeed', function() { 59 | return window.hwcrypto.debug().should.eventually.be.a('string'); 60 | }); 61 | it('should always contain "hwcrypto"', function() { 62 | return window.hwcrypto.debug().should.eventually.contain('hwcrypto'); 63 | }); 64 | }); 65 | 66 | describe('.getCertificate()', function(){ 67 | it('should be rejected with no arguments', function(){ 68 | return window.hwcrypto.getCertificate().should.be.rejectedWith(Error, window.hwcrypto.INVALID_ARGUMENT); 69 | }); 70 | it('should be rejected without backend', function(){ 71 | return window.hwcrypto.getCertificate({}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 72 | }); 73 | }); 74 | describe('.sign()', function(){ 75 | it('should be rejected with no arguments', function(){ 76 | return window.hwcrypto.sign().should.be.rejectedWith(Error, window.hwcrypto.INVALID_ARGUMENT); 77 | }); 78 | it('should be rejected without backend', function(){ 79 | return window.hwcrypto.sign({}, {type: "SHA-1", hex: "3132"}, {}).should.be.rejectedWith(Error, window.hwcrypto.NO_IMPLEMENTATION); 80 | }); 81 | }); 82 | }); 83 | 84 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | uglify: { 5 | options: { 6 | banner: '/*! This is hwcrypto.js <%= pkg.version %> generated on <%= grunt.template.today("yyyy-mm-dd") %> */\n/* DO NOT EDIT (use src/hwcrypto.js) */\n', 7 | }, 8 | minify: { 9 | src: 'build/hwcrypto.js', 10 | dest: 'dist/<%= pkg.name %>.min.js' 11 | }, 12 | beautify: { 13 | options: { 14 | beautify: true, 15 | mangle: false, 16 | compress: false 17 | }, 18 | src: 'build/hwcrypto.js', 19 | dest: 'dist/hwcrypto.js' 20 | }, 21 | release: { 22 | options: { 23 | beautify: true, 24 | mangle: false, 25 | compress: false 26 | }, 27 | src: 'build/hwcrypto.js', 28 | dest: 'hwcrypto.js' 29 | }, 30 | legacy: { 31 | options: { 32 | banner: '/* Legacy dependencies for hwcrypto.js <%= pkg.version %> generated on <%= grunt.template.today("yyyy-mm-dd") %> */\n' 33 | }, 34 | files: { 35 | 'dist/hwcrypto-legacy.js': ['bower_components/js-polyfills/typedarray.js', 36 | 'bower_components/native-promise-only/lib/npo.src.js'] 37 | } 38 | } 39 | }, 40 | jshint: { 41 | src: { 42 | src: ['src/hwcrypto.js', 'test/*.js'], 43 | }, 44 | release: { 45 | src: ['hwcrypto.js'] 46 | } 47 | }, 48 | includereplace: { 49 | build: { 50 | options: { 51 | globals: { 52 | hwcryptoversion: '<%= pkg.version %>' 53 | } 54 | }, 55 | files: [ 56 | {src: 'hwcrypto.js', dest: 'build/', expand: true, cwd: 'src'} 57 | ] 58 | }, 59 | dist: { 60 | options: { 61 | prefix: '', 63 | includesDir: 'snippets/' 64 | }, 65 | files: [ 66 | {src: 'sign.html', dest: 'dist/', expand: true, cwd: 'demo'}, 67 | {src: '*.html', dest: 'dist/', expand: true, cwd: 'test'}, 68 | {src: '*.js', dest: 'dist/', expand: true, cwd: 'test'}, 69 | {src: 'hex2base.js', dest: 'dist/'} 70 | ] 71 | } 72 | }, 73 | mocha: { 74 | test: { 75 | src: ['dist/api.html'], 76 | options: { 77 | run: true, 78 | }, 79 | }, 80 | }, 81 | connect: { 82 | server: { 83 | options: { 84 | keepalive: true, 85 | port: 8888, 86 | open: 'http://localhost:8888/test/okbrowser.html' 87 | } 88 | } 89 | }, 90 | bower: { 91 | build: { 92 | dest: 'dist', 93 | js_dest: 'dist/js', 94 | css_dest: 'dist/css' 95 | } 96 | }, 97 | clean: ['build', 'dist'] 98 | }); 99 | // Minification 100 | grunt.loadNpmTasks('grunt-contrib-uglify'); 101 | // code check 102 | grunt.loadNpmTasks('grunt-contrib-jshint'); 103 | // development server 104 | grunt.loadNpmTasks('grunt-contrib-connect'); 105 | // testing 106 | grunt.loadNpmTasks('grunt-mocha'); 107 | // version number syncing before releasing 108 | grunt.loadNpmTasks('grunt-sync-pkg'); 109 | // file templates 110 | grunt.loadNpmTasks('grunt-include-replace'); 111 | // copy bower components 112 | grunt.loadNpmTasks('grunt-bower'); 113 | // Clean up 114 | grunt.loadNpmTasks('grunt-contrib-clean'); 115 | 116 | // Default task(s). 117 | grunt.registerTask('build', ['clean', 'jshint:src', 'includereplace', 'uglify:minify', 'uglify:beautify']); 118 | grunt.registerTask('dist', ['sync', 'build', 'bower', 'uglify:legacy']); 119 | grunt.registerTask('default', ['dist', 'mocha']); 120 | grunt.registerTask('release', ['sync', 'build', 'includereplace:build', 'uglify:release', 'jshint:release']) 121 | }; 122 | -------------------------------------------------------------------------------- /demo/sign.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | hwcrypto.js 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 73 | 74 | 75 | 76 | 85 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /hwcrypto.js: -------------------------------------------------------------------------------- 1 | /*! This is hwcrypto.js 0.0.8 generated on 2015-04-08 */ 2 | /* DO NOT EDIT (use src/hwcrypto.js) */ 3 | var hwcrypto = function hwcrypto() { 4 | "use strict"; 5 | console.log("hwcrypto.js activated"); 6 | function hasPluginFor(mime) { 7 | if (navigator.mimeTypes && mime in navigator.mimeTypes) { 8 | return true; 9 | } 10 | return false; 11 | } 12 | function hasExtensionFor(cls) { 13 | if (typeof window[cls] === "function") return true; 14 | return false; 15 | } 16 | function _hex2array(str) { 17 | if (typeof str == "string") { 18 | var ret = new Uint8Array(Math.floor(str.length / 2)); 19 | var i = 0; 20 | str.replace(/(..)/g, function(str) { 21 | ret[i++] = parseInt(str, 16); 22 | }); 23 | return ret; 24 | } 25 | } 26 | function _array2hex(args) { 27 | var ret = ""; 28 | for (var i = 0; i < args.length; i++) ret += (args[i] < 16 ? "0" : "") + args[i].toString(16); 29 | return ret.toLowerCase(); 30 | } 31 | function _mimeid(mime) { 32 | return "hwc" + mime.replace("/", "").replace("-", ""); 33 | } 34 | function loadPluginFor(mime) { 35 | var element = _mimeid(mime); 36 | if (document.getElementById(element)) { 37 | console.log("Plugin element already loaded"); 38 | return document.getElementById(element); 39 | } 40 | console.log("Loading plugin for " + mime + " into " + element); 41 | var objectTag = ''; 42 | var div = document.createElement("div"); 43 | div.setAttribute("id", "pluginLocation" + element); 44 | document.body.appendChild(div); 45 | document.getElementById("pluginLocation" + element).innerHTML = objectTag; 46 | return document.getElementById(element); 47 | } 48 | var digidoc_mime = "application/x-digidoc"; 49 | var digidoc_chrome = "TokenSigning"; 50 | var USER_CANCEL = "user_cancel"; 51 | var NO_CERTIFICATES = "no_certificates"; 52 | var INVALID_ARGUMENT = "invalid_argument"; 53 | var TECHNICAL_ERROR = "technical_error"; 54 | var NO_IMPLEMENTATION = "no_implementation"; 55 | var NOT_ALLOWED = "not_allowed"; 56 | function probe() { 57 | var msg = "probe() detected "; 58 | if (hasExtensionFor(digidoc_chrome)) { 59 | console.log(msg + digidoc_chrome); 60 | } 61 | if (hasPluginFor(digidoc_mime)) { 62 | console.log(msg + digidoc_mime); 63 | } 64 | } 65 | window.addEventListener("load", function(event) { 66 | probe(); 67 | }); 68 | function DigiDocPlugin() { 69 | this._name = "NPAPI/BHO for application/x-digidoc"; 70 | var p = loadPluginFor(digidoc_mime); 71 | var certificate_ids = {}; 72 | function code2str(err) { 73 | console.log("Error: " + err + " with: " + p.errorMessage); 74 | switch (parseInt(err)) { 75 | case 1: 76 | return USER_CANCEL; 77 | 78 | case 2: 79 | return INVALID_ARGUMENT; 80 | 81 | case 17: 82 | return INVALID_ARGUMENT; 83 | 84 | case 19: 85 | return NOT_ALLOWED; 86 | 87 | default: 88 | console.log("Unknown error: " + err + " with: " + p.errorMessage); 89 | return TECHNICAL_ERROR; 90 | } 91 | } 92 | function code2err(err) { 93 | return new Error(code2str(err)); 94 | } 95 | this.check = function() { 96 | return new Promise(function(resolve, reject) { 97 | setTimeout(function() { 98 | resolve(typeof p.version !== "undefined"); 99 | }, 0); 100 | }); 101 | }; 102 | this.getVersion = function() { 103 | return new Promise(function(resolve, reject) { 104 | var v = p.version; 105 | resolve(v); 106 | }); 107 | }; 108 | this.getCertificate = function(options) { 109 | if (options && options.lang) { 110 | p.pluginLanguage = options.lang; 111 | } 112 | return new Promise(function(resolve, reject) { 113 | try { 114 | var v = p.getCertificate(); 115 | if (parseInt(p.errorCode) !== 0) { 116 | reject(code2err(p.errorCode)); 117 | } else { 118 | certificate_ids[v.cert] = v.id; 119 | resolve({ 120 | hex: v.cert 121 | }); 122 | } 123 | } catch (ex) { 124 | console.log(ex); 125 | reject(code2err(p.errorCode)); 126 | } 127 | }); 128 | }; 129 | this.sign = function(cert, hash, options) { 130 | return new Promise(function(resolve, reject) { 131 | var cid = certificate_ids[cert.hex]; 132 | if (cid) { 133 | try { 134 | var language = options.lang || "en"; 135 | var v = p.sign(cid, hash.hex, language); 136 | resolve({ 137 | hex: v 138 | }); 139 | } catch (ex) { 140 | console.log(JSON.stringify(ex)); 141 | reject(code2err(p.errorCode)); 142 | } 143 | } else { 144 | console.log("invalid certificate: " + cert); 145 | reject(new Error(INVALID_ARGUMENT)); 146 | } 147 | }); 148 | }; 149 | } 150 | function DigiDocExtension() { 151 | this._name = "Chrome native messaging extension"; 152 | var p = null; 153 | this.check = function() { 154 | return new Promise(function(resolve, reject) { 155 | if (!hasExtensionFor(digidoc_chrome)) { 156 | return resolve(false); 157 | } 158 | p = new window[digidoc_chrome](); 159 | if (p) { 160 | resolve(true); 161 | } else { 162 | resolve(false); 163 | } 164 | }); 165 | }; 166 | this.getVersion = function() { 167 | return p.getVersion(); 168 | }; 169 | this.getCertificate = function(options) { 170 | return p.getCertificate(options); 171 | }; 172 | this.sign = function(cert, hash, options) { 173 | return p.sign(cert, hash, options); 174 | }; 175 | } 176 | function NoBackend() { 177 | this._name = "No implementation"; 178 | this.check = function() { 179 | return new Promise(function(resolve, reject) { 180 | resolve(true); 181 | }); 182 | }; 183 | this.getVersion = function() { 184 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 185 | }; 186 | this.getCertificate = function() { 187 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 188 | }; 189 | this.sign = function() { 190 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 191 | }; 192 | } 193 | var _backend = null; 194 | var fields = {}; 195 | function _testAndUse(Backend) { 196 | return new Promise(function(resolve, reject) { 197 | var b = new Backend(); 198 | b.check().then(function(isLoaded) { 199 | if (isLoaded) { 200 | console.log("Using backend: " + b._name); 201 | _backend = b; 202 | resolve(true); 203 | } else { 204 | console.log(b._name + " check() failed"); 205 | resolve(false); 206 | } 207 | }); 208 | }); 209 | } 210 | function _autodetect(force) { 211 | return new Promise(function(resolve, reject) { 212 | console.log("Autodetecting best backend"); 213 | if (typeof force === "undefined") { 214 | force = false; 215 | } 216 | if (_backend !== null && !force) { 217 | return resolve(true); 218 | } 219 | function tryDigiDocPlugin() { 220 | _testAndUse(DigiDocPlugin).then(function(result) { 221 | if (result) { 222 | resolve(true); 223 | } else { 224 | resolve(_testAndUse(NoBackend)); 225 | } 226 | }); 227 | } 228 | if (navigator.userAgent.indexOf("MSIE") != -1 || navigator.userAgent.indexOf("Trident") != -1) { 229 | console.log("Assuming IE BHO, testing"); 230 | return tryDigiDocPlugin(); 231 | } 232 | if (navigator.userAgent.indexOf("Chrome") != -1 && hasExtensionFor(digidoc_chrome)) { 233 | _testAndUse(DigiDocExtension).then(function(result) { 234 | if (result) { 235 | resolve(true); 236 | } else { 237 | tryDigiDocPlugin(); 238 | } 239 | }); 240 | return; 241 | } 242 | if (hasPluginFor(digidoc_mime)) { 243 | return tryDigiDocPlugin(); 244 | } 245 | resolve(_testAndUse(NoBackend)); 246 | }); 247 | } 248 | fields.use = function(backend) { 249 | return new Promise(function(resolve, reject) { 250 | if (typeof backend === "undefined" || backend === "auto") { 251 | _autodetect().then(function(result) { 252 | resolve(result); 253 | }); 254 | } else { 255 | if (backend === "chrome") { 256 | resolve(_testAndUse(DigiDocExtension)); 257 | } else if (backend === "npapi") { 258 | resolve(_testAndUse(DigiDocPlugin)); 259 | } else { 260 | resolve(false); 261 | } 262 | } 263 | }); 264 | }; 265 | fields.debug = function() { 266 | return new Promise(function(resolve, reject) { 267 | var hwversion = "hwcrypto.js 0.0.8"; 268 | _autodetect().then(function(result) { 269 | _backend.getVersion().then(function(version) { 270 | resolve(hwversion + " with " + _backend._name + " " + version); 271 | }, function(error) { 272 | resolve(hwversion + " with failing backend " + _backend._name); 273 | }); 274 | }); 275 | }); 276 | }; 277 | fields.getCertificate = function(options) { 278 | if (typeof options !== "object") { 279 | console.log("getCertificate options parameter must be an object"); 280 | return Promise.reject(new Error(INVALID_ARGUMENT)); 281 | } 282 | if (options && !options.lang) { 283 | options.lang = "en"; 284 | } 285 | return _autodetect().then(function(result) { 286 | if (location.protocol !== "https:" && location.protocol !== "file:") { 287 | return Promise.reject(new Error(NOT_ALLOWED)); 288 | } 289 | return _backend.getCertificate(options).then(function(certificate) { 290 | if (certificate.hex && !certificate.encoded) certificate.encoded = _hex2array(certificate.hex); 291 | return certificate; 292 | }); 293 | }); 294 | }; 295 | fields.sign = function(cert, hash, options) { 296 | if (arguments.length < 2) return Promise.reject(new Error(INVALID_ARGUMENT)); 297 | if (options && !options.lang) { 298 | options.lang = "en"; 299 | } 300 | if (!hash.type || !hash.value && !hash.hex) return Promise.reject(new Error(INVALID_ARGUMENT)); 301 | if (hash.hex && !hash.value) { 302 | console.log("DEPRECATED: hash.hex as argument to sign() is deprecated, use hash.value instead"); 303 | hash.value = _hex2array(hash.hex); 304 | } 305 | if (hash.value && !hash.hex) hash.hex = _array2hex(hash.value); 306 | return _autodetect().then(function(result) { 307 | if (location.protocol !== "https:" && location.protocol !== "file:") { 308 | return Promise.reject(new Error(NOT_ALLOWED)); 309 | } 310 | return _backend.sign(cert, hash, options).then(function(signature) { 311 | if (signature.hex && !signature.value) signature.value = _hex2array(signature.hex); 312 | return signature; 313 | }); 314 | }); 315 | }; 316 | fields.NO_IMPLEMENTATION = NO_IMPLEMENTATION; 317 | fields.USER_CANCEL = USER_CANCEL; 318 | fields.NOT_ALLOWED = NOT_ALLOWED; 319 | fields.NO_CERTIFICATES = NO_CERTIFICATES; 320 | fields.TECHNICAL_ERROR = TECHNICAL_ERROR; 321 | fields.INVALID_ARGUMENT = INVALID_ARGUMENT; 322 | return fields; 323 | }(); -------------------------------------------------------------------------------- /src/hwcrypto.js: -------------------------------------------------------------------------------- 1 | // JavaScript library as described in 2 | // https://github.com/open-eid/hwcrypto.js 3 | var hwcrypto = (function hwcrypto() { 4 | 'use strict'; 5 | console.log("hwcrypto.js activated"); 6 | 7 | // Returns "true" if a plugin is present for the MIME 8 | function hasPluginFor(mime) { 9 | if(navigator.mimeTypes && mime in navigator.mimeTypes) { 10 | return true; 11 | } 12 | return false; 13 | } 14 | // Checks if a function is present (used for Chrome) 15 | function hasExtensionFor(cls) { 16 | return typeof window[cls] === 'function'; 17 | } 18 | 19 | function _hex2array(str) { 20 | if(typeof str == 'string') { 21 | var len = Math.floor(str.length / 2); 22 | var ret = new Uint8Array(len); 23 | for (var i = 0; i < len; i++) { 24 | ret[i] = parseInt(str.substr(i * 2, 2), 16); 25 | } 26 | return ret; 27 | } 28 | } 29 | 30 | function _array2hex(args) { 31 | var ret = ""; 32 | for(var i = 0; i < args.length; i++) ret += (args[i] < 16 ? "0" : "") + args[i].toString(16); 33 | return ret.toLowerCase(); 34 | } 35 | 36 | function _mimeid(mime) { 37 | return "hwc" + mime.replace('/', '').replace('-', ''); 38 | } 39 | 40 | function loadPluginFor(mime) { 41 | var element = _mimeid(mime); 42 | if(document.getElementById(element)) { 43 | console.log("Plugin element already loaded"); 44 | return document.getElementById(element); 45 | } 46 | console.log('Loading plugin for ' + mime + ' into ' + element); 47 | // Must insert tag as string (not as an Element object) so that IE9 can access plugin methods 48 | var objectTag = ''; 49 | var div = document.createElement("div"); 50 | div.setAttribute("id", 'pluginLocation' + element); 51 | document.body.appendChild(div); 52 | // Must not manipulate body's innerHTML directly, otherwise previous Element references get lost 53 | div.innerHTML = objectTag; 54 | return document.getElementById(element); 55 | } 56 | // Important constants 57 | var digidoc_mime = 'application/x-digidoc'; 58 | var digidoc_chrome = 'TokenSigning'; 59 | // Some error strings 60 | var USER_CANCEL = "user_cancel"; 61 | var NO_CERTIFICATES = "no_certificates"; 62 | var INVALID_ARGUMENT = "invalid_argument"; 63 | var TECHNICAL_ERROR = "technical_error"; 64 | var NO_IMPLEMENTATION = "no_implementation"; 65 | var NOT_ALLOWED = "not_allowed"; 66 | // Probe all existing backends in a failsafe manner. 67 | function probe() { 68 | var msg = 'probe() detected '; 69 | // First try Chrome extensions 70 | if(hasExtensionFor(digidoc_chrome)) { 71 | console.log(msg + digidoc_chrome); 72 | } 73 | if(hasPluginFor(digidoc_mime)) { 74 | console.log(msg + digidoc_mime); 75 | } 76 | } 77 | // TODO: remove 78 | // There's a timeout because chrome content script needs to be loaded 79 | (window.addEventListener || window.attachEvent).call(window, 'load', probe, false); 80 | 81 | // Backend for DigiDoc plugin 82 | function DigiDocPlugin() { 83 | this._name = "NPAPI/BHO for application/x-digidoc"; 84 | var p = loadPluginFor(digidoc_mime); 85 | // keeps track of detected certificates and their ID-s 86 | var certificate_ids = {}; 87 | 88 | function code2str(err) { 89 | console.log("Error: " + err + " with: " + p.errorMessage); 90 | switch(parseInt(err)) { 91 | case 1: 92 | return USER_CANCEL; 93 | case 2: 94 | return INVALID_ARGUMENT; 95 | case 17: 96 | // invalid hash length 97 | return INVALID_ARGUMENT; 98 | case 19: 99 | return NOT_ALLOWED; 100 | default: 101 | console.log("Unknown error: " + err + " with: " + p.errorMessage); 102 | return TECHNICAL_ERROR; 103 | } 104 | } 105 | 106 | function code2err(err) { 107 | return new Error(code2str(err)); 108 | } 109 | this.check = function() { 110 | return new Promise(function(resolve, reject) { 111 | // IE8 cannot access the newly inserted plugin object before the end of call queue 112 | setTimeout(function() { 113 | resolve(typeof p.version !== "undefined"); 114 | }, 0); 115 | }); 116 | }; 117 | this.getVersion = function() { 118 | return new Promise(function(resolve, reject) { 119 | var v = p.version; 120 | resolve(v); 121 | }); 122 | }; 123 | this.getCertificate = function(options) { 124 | // Ignore everything except language 125 | if(options && options.lang) { 126 | p.pluginLanguage = options.lang; 127 | } 128 | return new Promise(function(resolve, reject) { 129 | try { 130 | var v = p.getCertificate(); 131 | if(parseInt(p.errorCode) !== 0) { 132 | reject(code2err(p.errorCode)); 133 | } else { 134 | // Store plugin-internal ID 135 | certificate_ids[v.cert] = v.id; 136 | resolve({ 137 | hex: v.cert 138 | }); 139 | } 140 | } catch(ex) { 141 | console.log(ex); 142 | reject(code2err(p.errorCode)); 143 | } 144 | }); 145 | }; 146 | this.sign = function(cert, hash, options) { 147 | return new Promise(function(resolve, reject) { 148 | // get the ID of the certificate 149 | var cid = certificate_ids[cert.hex]; 150 | if(cid) { 151 | try { 152 | //var v = p.sign(cid, hash, 'en'); // FIXME: only BHO requires language but does not use it 153 | var language = options.lang || 'en'; 154 | //p.pluginLanguage = language; 155 | var v = p.sign(cid, hash.hex, language); 156 | resolve({ 157 | hex: v 158 | }); 159 | } catch(ex) { 160 | console.log(JSON.stringify(ex)); 161 | reject(code2err(p.errorCode)); 162 | } 163 | } else { 164 | console.log("invalid certificate: " + cert); 165 | reject(new Error(INVALID_ARGUMENT)); 166 | } 167 | }); 168 | }; 169 | } 170 | // Backend for Digidoc Chrome Extension 171 | function DigiDocExtension() { 172 | this._name = "Chrome native messaging extension"; 173 | var p = null; 174 | this.check = function() { 175 | return new Promise(function(resolve, reject) { 176 | if (!hasExtensionFor(digidoc_chrome)) { 177 | return resolve(false); 178 | } 179 | // FIXME: remove this from content script! 180 | p = new window[digidoc_chrome](); 181 | if (p) { 182 | resolve(true); 183 | } else { 184 | resolve(false); 185 | } 186 | }); 187 | }; 188 | this.getVersion = function() { 189 | return p.getVersion(); 190 | }; 191 | this.getCertificate = function(options) { 192 | return p.getCertificate(options); 193 | }; 194 | this.sign = function(cert, hash, options) { 195 | return p.sign(cert, hash, options); 196 | }; 197 | } 198 | 199 | // Dummy 200 | function NoBackend() { 201 | this._name = "No implementation"; 202 | this.check = function() { 203 | return new Promise(function(resolve, reject) { 204 | resolve(true); 205 | }); 206 | }; 207 | this.getVersion = function() { 208 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 209 | }; 210 | this.getCertificate = function() { 211 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 212 | }; 213 | this.sign = function() { 214 | return Promise.reject(new Error(NO_IMPLEMENTATION)); 215 | }; 216 | } 217 | // Active backend 218 | var _backend = null; 219 | // To be exposed 220 | var fields = {}; 221 | 222 | function _testAndUse(Backend) { 223 | return new Promise(function(resolve, reject) { 224 | var b = new Backend(); 225 | b.check().then(function(isLoaded) { 226 | if (isLoaded) { 227 | console.log("Using backend: " + b._name); 228 | _backend = b; 229 | resolve(true); 230 | } else { 231 | console.log(b._name + " check() failed"); 232 | resolve(false); 233 | } 234 | }); 235 | }); 236 | } 237 | 238 | function _autodetect(force) { 239 | return new Promise(function(resolve, reject) { 240 | console.log("Autodetecting best backend"); 241 | if (typeof force === 'undefined') { 242 | force = false; 243 | } 244 | if (_backend !== null && !force) { 245 | return resolve(true); 246 | } 247 | 248 | function tryDigiDocPlugin() { 249 | _testAndUse(DigiDocPlugin).then(function(result) { 250 | if (result) { 251 | resolve(true); 252 | } else { 253 | resolve(_testAndUse(NoBackend)); 254 | } 255 | }); 256 | } 257 | 258 | // IE BHO 259 | if (navigator.userAgent.indexOf("MSIE") != -1 || navigator.userAgent.indexOf("Trident") != -1) { 260 | console.log("Assuming IE BHO, testing"); 261 | return tryDigiDocPlugin(); 262 | } 263 | 264 | // Chrome extension or NPAPI 265 | if (navigator.userAgent.indexOf("Chrome") != -1 && hasExtensionFor(digidoc_chrome)) { 266 | _testAndUse(DigiDocExtension).then(function(result) { 267 | if (result) { 268 | resolve(true); 269 | } else { 270 | tryDigiDocPlugin(); 271 | } 272 | }); 273 | return; 274 | } 275 | 276 | // Other browsers with NPAPI support 277 | if (hasPluginFor(digidoc_mime)) { 278 | return tryDigiDocPlugin(); 279 | } 280 | 281 | // No backend supported 282 | resolve(_testAndUse(NoBackend)); 283 | }); 284 | } 285 | // Use a specific backend or autodetect 286 | fields.use = function(backend) { 287 | return new Promise(function(resolve, reject) { 288 | if (typeof backend === "undefined" || backend === 'auto') { 289 | _autodetect().then(function(result) { resolve(result);}); 290 | } else { 291 | if (backend === "chrome") { 292 | resolve(_testAndUse(DigiDocExtension)); 293 | } else if (backend === "npapi") { 294 | resolve(_testAndUse(DigiDocPlugin)); 295 | } else { 296 | resolve(false); // unknown backend 297 | } 298 | } 299 | }); 300 | }; 301 | // Give debugging information. 302 | fields.debug = function() { 303 | return new Promise(function(resolve, reject) { 304 | var hwversion = "hwcrypto.js @@hwcryptoversion"; 305 | _autodetect().then(function(result) { 306 | _backend.getVersion().then(function(version) { 307 | resolve(hwversion + " with " + _backend._name + " " + version); 308 | }, function(error) { 309 | resolve(hwversion + " with failing backend " + _backend._name); 310 | }); 311 | }); 312 | }); 313 | }; 314 | // Get a certificate 315 | fields.getCertificate = function(options) { 316 | if(typeof options !== 'object') { 317 | console.log("getCertificate options parameter must be an object"); 318 | return Promise.reject(new Error(INVALID_ARGUMENT)); 319 | } 320 | // If options does not specify a language, set to 'en' 321 | if(options && !options.lang) { 322 | options.lang = 'en'; 323 | } 324 | return _autodetect().then(function(result) { 325 | // FIXME: dummy security check in website context 326 | if (location.protocol !== 'https:' && location.protocol !== 'file:') { 327 | return Promise.reject(new Error(NOT_ALLOWED)); 328 | } 329 | return _backend.getCertificate(options).then(function(certificate) { 330 | // Add binary value as well 331 | if (certificate.hex && !certificate.encoded) 332 | certificate.encoded = _hex2array(certificate.hex); 333 | return certificate; 334 | }); 335 | }); 336 | }; 337 | // Sign a hash 338 | fields.sign = function(cert, hash, options) { 339 | if (arguments.length < 2) 340 | return Promise.reject(new Error(INVALID_ARGUMENT)); 341 | // If options does not specify a language, set to 'en' 342 | if(options && !options.lang) { 343 | options.lang = 'en'; 344 | } 345 | // Hash type and value must be present 346 | if (!hash.type || (!hash.value && !hash.hex)) 347 | return Promise.reject(new Error(INVALID_ARGUMENT)); 348 | 349 | // Convert Hash to hex and vice versa. 350 | // TODO: All backends currently expect the presence of Hex. 351 | if (hash.hex && !hash.value) { 352 | console.log("DEPRECATED: hash.hex as argument to sign() is deprecated, use hash.value instead"); 353 | hash.value = _hex2array(hash.hex); 354 | } 355 | if (hash.value && !hash.hex) 356 | hash.hex = _array2hex(hash.value); 357 | 358 | return _autodetect().then(function(result) { 359 | // FIXME: dummy security check in website context 360 | if(location.protocol !== 'https:' && location.protocol !== 'file:') { 361 | return Promise.reject(new Error(NOT_ALLOWED)); 362 | } 363 | return _backend.sign(cert, hash, options).then(function(signature) { 364 | // Add binary value as well 365 | // TODO: all backends return hex currently 366 | if (signature.hex && !signature.value) 367 | signature.value = _hex2array(signature.hex); 368 | return signature; 369 | }); 370 | }); 371 | }; 372 | // Constants for errors 373 | fields.NO_IMPLEMENTATION = NO_IMPLEMENTATION; 374 | fields.USER_CANCEL = USER_CANCEL; 375 | fields.NOT_ALLOWED = NOT_ALLOWED; 376 | fields.NO_CERTIFICATES = NO_CERTIFICATES; 377 | fields.TECHNICAL_ERROR = TECHNICAL_ERROR; 378 | fields.INVALID_ARGUMENT = INVALID_ARGUMENT; 379 | return fields; 380 | }()); 381 | --------------------------------------------------------------------------------