UNPKG

undersign

Version:

Create eIDAS compatible XAdES digital signatures with certificate OCSP responses and timestamps. Works with the Estonian Id-card and Mobile-Id to create BDOCs, but isn't limited to Estonia.

168 lines (145 loc) 5.34 kB
var _ = require("../lib/underscore") var Certificate = require("./certificate") var StandardError = require("standard-error") var FetchError = require("fetch-error") var fetchDefaults = require("fetch-defaults") var URL = "https://mid.sk.ee/mid-api/" exports = module.exports = MobileId exports.MobileIdError = MobileIdError exports.confirmation = confirmation function MobileId(url, opts) { if (typeof url != "string") { opts = url; url = null } this.user = opts.user this.password = opts.password this.fetch = fetchDefaults(this.fetch, url || URL) } MobileId.prototype.fetch = fetchDefaults(require("./fetch"), { timeout: 20 * 1000, headers: {"Accept": "application/json"} }) MobileId.prototype.request = function(url, opts) { return this.fetch(url, "json" in opts ? _.defaults({ json: _.assign({ relyingPartyName: this.user, relyingPartyUUID: this.password }, opts.json) }, opts) : opts) } MobileId.prototype.readCertificate = function(phoneNumber, personalId) { return this.request("certificate", { method: "POST", json: {phoneNumber: phoneNumber, nationalIdentityNumber: personalId} }).then(parse, handleFetchError).then((obj) => ( new Certificate(parseBase64(obj.cert)) )) } MobileId.prototype.authenticate = function( phoneNumber, personalId, signableHash, opts ) { // The authentication endpoint seems to always return a session id. It's only // when requesting the session status do you get errors. return this.request("authentication", { method: "POST", json: _.assign({ phoneNumber: phoneNumber, nationalIdentityNumber: personalId, hash: signableHash.toString("base64"), hashType: "SHA256", language: "EST" }, opts) }).then((res) => res.body.sessionID, handleFetchError) } MobileId.prototype.sign = function( phoneNumber, personalId, signableHash, opts ) { // The signature endpoint seems to always return a session id. It's only when // requesting the session status do you get errors. return this.request("signature", { method: "POST", json: _.assign({ phoneNumber: phoneNumber, nationalIdentityNumber: personalId, hash: signableHash.toString("base64"), hashType: "SHA256", language: "EST" }, opts) }).then((res) => res.body.sessionID, handleFetchError) } MobileId.prototype.waitForAuthentication = function(sessionId, timeout) { var timeoutMs = timeout == null ? 90000 : timeout * 1000 var url = "authentication/session/" + sessionId + "?timeoutMs=" + timeoutMs var res = this.request(url, {timeout: timeoutMs + 5000}) return res.then(parse, handleFetchError).then((obj) => ( obj.state != "COMPLETE" ? null : [ new Certificate(parseBase64(obj.cert)), parseBase64(obj.signature.value) ] )) } MobileId.prototype.waitForSignature = function(sessionId, timeout) { var timeoutMs = timeout == null ? 90000 : timeout * 1000 var url = "signature/session/" + sessionId + "?timeoutMs=" + timeoutMs var res = this.request(url, {timeout: timeoutMs + 5000}) // The signature response doesn't include the certificate. return res.then(parse, handleFetchError).then((obj) => ( obj.state == "COMPLETE" ? parseBase64(obj.signature.value) : null )) } exports.demo = new MobileId("https://tsp.demo.sk.ee/mid-api/", { user: "DEMO", password: "00000000-0000-0000-0000-000000000000" }) function MobileIdError(code, msg, props) { this.code = code StandardError.call(this, msg, props) if (this.response) Object.defineProperty(this, "response", {enumerable: !!0}) } MobileIdError.prototype = Object.create(StandardError.prototype, { constructor: {value: MobileIdError, configurable: true, writeable: true} }) // https://github.com/SK-EID/MID#24-verification-code // 6 bits from the beginning of the hash and 7 from the end, then concatenated. function confirmation(signableHash) { return ( ((signableHash[0] & 0b11111100) << 5) + (signableHash[signableHash.length - 1] & 0b01111111) ) } // https://github.com/SK-EID/MID/wiki/Test-number-for-automated-testing-in-DEMO var ERROR_MESSAGES = exports.ERROR_MESSAGES = { // Certificate errors: // https://github.com/SK-EID/MID#317-possible-result-values NOT_FOUND: "Person is not a Mobile-Id user or personal id mismatch", NOT_ACTIVE: "Person hasn't activated their certificates", // Signing errors: // https://github.com/SK-EID/MID#338-session-end-result-codes TIMEOUT: "Person did not respond in time", NOT_MID_CLIENT: "Person hasn't activated their certificates", USER_CANCELLED: "Person cancelled", SIGNATURE_HASH_MISMATCH: "Mobile-Id certificate differs from service provider's", PHONE_ABSENT: "Phone is unavailable", DELIVERY_ERROR: "Failed to send a message to the phone", SIM_ERROR: "SIM application error" } function parse(res) { if (res.body.state == "RUNNING" || res.body.result == "OK") return res.body var code = res.body.result throw new MobileIdError(code, ERROR_MESSAGES[code] || code, {response: res}) } function handleFetchError(err) { if (err instanceof FetchError && err.response) { var res = err.response var msg = res.body && res.body.error || res.statusMessage var props = {response: res} if (err.code == 400) throw new MobileIdError("BAD_REQUEST", msg, props) if (err.code == 401) throw new MobileIdError("UNAUTHORIZED", msg, props) } throw err } function parseBase64(base64) { return Buffer.from(base64, "base64") }