Skip to main content

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):

PrcDescriptionTaxG
20%NormalA
10%IntermédiareB
5.5%RéduitC
2.1%Super réduitD
0%ZeroE

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

CodeSeverityDescriptionExit
#FILENAMEabortfilename to be specified as start parameter2
#OPENabortcannot open file specified3
#FORMATerrorline is not a valid XML element1
#ERRORerrorunexpected runtime error1
#ES256errorincompatible input file ("alg":"ES256" expected)1
#ECerrorincompatible curve (NIST P-256 expected)1
#CHAINerror"Payload": predecessor signature mismatch1
#CERTIFICATEerror"_":"certificate" missing1
#PAYLOADerror"Payload": missing in line1
#SIGNATUREerror"Signature": missing in line1
#VERIFYerrorline signature verification failed1
#SIGNerror"Signature": invalid length (base64url 86 bytes)1
!CONTINUEDwarningfirst "Payload": with unknown predecessor signature0

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:

CodeNameDescription
#LICfile checksum errormay be caused by exception during operation or modification
#PARSEdata parsing errormay be caused by exception during operation
#AUDITaudit messageimportant selfcheck event
!INCRnumber not incrementalregarding SQ, TN or DN; possibly caused by operation exception
!DIFFrecalculation differencewithin transaction or against GT
?CONTINUEDchain continuationsignature chain continued from previous transactions
#CHAINchain brokenpossibly transaction missing in signature chain
#FORMATformat errorcannot verify signature
#VERIFYsignature invalidfor payload given using fiscal.pem
#HASHfile modifiedfile 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:

DescriptionABCRESPONSIBILITYIf efsta concerned
Generic requirements
Supplier requirements
Editor approachXXXEditor
Associated servicesXXXEditor
Associated documentsXXXEditor
DocumentationXXXEditor + efstaEFR Guide [FR]
Compliance management plan
Systems installedXXXEditor
Systems not installedXXXEditor
Identification in use of the systemsXXXEditor
Category A systems specificitiesXEditor
Accounting requirements for software
Accounting entries managementCXEditor
Accounting entries formatCXEditor
Periods managementXXXEditor
Sales Periods managementXXXEditor
Accounting periods managementCXEditor
Technical requirements for software
Technical event log (JET)XXXEditor + efstaAutomatically or /audit
Data change management - TraceabilityXXXEditor + efsta/repo
Traceability dataXXXEditor + efsta
Fiscal archives functionXXXefstaAutomatically (efsta cloud)
Fiscal archives managementXXXEditor + efsta
Fiscal archives dataXXXEditor + efsta
Sales data permanent retentionCCEditor + efstaCloud
Sales data purge managementXXXEditor + efstaAutomatically using a storage threshold
External retention on SASCCCefstaCloud
Software AdministrationXXXEditorefsta cloud
BackupXXXEditorNot needed (automatically)
Technical requirements for dematerialized data
Electronic recordXXXEditor + efsta
TraceabilityXXEditor + efsta
Control check-listXXXEditor + efsta
Data security
IntegrityXXXefstaChained signature
Records securityXXXefstaLocal HMAC (lic field)
Electronic signatureXXXefstaYes
Electronic signature data formatXXXefstaNIST P-256
Administrative control assistance
Accounting data displayCXefsta
Control assistanceXXefsta
POS systems
Records identification
Sales data identificationXXXEditor + efstaTL Transaction Location, TT Terminal, TN Number
Accounting entries identificationCXEditor + efsta
Continuous sequence processXXXEditor + efstaYes, internal SQ sequence number
Security method
Sales data securityXXXefstaAignature, lic
Fiscal archives securityXXXefstaCloud
Electronic signatureXXXefsta
External data securityXXXefsta
Data to be secured
Sales dataXXXEditor + efsta
Software administration dataXXXEditor + efsta
Operation data recordXXXEditor + efsta
Final closure operation dataXXXEditor + efsta
Ticket data recordXXXEditor + efsta
Invoice data recordCCXEditor + efsta
Duplicates data recordXXXEditor + efsta/reprint or /reprintcnt resp.
Periodic processes and operations
Cash drawer processCCCEditor + efstaNo, /audit can be registered
Periodic closing process (accounting)CCXEditor + efsta
Period closing processXXXEditor + efstaYes, /register NFS="CLO"
Accounting entries generation processCXEditor + efsta
Transfer to accounting systemsCXEditor + efsta
Special functionsCCCEditor
Generic processes
Traceability dataXXXEditor + efsta
ArchivingXXXefstaAutomatically (cloud)
Backup / restoreCCXefstaRestore not needed
Fiscal data transmissionXXXefstaProof, data export from cloud
Software Administration
Software administration dataXXXEditor + efsta
General settingsXXXEditor + efsta
Operating parametersXXXEditor + efsta
Management of supporting documents
Bill (temporary document)CCCEditor + efsta
Proof of paymentXXXEditor + efstae.g. /register NFS="VERSEMENT"
TicketXXXEditor + efsta/register
Ticket grand totalXXXEditor + efstaGrand totals are managed in EFR
Period grand total & monthly grand totalXXXEditor + efstaGenerated automatically
InvoiceCCCEditor + efsta/register DN="FACT"
Fiscal year grand totalCCCEditor + efstaAutomatically (each monthly GT contains totals of last 12 months)
Return and printing
Ticket returnXXXEditor + efsta
Summary reportsXXXEditor
Business recommendations
Business requirementsXXXEditor
Data collection
Official sales document
TicketXXXEditor + efsta/register
DuplicateXXXEditor + efsta/register DT="DUP"
Add-ons for the invoiceXXXEditor + efsta
Grand totalsXXXEditor + efsta
JET (Technical Event Log)XXXEditor + efsta
Electronic signature
Ticket & bill signatureXXXefsta
Duplicate signatureXXXefsta
Invoice signatureXXXefsta
Grand Totals signatureXXXefsta
JET events signatureXXXefsta
Archives signatureXXXefstaCloud

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++
}