Appendix
VAT Rates
Following rates are used by default, if only TaxG is delivered, but no tax rate in TaxA element (/EFR/app/FR/cfg/taxg.cfg):
Prc | Description | TaxG |
---|---|---|
20% | Normal | A |
10% | Intermédiare | B |
5.5% | Réduit | C |
2.1% | Super réduit | D |
0% | Zero | E |
Verification Tool "proof.js"
The program "proof.js" can be copied from /ProgramData/EFR/app/FR/web/proof.js or downloaded from http://localhost:5618/control/proof.js or from EFSTA Cloud Portal.
It verifies the content of data export files by:
- verifying signatures
- checking the signature chain per DataType
- check file hash
The program source is open and may be used in any way, including reimplementation. Please be aware, that source and program logic as well as data export format may change with a new version of EFR.
To test the effectiveness of proof.js use a text editor to manipulate a signature value in the input file – proof.js will detect signature and file hash invalidity.
proof.js error codes
Code | Severity | Description | Exit |
---|---|---|---|
#FILENAME | abort | filename to be specified as start parameter | 2 |
#OPEN | abort | cannot open file specified | 3 |
#FORMAT | error | line is not a valid XML element | 1 |
#ERROR | error | unexpected runtime error | 1 |
#ES256 | error | incompatible input file ("alg":"ES256" expected) | 1 |
#EC | error | incompatible curve (NIST P-256 expected) | 1 |
#CHAIN | error | "Payload": predecessor signature mismatch | 1 |
#CERTIFICATE | error | "_":"certificate" missing | 1 |
#PAYLOAD | error | "Payload": missing in line | 1 |
#SIGNATURE | error | "Signature": missing in line | 1 |
#VERIFY | error | line signature verification failed | 1 |
#SIGN | error | "Signature": invalid length (base64url 86 bytes) | 1 |
!CONTINUED | warning | first "Payload": with unknown predecessor signature | 0 |
Journal File Verification "proofjou.js"
This program is automatically contained in jou*.zip when an export of local journal archive is performed (alternatively run from /ProgramData/EFR/app/FR/web).
Command Syntax:
mydir> node proofjou.js [jouPath] [-expert]
Checks performed on.jou files:
- file checksum
- structural integrity
- gaplessness of sequence number SQ
- signature chain per DT
- signature verification against fiscal.cer
- in –expert mode analyzis of transaction amounts
- and gaplessness of TN, DN per DT
Following errors (#...) and warnings (!...) may be reported:
Code | Name | Description |
---|---|---|
#LIC | file checksum error | may be caused by exception during operation or modification |
#PARSE | data parsing error | may be caused by exception during operation |
#AUDIT | audit message | important selfcheck event |
!INCR | number not incremental | regarding SQ, TN or DN; possibly caused by operation exception |
!DIFF | recalculation difference | within transaction or against GT |
?CONTINUED | chain continuation | signature chain continued from previous transactions |
#CHAIN | chain broken | possibly transaction missing in signature chain |
#FORMAT | format error | cannot verify signature |
#VERIFY | signature invalid | for payload given using fiscal.pem |
#HASH | file modified | file content has been modified resulting in a hash mismatch |
Program exit code is 1 in case of errors.
Infocert Audit Workflow
Infocert certification of frontend application + EFR is performed using following rules of responsibility:
Description | A | B | C | RESPONSIBILITY | If efsta concerned |
---|---|---|---|---|---|
Generic requirements | |||||
Supplier requirements | |||||
Editor approach | X | X | X | Editor | |
Associated services | X | X | X | Editor | |
Associated documents | X | X | X | Editor | |
Documentation | X | X | X | Editor + efsta | EFR Guide [FR] |
Compliance management plan | |||||
Systems installed | X | X | X | Editor | |
Systems not installed | X | X | X | Editor | |
Identification in use of the systems | X | X | X | Editor | |
Category A systems specificities | X | Editor | |||
Accounting requirements for software | |||||
Accounting entries management | C | X | Editor | ||
Accounting entries format | C | X | Editor | ||
Periods management | X | X | X | Editor | |
Sales Periods management | X | X | X | Editor | |
Accounting periods management | C | X | Editor | ||
Technical requirements for software | |||||
Technical event log (JET) | X | X | X | Editor + efsta | Automatically or /audit |
Data change management - Traceability | X | X | X | Editor + efsta | /repo |
Traceability data | X | X | X | Editor + efsta | |
Fiscal archives function | X | X | X | efsta | Automatically (efsta cloud) |
Fiscal archives management | X | X | X | Editor + efsta | |
Fiscal archives data | X | X | X | Editor + efsta | |
Sales data permanent retention | C | C | Editor + efsta | Cloud | |
Sales data purge management | X | X | X | Editor + efsta | Automatically using a storage threshold |
External retention on SAS | C | C | C | efsta | Cloud |
Software Administration | X | X | X | Editor | efsta cloud |
Backup | X | X | X | Editor | Not needed (automatically) |
Technical requirements for dematerialized data | |||||
Electronic record | X | X | X | Editor + efsta | |
Traceability | X | X | Editor + efsta | ||
Control check-list | X | X | X | Editor + efsta | |
Data security | |||||
Integrity | X | X | X | efsta | Chained signature |
Records security | X | X | X | efsta | Local HMAC (lic field) |
Electronic signature | X | X | X | efsta | Yes |
Electronic signature data format | X | X | X | efsta | NIST P-256 |
Administrative control assistance | |||||
Accounting data display | C | X | efsta | ||
Control assistance | X | X | efsta | ||
POS systems | |||||
Records identification | |||||
Sales data identification | X | X | X | Editor + efsta | TL Transaction Location, TT Terminal, TN Number |
Accounting entries identification | C | X | Editor + efsta | ||
Continuous sequence process | X | X | X | Editor + efsta | Yes, internal SQ sequence number |
Security method | |||||
Sales data security | X | X | X | efsta | Aignature, lic |
Fiscal archives security | X | X | X | efsta | Cloud |
Electronic signature | X | X | X | efsta | |
External data security | X | X | X | efsta | |
Data to be secured | |||||
Sales data | X | X | X | Editor + efsta | |
Software administration data | X | X | X | Editor + efsta | |
Operation data record | X | X | X | Editor + efsta | |
Final closure operation data | X | X | X | Editor + efsta | |
Ticket data record | X | X | X | Editor + efsta | |
Invoice data record | C | C | X | Editor + efsta | |
Duplicates data record | X | X | X | Editor + efsta | /reprint or /reprintcnt resp. |
Periodic processes and operations | |||||
Cash drawer process | C | C | C | Editor + efsta | No, /audit can be registered |
Periodic closing process (accounting) | C | C | X | Editor + efsta | |
Period closing process | X | X | X | Editor + efsta | Yes, /register NFS="CLO" |
Accounting entries generation process | C | X | Editor + efsta | ||
Transfer to accounting systems | C | X | Editor + efsta | ||
Special functions | C | C | C | Editor | |
Generic processes | |||||
Traceability data | X | X | X | Editor + efsta | |
Archiving | X | X | X | efsta | Automatically (cloud) |
Backup / restore | C | C | X | efsta | Restore not needed |
Fiscal data transmission | X | X | X | efsta | Proof, data export from cloud |
Software Administration | |||||
Software administration data | X | X | X | Editor + efsta | |
General settings | X | X | X | Editor + efsta | |
Operating parameters | X | X | X | Editor + efsta | |
Management of supporting documents | |||||
Bill (temporary document) | C | C | C | Editor + efsta | |
Proof of payment | X | X | X | Editor + efsta | e.g. /register NFS="VERSEMENT" |
Ticket | X | X | X | Editor + efsta | /register |
Ticket grand total | X | X | X | Editor + efsta | Grand totals are managed in EFR |
Period grand total & monthly grand total | X | X | X | Editor + efsta | Generated automatically |
Invoice | C | C | C | Editor + efsta | /register DN="FACT" |
Fiscal year grand total | C | C | C | Editor + efsta | Automatically (each monthly GT contains totals of last 12 months) |
Return and printing | |||||
Ticket return | X | X | X | Editor + efsta | |
Summary reports | X | X | X | Editor | |
Business recommendations | |||||
Business requirements | X | X | X | Editor | |
Data collection | |||||
Official sales document | |||||
Ticket | X | X | X | Editor + efsta | /register |
Duplicate | X | X | X | Editor + efsta | /register DT="DUP" |
Add-ons for the invoice | X | X | X | Editor + efsta | |
Grand totals | X | X | X | Editor + efsta | |
JET (Technical Event Log) | X | X | X | Editor + efsta | |
Electronic signature | |||||
Ticket & bill signature | X | X | X | efsta | |
Duplicate signature | X | X | X | efsta | |
Invoice signature | X | X | X | efsta | |
Grand Totals signature | X | X | X | efsta | |
JET events signature | X | X | X | efsta | |
Archives signature | X | X | X | efsta | Cloud |
proof.js source
// proof.js
// ================================================================================================
'use strict'
log('proof.js', 'efsta export file proof utility 2018-03-30')
var file = process.argv[2]; if (!file) { log('#FILENAME', 'filename required'); process.exit(2) }
log('file', file)
try {
var input = require('fs').createReadStream(file, {encoding:'ascii'})
require('readline').createInterface({input:input}).on('line', fileLine).on('close', fileClose)
} catch (err) { log('#OPEN', err.toString()); process.exit(3) }
//-----------------------------------------------------------------------------------------------
// readline event handlers
const crypto = require('crypto')
var pem, hash = crypto.createHash('sha256'), chainDT = {}, recordsDT = {}, errors = 0
function fileLine(line) {
if (!/^\s*<(\/)?\w+>\s*$/.test(line)) try { // ignore XML root lines
function decode(value) { return value.replace(/&(.+);/g, function (m, code) { return {amp:'&',lt:'<',gt:'>',quot:'"',apos:"'" }[code]||m }) }
var chk = line.trim(), type = 'undefined', rec = {}, expect
chk = chk.replace(/\s+(\w+)="([^"]*)"/g, function(m, name, value) { rec[name] = decode(value); return '' })
chk = chk.replace(/<(\w+)\/>/g, function(m, name) { type = name; return '' })
if (chk) throw '#FORMAT' // line is not a valid XML element
switch (type) {
case 'certificate': // verification certificate
if (rec.alg != 'ES256') throw '#ES256' // alg:ES256 expected
var pattern = new Buffer('06082a8648ce3d030107', 'hex') // ASN.1: 1.2.840.10045.3.1.7 (NIST P-256)
if (new Buffer(rec.PublicKey, 'base64').indexOf(pattern) === -1) throw '#EC' // P-256 expected
pem = []; pem.push('-----BEGIN PUBLIC KEY-----')
for (var i = 0; i < rec.PublicKey.length; i += 64) pem.push(rec.PublicKey.slice(i, i + 64))
pem.push('-----END PUBLIC KEY-----'); pem = pem.join('\n')
break
case 'fis': // fiscal signature
expect = chainDT[rec.DT] ? ',O,' + chainDT[rec.DT] : ',N,'
chainDT[rec.DT] = rec.Signature
recordsDT[rec.DT] = (recordsDT[rec.DT] || 0) + 1
verify(rec)
if (',N,' == expect && /,O,.{86}$/.test(rec.Payload)) log('!CONTINUED', type, rec) // existing chain is continued (warning)
else if (rec.Payload.slice(-expect.length) != expect) log('#CHAIN', type, rec) // chain broken
break
case 'signature': // file signature
expect = 'sha256:' + hash.digest('base64'); hash = null
if (rec.Payload != expect) throw '#SHA256' // sha256 mismatch
verify(rec)
return
case 'readme': // readme
return // is ignored
default: // other lines are displayed
log(type, rec)
}
if (!hash) log('#OFF', type, rec) // line outside file signature
} catch (err) {
if (/^#/.test(err)) log(err, type, rec)
else log('#ERROR', err + ' ' + type, rec)// unexpected runtime error
}
if (hash) hash.update(line) // canonicalization: all except \r and \n, plain ASCII
}
function fileClose() { // end of file
log('records', recordsDT)
log('')
log('summary', errors ? errors + ' errors' : 'OK')
process.exit(errors ? 1 : 0)
}
//-----------------------------------------------------------------------------------------------
// helper functions
function verify(rec) { // signature verification
if (pem == '#') return true; if (!pem) { pem = '#'; throw '#CERTIFICATE' } // certificate missing
if (!rec.Payload) throw '#PAYLOAD' // payload missing
if (!rec.Signature) throw '#SIGNATURE' // signature missing
var verifyer = crypto.createVerify('sha256')
verifyer.update(rec.Payload)
var signature = convertToDer(rec.Signature)
if (!verifyer.verify(pem, signature)) throw '#VERIFY' // verification error
}
function convertToDer(signature) { // crypto requires DER encoded signature
const paramBytes = 32, MAX_OCTET = 128, ENCODED_TAG_SEQ = 48, ENCODED_TAG_INT = 2 // valid for alg:ES256
signature = new Buffer(signature.replace(/-/g, '+').replace(/_/g, '/'), 'base64')
if (signature.length !== paramBytes * 2) throw '#SIGN_LEN'
function countPadding(buf, start, stop) {
var ret = 0; while (start + ret < stop && buf[start + ret] === 0) { ret++ }
return (buf[start + ret] >= MAX_OCTET) ? --ret : ret
}
var rPadding = countPadding(signature, 0, paramBytes), sPadding = countPadding(signature, paramBytes, signature.length)
var rLength = paramBytes - rPadding, sLength = paramBytes - sPadding, rsBytes = 1 + 1 + rLength + 1 + 1 + sLength, isShort = rsBytes < MAX_OCTET
var ret = new Buffer((isShort ? 2 : 3) + rsBytes), off = 0
ret[off++] = ENCODED_TAG_SEQ
if (isShort) ret[off++] = rsBytes
else { ret[off++] = MAX_OCTET | 1; ret[off++] = rsBytes & 0xff }
ret[off++] = ENCODED_TAG_INT; ret[off++] = rLength
if (rPadding < 0) { ret[off++] = 0; off += signature.copy(ret, off, 0, paramBytes) }
else { off += signature.copy(ret, off, rPadding, paramBytes) }
ret[off++] = ENCODED_TAG_INT; ret[off++] = sLength
if (sPadding < 0) { ret[off++] = 0; signature.copy(ret, off, paramBytes) }
else { signature.copy(ret, off, paramBytes + sPadding) }
return ret
}
function log(par1, par2, par3) { // console output
function fmt(par) {
if ('object' === typeof par) return JSON.stringify(par).replace(/,"/g, ' "').replace(/[{"}]/g, '')
else return par||''
}
console.log(((par1 + ' ').substr(0, 11) + fmt(par2) + ' ' + fmt(par3)).substr(0, 119))
if (/^\#/.test(par1)) errors++
}