rewrite for eet 2.0 api version v4

This commit is contained in:
2026-10-13 23:28:55 +02:00
parent c926a173ba
commit 3f8e4796b3
55 changed files with 3698 additions and 1611 deletions
+427
View File
@@ -0,0 +1,427 @@
#include "eetca.h"
#include <QCryptographicHash>
#include <QEventLoop>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/crypto.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/provider.h>
#endif
namespace
{
struct SigningMaterial
{
EVP_PKEY *key{nullptr};
QByteArray certificateDer;
};
void setError(QString *error, const QString &message)
{
if (error != nullptr)
{
*error = message;
}
}
QByteArray base64Url(const QByteArray &data)
{
return data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
}
bool loadSigningMaterial(const QString &path, const QByteArray &password,
SigningMaterial *material, QString *error)
{
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
OSSL_PROVIDER_load(nullptr, "default");
OSSL_PROVIDER_load(nullptr, "legacy");
#endif
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
setError(error, QStringLiteral("Cannot read the CA signing PKCS#12 file."));
return false;
}
const QByteArray data = file.readAll();
BIO *bio = BIO_new_mem_buf(data.constData(), data.size());
PKCS12 *container = bio == nullptr ? nullptr : d2i_PKCS12_bio(bio, nullptr);
BIO_free(bio);
if (container == nullptr)
{
setError(error, QStringLiteral("The CA signing PKCS#12 file is invalid."));
return false;
}
EVP_PKEY *key = nullptr;
X509 *certificate = nullptr;
STACK_OF(X509) *chain = nullptr;
const bool parsed = PKCS12_parse(container, password.constData(), &key,
&certificate, &chain) == 1;
PKCS12_free(container);
sk_X509_pop_free(chain, X509_free);
if (!parsed || key == nullptr || certificate == nullptr)
{
EVP_PKEY_free(key);
X509_free(certificate);
setError(error, QStringLiteral("The CA signing PKCS#12 password is invalid."));
return false;
}
const int length = i2d_X509(certificate, nullptr);
if (length <= 0)
{
EVP_PKEY_free(key);
X509_free(certificate);
setError(error, QStringLiteral("The CA signing certificate is invalid."));
return false;
}
material->certificateDer.resize(length);
unsigned char *destination =
reinterpret_cast<unsigned char *>(material->certificateDer.data());
if (i2d_X509(certificate, &destination) != length)
{
EVP_PKEY_free(key);
X509_free(certificate);
material->certificateDer.clear();
setError(error, QStringLiteral("The CA signing certificate cannot be encoded."));
return false;
}
material->key = key;
X509_free(certificate);
return true;
}
QByteArray createJwt(const SigningMaterial &material, QString *error)
{
const QByteArray thumbprint = base64Url(
QCryptographicHash::hash(material.certificateDer, QCryptographicHash::Sha256));
const QByteArray header = QJsonDocument(QJsonObject{
{QStringLiteral("alg"), QStringLiteral("RS256")},
{QStringLiteral("typ"), QStringLiteral("JWT")},
{QStringLiteral("x5t#S256"), QString::fromLatin1(thumbprint)}
}).toJson(QJsonDocument::Compact);
const QByteArray payload = QByteArrayLiteral("{}");
const QByteArray signingInput = base64Url(header) + '.' + base64Url(payload);
EVP_MD_CTX *context = EVP_MD_CTX_new();
if (context == nullptr ||
EVP_DigestSignInit(context, nullptr, EVP_sha256(), nullptr, material.key) != 1 ||
EVP_DigestSignUpdate(context, signingInput.constData(), signingInput.size()) != 1)
{
EVP_MD_CTX_free(context);
setError(error, QStringLiteral("Cannot initialize CA JWT signing."));
return {};
}
size_t signatureSize = 0;
if (EVP_DigestSignFinal(context, nullptr, &signatureSize) != 1)
{
EVP_MD_CTX_free(context);
setError(error, QStringLiteral("Cannot determine CA JWT signature size."));
return {};
}
QByteArray signature(static_cast<qsizetype>(signatureSize), Qt::Uninitialized);
if (EVP_DigestSignFinal(context, reinterpret_cast<unsigned char *>(signature.data()),
&signatureSize) != 1)
{
EVP_MD_CTX_free(context);
setError(error, QStringLiteral("Cannot create CA JWT signature."));
return {};
}
EVP_MD_CTX_free(context);
signature.resize(static_cast<qsizetype>(signatureSize));
return signingInput + '.' + base64Url(signature);
}
bool parseJsonObject(const QByteArray &data, QJsonObject *object, QString *error)
{
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject())
{
setError(error, QStringLiteral("CA API returned invalid JSON."));
return false;
}
*object = document.object();
return true;
}
bool parseApiError(const QByteArray &data, int statusCode, QString *error)
{
QJsonObject object;
if (parseJsonObject(data, &object, nullptr))
{
const QString message = object.value(QStringLiteral("message")).toString();
if (!message.isEmpty())
{
setError(error, QStringLiteral("CA API HTTP %1: %2").arg(statusCode).arg(message));
return false;
}
}
setError(error, QStringLiteral("CA API returned HTTP status %1.").arg(statusCode));
return false;
}
bool parseCertificate(const QJsonObject &object, EetCaCertificate *certificate)
{
certificate->serialNumber = object.value(QStringLiteral("sn")).toString();
certificate->validFrom = QDateTime::fromString(
object.value(QStringLiteral("validFrom")).toString(), Qt::ISODate);
certificate->validTo = QDateTime::fromString(
object.value(QStringLiteral("validTo")).toString(), Qt::ISODate);
certificate->state = object.value(QStringLiteral("state")).toString();
certificate->description = object.value(QStringLiteral("description")).toString();
certificate->email = object.value(QStringLiteral("email")).toString();
return !certificate->serialNumber.isEmpty() && certificate->validFrom.isValid() &&
certificate->validTo.isValid() && !certificate->state.isEmpty();
}
bool performRequest(const EetCaClientConfig &config, const QString &path,
const QByteArray &method, QByteArray *data, int *statusCode,
QString *error)
{
if (!config.apiBaseUrl.isValid() || config.apiBaseUrl.scheme() != QLatin1String("https") ||
config.apiBaseUrl.host().isEmpty() || config.timeoutMilliseconds <= 0)
{
setError(error, QStringLiteral("CA API requires a valid HTTPS base URL and positive timeout."));
return false;
}
SigningMaterial material;
if (!loadSigningMaterial(config.signingPkcs12Path, config.signingPkcs12Password,
&material, error))
{
return false;
}
const QByteArray jwt = createJwt(material, error);
EVP_PKEY_free(material.key);
if (jwt.isEmpty())
{
return false;
}
QNetworkAccessManager manager;
QUrl endpoint(config.apiBaseUrl);
QString basePath = endpoint.path();
if (!basePath.endsWith(QLatin1Char('/')))
{
basePath.append(QLatin1Char('/'));
}
endpoint.setPath(basePath + path.mid(1));
QNetworkRequest request(endpoint);
if (!config.sslConfiguration.isNull())
{
request.setSslConfiguration(config.sslConfiguration);
}
request.setRawHeader("Authorization", "Bearer " + jwt);
QNetworkReply *reply = method == QByteArrayLiteral("POST")
? manager.post(request, QByteArray())
: manager.get(request);
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
bool timedOut = false;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(&timer, &QTimer::timeout, reply, [&] { timedOut = true; reply->abort(); });
timer.start(config.timeoutMilliseconds);
loop.exec();
if (timedOut || reply->error() != QNetworkReply::NoError)
{
setError(error, timedOut ? QStringLiteral("CA API request timed out.")
: reply->errorString());
reply->deleteLater();
return false;
}
*statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
*data = reply->readAll();
reply->deleteLater();
return true;
}
}
EetCaClient::EetCaClient(EetCaClientConfig config)
: m_config(std::move(config))
{
}
bool EetCaClient::requestRenewal(QUuid *requestId, QString *error) const
{
if (requestId == nullptr)
{
setError(error, QStringLiteral("A request ID output object is required."));
return false;
}
*requestId = {};
QByteArray data;
int statusCode = 0;
if (!performRequest(m_config, QStringLiteral("/request/renew"), QByteArrayLiteral("POST"),
&data, &statusCode, error))
{
return false;
}
if (statusCode != 201)
{
return parseApiError(data, statusCode, error);
}
QJsonObject object;
if (!parseJsonObject(data, &object, error))
{
return false;
}
const QUuid id(object.value(QStringLiteral("reqId")).toString());
if (id.isNull())
{
setError(error, QStringLiteral("CA API returned an invalid request ID."));
return false;
}
*requestId = id;
return true;
}
bool EetCaClient::requestStatus(const QUuid &requestId,
EetCaRequestStatus *status,
QString *error) const
{
if (status == nullptr || requestId.isNull())
{
setError(error, QStringLiteral("A valid request ID and output object are required."));
return false;
}
QByteArray data;
int statusCode = 0;
if (!performRequest(m_config, QStringLiteral("/request/%1/status").arg(
requestId.toString(QUuid::WithoutBraces)),
QByteArrayLiteral("GET"), &data, &statusCode, error))
{
return false;
}
if (statusCode != 200)
{
return parseApiError(data, statusCode, error);
}
QJsonObject object;
if (!parseJsonObject(data, &object, error))
{
return false;
}
const QUuid returnedId(object.value(QStringLiteral("reqId")).toString());
const QString state = object.value(QStringLiteral("state")).toString();
if (returnedId.isNull() || state.isEmpty())
{
setError(error, QStringLiteral("CA API returned an invalid request status."));
return false;
}
status->requestId = returnedId;
status->state = state;
status->pollAfterSeconds = object.value(QStringLiteral("pollAfterSeconds")).toInt();
status->rejectedReason = object.value(QStringLiteral("rejectedReason")).toString();
return true;
}
bool EetCaClient::listUnfinished(QList<QUuid> *requestIds, QString *error) const
{
if (requestIds == nullptr)
{
setError(error, QStringLiteral("A request ID list output object is required."));
return false;
}
requestIds->clear();
QByteArray data;
int statusCode = 0;
if (!performRequest(m_config, QStringLiteral("/request/not-finished"),
QByteArrayLiteral("GET"), &data, &statusCode, error))
{
return false;
}
if (statusCode != 200)
{
return parseApiError(data, statusCode, error);
}
QJsonObject object;
if (!parseJsonObject(data, &object, error))
{
return false;
}
for (const QJsonValue &value : object.value(QStringLiteral("data")).toArray())
{
const QUuid id(value.toString());
if (id.isNull())
{
setError(error, QStringLiteral("CA API returned an invalid request ID."));
requestIds->clear();
return false;
}
requestIds->append(id);
}
return true;
}
bool EetCaClient::claimDownload(const QUuid &requestId,
EetCaPkcs12 *result,
QString *error) const
{
if (result == nullptr || requestId.isNull())
{
setError(error, QStringLiteral("A valid request ID and output object are required."));
return false;
}
*result = {};
QByteArray data;
int statusCode = 0;
if (!performRequest(m_config, QStringLiteral("/request/%1/claim-download").arg(
requestId.toString(QUuid::WithoutBraces)),
QByteArrayLiteral("POST"), &data, &statusCode, error))
{
return false;
}
if (statusCode != 200)
{
return parseApiError(data, statusCode, error);
}
QJsonObject object;
if (!parseJsonObject(data, &object, error))
{
return false;
}
result->pkcs12 = QByteArray::fromBase64(
object.value(QStringLiteral("pkcs12")).toString().toLatin1());
result->password = object.value(QStringLiteral("password")).toString().toUtf8();
if (result->pkcs12.isEmpty() || result->password.isEmpty() ||
!parseCertificate(object.value(QStringLiteral("certificate")).toObject(),
&result->certificate))
{
*result = {};
setError(error, QStringLiteral("CA API returned an invalid certificate package."));
return false;
}
return true;
}
bool EetCaClient::acknowledgeDownload(const QUuid &requestId, QString *error) const
{
if (requestId.isNull())
{
setError(error, QStringLiteral("A valid request ID is required."));
return false;
}
QByteArray data;
int statusCode = 0;
if (!performRequest(m_config, QStringLiteral("/request/%1/ack-download").arg(
requestId.toString(QUuid::WithoutBraces)),
QByteArrayLiteral("POST"), &data, &statusCode, error))
{
return false;
}
if (statusCode != 204)
{
return parseApiError(data, statusCode, error);
}
return true;
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef EETCA_H
#define EETCA_H
#include <QByteArray>
#include <QDateTime>
#include <QList>
#include <QUuid>
#include <QUrl>
#include <QSslConfiguration>
#include "eetcpp_global.h"
struct EETCPPSHARED_EXPORT EetCaClientConfig
{
QUrl apiBaseUrl;
QString signingPkcs12Path;
QByteArray signingPkcs12Password;
int timeoutMilliseconds{30000};
QSslConfiguration sslConfiguration;
};
struct EETCPPSHARED_EXPORT EetCaRequestStatus
{
QUuid requestId;
QString state;
int pollAfterSeconds{0};
QString rejectedReason;
};
struct EETCPPSHARED_EXPORT EetCaCertificate
{
QString serialNumber;
QDateTime validFrom;
QDateTime validTo;
QString state;
QString description;
QString email;
};
struct EETCPPSHARED_EXPORT EetCaPkcs12
{
QByteArray pkcs12;
QByteArray password;
EetCaCertificate certificate;
};
class EETCPPSHARED_EXPORT EetCaClient
{
public:
explicit EetCaClient(EetCaClientConfig config);
bool requestRenewal(QUuid *requestId, QString *error = nullptr) const;
bool requestStatus(const QUuid &requestId,
EetCaRequestStatus *status,
QString *error = nullptr) const;
bool listUnfinished(QList<QUuid> *requestIds, QString *error = nullptr) const;
bool claimDownload(const QUuid &requestId,
EetCaPkcs12 *result,
QString *error = nullptr) const;
bool acknowledgeDownload(const QUuid &requestId, QString *error = nullptr) const;
private:
EetCaClientConfig m_config;
};
#endif // EETCA_H
-6
View File
@@ -1,6 +0,0 @@
#include "eetcpp.h"
EetCpp::EetCpp()
{
}
+7 -9
View File
@@ -3,14 +3,12 @@
#include "eetcpp_global.h"
#include "eetrequest.h"
#include "eetsender.h"
class EETCPPSHARED_EXPORT EetCpp
{
public:
EetCpp();
};
#include "eetv4request.h"
#include "eetv4xml.h"
#include "eetv4soap.h"
#include "eetv4signer.h"
#include "eetv4response.h"
#include "eetv4client.h"
#include "eetca.h"
#endif // EETCPP_H
-403
View File
@@ -1,403 +0,0 @@
#include "eetrequest.h"
EetRequest::EetRequest(QObject *parent) : QObject(parent)
{
m_prvniZaslani = true;
m_rezim = STANDARD;
}
QDateTime EetRequest::datOdesl() const
{
return m_datOdesl;
}
void EetRequest::setDatOdesl(const QDateTime &datOdesl)
{
m_datOdesl = datOdesl;
}
QString EetRequest::datOdeslStr() const
{
return m_datOdesl.toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
}
QTimeZone EetRequest::datOdeslZone() const
{
return m_datOdeslZone;
}
void EetRequest::setDatOdeslZone(const QTimeZone &datOdeslZone)
{
m_datOdeslZone = datOdeslZone;
}
bool EetRequest::prvniZaslani() const
{
return m_prvniZaslani;
}
void EetRequest::setPrvniZaslani(bool prvniZaslani)
{
m_prvniZaslani = prvniZaslani;
}
QString EetRequest::prvniZaslaniStr() const
{
return m_prvniZaslani ? "true" : "false";
}
QUuid EetRequest::uuidZpravy() const
{
return m_uuidZpravy;
}
void EetRequest::setUuidZpravy(const QUuid &uuidZpravy)
{
m_uuidZpravy = uuidZpravy;
}
QString EetRequest::uuidZpravyStr() const
{
QString ret = m_uuidZpravy.toString();
ret = ret.replace(0, 1, "");
ret = ret.replace(ret.size() - 1, 1, "");
return ret;
}
bool EetRequest::overeni() const
{
return m_overeni.isNull() ? false : *m_overeni.data();
}
void EetRequest::setOvereni(bool overeni)
{
m_overeni = QSharedPointer<bool>(new bool);
*m_overeni.data() = overeni;
}
QString EetRequest::overeniStr() const
{
return m_overeni.isNull() ? NULL_VAL : (*m_overeni.data() ? "true" : "false");
}
QString EetRequest::dicPopl() const
{
return m_dicPopl.isNull() ? NULL_VAL : QString(*m_dicPopl.data());
}
void EetRequest::setDicPopl(const QString &dicPopl)
{
m_dicPopl = QSharedPointer<QString>(new QString(dicPopl));
}
QString EetRequest::dicPoverujiciho() const
{
return m_dicPoverujiciho.isNull() ? NULL_VAL : QString(*m_dicPoverujiciho.data());
}
void EetRequest::setDicPoverujiciho(const QString &dicPoverujiciho)
{
m_dicPoverujiciho = QSharedPointer<QString>(new QString(dicPoverujiciho));
}
QString EetRequest::idProvoz() const
{
return m_idProvoz;
}
void EetRequest::setIdProvoz(const QString &idProvoz)
{
m_idProvoz = idProvoz;
}
QString EetRequest::idPokl() const
{
return m_idPokl;
}
void EetRequest::setIdPokl(const QString &idPokl)
{
m_idPokl = idPokl;
}
QString EetRequest::poradCis() const
{
return m_poradCis;
}
void EetRequest::setPoradCis(const QString &poradCis)
{
m_poradCis = poradCis;
}
QDateTime EetRequest::datTrzby() const
{
return m_datTrzby;
}
void EetRequest::setDatTrzby(const QDateTime &datTrzby)
{
m_datTrzby = datTrzby;
}
QString EetRequest::datTrzbyStr() const
{
return m_datTrzby.toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);
}
QTimeZone EetRequest::datTrzbyZone() const
{
return m_datTrzbyZone;
}
void EetRequest::setDatTrzbyZone(const QTimeZone &datTrzbyZone)
{
m_datTrzbyZone = datTrzbyZone;
}
double EetRequest::celkTrzba() const
{
return m_celkTrzba.isNull() ? 0 : *m_celkTrzba.data();
}
void EetRequest::setCelkTrzba(double celkTrzba)
{
m_celkTrzba = QSharedPointer<double>(new double);
*m_celkTrzba.data() = celkTrzba;
}
QString EetRequest::celkTrzbaStr() const
{
return m_celkTrzba.isNull() ? NULL_VAL : QString::number(*m_celkTrzba.data(), 'f', 2);
}
double EetRequest::zaklNepodlDph() const
{
return m_zaklNepodlDph.isNull() ? 0 : *m_zaklNepodlDph.data();
}
void EetRequest::setZaklNepodlDph(double zaklNepodlDph)
{
m_zaklNepodlDph = QSharedPointer<double>(new double);
*m_zaklNepodlDph.data() = zaklNepodlDph;
}
QString EetRequest::zaklNepodlDphStr() const
{
return m_zaklNepodlDph.isNull() ? NULL_VAL : QString::number(*m_zaklNepodlDph.data(), 'f', 2);
}
double EetRequest::zaklDan1() const
{
return m_zaklDan1.isNull() ? 0 : *m_zaklDan1.data();
}
void EetRequest::setZaklDan1(double zaklDan1)
{
m_zaklDan1 = QSharedPointer<double>(new double);
*m_zaklDan1.data() = zaklDan1;
}
QString EetRequest::zaklDan1Str() const
{
return m_zaklDan1.isNull() ? NULL_VAL : QString::number(*m_zaklDan1.data(), 'f', 2);
}
double EetRequest::dan1() const
{
return m_dan1.isNull() ? 0 : *m_dan1.data();
}
void EetRequest::setDan1(double dan1)
{
m_dan1 = QSharedPointer<double>(new double);
*m_dan1.data() = dan1;
}
QString EetRequest::dan1Str() const
{
return m_dan1.isNull() ? NULL_VAL : QString::number(*m_dan1.data(), 'f', 2);
}
double EetRequest::zaklDan2() const
{
return m_zaklDan2.isNull() ? 0 : *m_zaklDan2.data();
}
void EetRequest::setZaklDan2(double zaklDan2)
{
m_zaklDan2 = QSharedPointer<double>(new double);
*m_zaklDan2.data() = zaklDan2;
}
QString EetRequest::zaklDan2Str() const
{
return m_zaklDan2.isNull() ? NULL_VAL : QString::number(*m_zaklDan2.data(), 'f', 2);
}
double EetRequest::dan2() const
{
return m_dan2.isNull() ? 0 : *m_dan2.data();
}
void EetRequest::setDan2(double dan2)
{
m_dan2 = QSharedPointer<double>(new double);
*m_dan2.data() = dan2;
}
QString EetRequest::dan2Str() const
{
return m_dan2.isNull() ? NULL_VAL : QString::number(*m_dan2.data(), 'f', 2);
}
double EetRequest::zaklDan3() const
{
return m_zaklDan3.isNull() ? 0 : *m_zaklDan3.data();
}
void EetRequest::setZaklDan3(double zaklDan3)
{
m_zaklDan3 = QSharedPointer<double>(new double);
*m_zaklDan3.data() = zaklDan3;
}
QString EetRequest::zaklDan3Str() const
{
return m_zaklDan3.isNull() ? NULL_VAL : QString::number(*m_zaklDan3.data(), 'f', 2);
}
QString EetRequest::dan3Str() const
{
return m_dan3.isNull() ? NULL_VAL : QString::number(*m_dan3.data(), 'f', 2);
}
double EetRequest::dan3() const
{
return m_dan3.isNull() ? 0 : *m_dan3.data();
}
void EetRequest::setDan3(double dan3)
{
m_dan3 = QSharedPointer<double>(new double);
*m_dan3.data() = dan3;
}
double EetRequest::cestSluz() const
{
return m_cestSluz.isNull() ? 0 : *m_cestSluz.data();
}
void EetRequest::setCestSluz(double cestSluz)
{
m_cestSluz = QSharedPointer<double>(new double);
*m_cestSluz.data() = cestSluz;
}
QString EetRequest::cestSluzStr() const
{
return m_cestSluz.isNull() ? NULL_VAL : QString::number(*m_cestSluz.data(), 'f', 2);
}
double EetRequest::pouzitZboz1() const
{
return m_pouzitZboz1.isNull() ? 0 : *m_pouzitZboz1.data();
}
void EetRequest::setPouzitZboz1(double pouzitZboz1)
{
m_pouzitZboz1 = QSharedPointer<double>(new double);
*m_pouzitZboz1.data() = pouzitZboz1;
}
QString EetRequest::pouzitZboz1Str() const
{
return m_pouzitZboz1.isNull() ? NULL_VAL : QString::number(*m_pouzitZboz1.data(), 'f', 2);
}
double EetRequest::pouzitZboz2() const
{
return m_pouzitZboz2.isNull() ? 0 : *m_pouzitZboz2.data();
}
void EetRequest::setPouzitZboz2(double pouzitZboz2)
{
m_pouzitZboz2 = QSharedPointer<double>(new double);
*m_pouzitZboz2.data() = pouzitZboz2;
}
QString EetRequest::pouzitZboz2Str() const
{
return m_pouzitZboz2.isNull() ? NULL_VAL : QString::number(*m_pouzitZboz2.data(), 'f', 2);
}
double EetRequest::pouzitZboz3() const
{
return m_pouzitZboz3.isNull() ? 0 : *m_pouzitZboz3.data();
}
void EetRequest::setPouzitZboz3(double pouzitZboz3)
{
m_pouzitZboz3 = QSharedPointer<double>(new double);
*m_pouzitZboz3.data() = pouzitZboz3;
}
QString EetRequest::pouzitZboz3Str() const
{
return m_pouzitZboz3.isNull() ? NULL_VAL : QString::number(*m_pouzitZboz3.data(), 'f', 2);
}
double EetRequest::urcenoCerpZuct() const
{
return m_urcenoCerpZuct.isNull() ? 0 : *m_urcenoCerpZuct.data();
}
void EetRequest::setUrcenoCerpZuct(double urcenoCerpZuct)
{
m_urcenoCerpZuct = QSharedPointer<double>(new double);
*m_urcenoCerpZuct.data() = urcenoCerpZuct;
}
QString EetRequest::urcenoCerpZuctStr() const
{
return m_urcenoCerpZuct.isNull() ? NULL_VAL : QString::number(*m_urcenoCerpZuct.data(), 'f', 2);
}
double EetRequest::cerpZuct() const
{
return m_cerpZuct.isNull() ? 0 : *m_cerpZuct.data();
}
void EetRequest::setCerpZuct(double cerpZuct)
{
m_cerpZuct = QSharedPointer<double>(new double);
*m_cerpZuct.data() = cerpZuct;
}
QString EetRequest::cerpZuctStr() const
{
return m_cerpZuct.isNull() ? NULL_VAL : QString::number(*m_cerpZuct.data(), 'f', 2);
}
EetRequest::EetRezim EetRequest::rezim() const
{
return m_rezim;
}
void EetRequest::setRezim(const EetRequest::EetRezim &rezim)
{
m_rezim = rezim;
}
QString EetRequest::rezimStr() const
{
switch (m_rezim) {
case ZJEDNODUSENY:
return "1";
case STANDARD:
return "0";
default:
return "0";
}
}
-186
View File
@@ -1,186 +0,0 @@
#ifndef EETREQUEST_H
#define EETREQUEST_H
#include <QString>
#include <QDate>
#include <QUuid>
#include <QTimeZone>
#include <QSharedPointer>
#include "eetcpp_global.h"
#define NULL_VAL "__NULL__"
class EETCPPSHARED_EXPORT EetRequest : public QObject
{
Q_OBJECT
Q_PROPERTY(QString dat_odesl READ datOdeslStr)
Q_PROPERTY(QString prvni_zaslani READ prvniZaslaniStr)
Q_PROPERTY(QString uuid_zpravy READ uuidZpravyStr)
Q_PROPERTY(QString overeni READ overeniStr)
Q_PROPERTY(QString dic_popl READ dicPopl)
Q_PROPERTY(QString dic_poverujiciho READ dicPoverujiciho)
Q_PROPERTY(QString id_provoz READ idProvoz)
Q_PROPERTY(QString id_pokl READ idPokl)
Q_PROPERTY(QString porad_cis READ poradCis)
Q_PROPERTY(QString dat_trzby READ datTrzbyStr)
Q_PROPERTY(QString celk_trzba READ celkTrzbaStr)
Q_PROPERTY(QString zakl_nepodl_dph READ zaklNepodlDphStr)
Q_PROPERTY(QString zakl_dan1 READ zaklDan1Str)
Q_PROPERTY(QString zakl_dan2 READ zaklDan2Str)
Q_PROPERTY(QString zakl_dan3 READ zaklDan3Str)
Q_PROPERTY(QString dan1 READ dan1Str)
Q_PROPERTY(QString dan2 READ dan2Str)
Q_PROPERTY(QString dan3 READ dan3Str)
Q_PROPERTY(QString cest_sluz READ cestSluzStr)
Q_PROPERTY(QString pouzit_zboz1 READ pouzitZboz1Str)
Q_PROPERTY(QString pouzit_zboz2 READ pouzitZboz2Str)
Q_PROPERTY(QString pouzit_zboz3 READ pouzitZboz3Str)
Q_PROPERTY(QString urceno_cerp_zuct READ urcenoCerpZuctStr)
Q_PROPERTY(QString cerp_zuct READ cerpZuctStr)
Q_PROPERTY(QString rezim READ rezimStr)
public:
explicit EetRequest(QObject *parent = 0);
enum EetRezim {
ZJEDNODUSENY = 0,
STANDARD
};
QDateTime datOdesl() const;
void setDatOdesl(const QDateTime &datOdesl);
QString datOdeslStr() const;
QTimeZone datOdeslZone() const;
void setDatOdeslZone(const QTimeZone &datOdeslZone);
QString datOdeslZoneStr() const;
bool prvniZaslani() const;
void setPrvniZaslani(bool prvniZaslani);
QString prvniZaslaniStr() const;
QUuid uuidZpravy() const;
void setUuidZpravy(const QUuid &uuidZpravy);
QString uuidZpravyStr() const;
bool overeni() const;
void setOvereni(bool overeni);
QString overeniStr() const;
QString dicPopl() const;
void setDicPopl(const QString &dicPopl);
QString dicPoverujiciho() const;
void setDicPoverujiciho(const QString &dicPoverujiciho);
QString idProvoz() const;
void setIdProvoz(const QString &idProvoz);
QString idPokl() const;
void setIdPokl(const QString &idPokl);
QString poradCis() const;
void setPoradCis(const QString &poradCis);
QDateTime datTrzby() const;
void setDatTrzby(const QDateTime &datTrzby);
QString datTrzbyStr() const;
QTimeZone datTrzbyZone() const;
void setDatTrzbyZone(const QTimeZone &datTrzbyZone);
double celkTrzba() const;
void setCelkTrzba(double celkTrzba);
QString celkTrzbaStr() const;
double zaklNepodlDph() const;
void setZaklNepodlDph(double zaklNepodlDph);
QString zaklNepodlDphStr() const;
double zaklDan1() const;
void setZaklDan1(double zaklDan1);
QString zaklDan1Str() const;
double dan1() const;
void setDan1(double dan1);
QString dan1Str() const;
double zaklDan2() const;
void setZaklDan2(double zaklDan2);
QString zaklDan2Str() const;
double dan2() const;
void setDan2(double dan2);
QString dan2Str() const;
double zaklDan3() const;
void setZaklDan3(double zaklDan3);
QString zaklDan3Str() const;
double dan3() const;
void setDan3(double dan3);
QString dan3Str() const;
double cestSluz() const;
void setCestSluz(double cestSluz);
QString cestSluzStr() const;
double pouzitZboz1() const;
void setPouzitZboz1(double pouzitZboz1);
QString pouzitZboz1Str() const;
double pouzitZboz2() const;
void setPouzitZboz2(double pouzitZboz2);
QString pouzitZboz2Str() const;
double pouzitZboz3() const;
void setPouzitZboz3(double pouzitZboz3);
QString pouzitZboz3Str() const;
double urcenoCerpZuct() const;
void setUrcenoCerpZuct(double urcenoCerpZuct);
QString urcenoCerpZuctStr() const;
double cerpZuct() const;
void setCerpZuct(double cerpZuct);
QString cerpZuctStr() const;
EetRezim rezim() const;
void setRezim(const EetRezim &rezim);
QString rezimStr() const;
private:
QDateTime m_datOdesl;
QTimeZone m_datOdeslZone;
bool m_prvniZaslani;
QUuid m_uuidZpravy;
QSharedPointer<bool> m_overeni;
QSharedPointer<QString> m_dicPopl;
QSharedPointer<QString> m_dicPoverujiciho;
QString m_idProvoz;
QString m_idPokl;
QString m_poradCis;
QDateTime m_datTrzby;
QTimeZone m_datTrzbyZone;
QSharedPointer<double> m_celkTrzba;
QSharedPointer<double> m_zaklNepodlDph;
QSharedPointer<double> m_zaklDan1;
QSharedPointer<double> m_dan1;
QSharedPointer<double> m_zaklDan2;
QSharedPointer<double> m_dan2;
QSharedPointer<double> m_zaklDan3;
QSharedPointer<double> m_dan3;
QSharedPointer<double> m_cestSluz;
QSharedPointer<double> m_pouzitZboz1;
QSharedPointer<double> m_pouzitZboz2;
QSharedPointer<double> m_pouzitZboz3;
QSharedPointer<double> m_urcenoCerpZuct;
QSharedPointer<double> m_cerpZuct;
EetRezim m_rezim;
signals:
public slots:
};
#endif // EETREQUEST_H
-119
View File
@@ -1,119 +0,0 @@
#include "eetresult.h"
EetResult::EetResult(QObject *parent) : QObject(parent)
{
m_status = RESPONSE_OK;
}
EetResult::ResponseStatus EetResult::status() const
{
return m_status;
}
void EetResult::setStatus(const EetResult::ResponseStatus &status)
{
m_status = status;
}
QString EetResult::fik() const
{
return m_fik;
}
void EetResult::setFik(const QString &fik)
{
m_fik = fik;
}
EetMessageList EetResult::warnings() const
{
return m_warnings;
}
void EetResult::setWarnings(const EetMessageList &warnings)
{
m_warnings = warnings;
}
EetMessageList EetResult::errors() const
{
return m_errors;
}
void EetResult::setErrors(const EetMessageList &errors)
{
m_errors = errors;
}
QUuid EetResult::uuid() const
{
return m_uuid;
}
void EetResult::setUuid(const QUuid &uuid)
{
m_uuid = uuid;
}
QDateTime EetResult::reciveDate() const
{
return m_reciveDate;
}
void EetResult::setReciveDate(const QDateTime &reciveDate)
{
m_reciveDate = reciveDate;
}
QString EetResult::pkp() const
{
return m_pkp;
}
void EetResult::setPkp(const QString &pkp)
{
m_pkp = pkp;
}
QString EetResult::bkp() const
{
return m_bkp;
}
void EetResult::setBkp(const QString &bkp)
{
m_bkp = bkp;
}
EetMessage::EetMessage(QObject *parent)
:QObject(parent)
{
m_code = 0;
}
EetMessage::EetMessage(int code, const QString &message, QObject *parent)
:QObject(parent)
{
m_code = code;
m_message = message;
}
int EetMessage::code() const
{
return m_code;
}
void EetMessage::setCode(int code)
{
m_code = code;
}
QString EetMessage::message() const
{
return m_message;
}
void EetMessage::setMessage(const QString &message)
{
m_message = message;
}
-85
View File
@@ -1,85 +0,0 @@
#ifndef EETRESULT_H
#define EETRESULT_H
#include <QObject>
#include <QUuid>
#include <QString>
#include <QList>
#include <QDateTime>
#include "eetcpp_global.h"
class EETCPPSHARED_EXPORT EetMessage : public QObject
{
Q_OBJECT
public:
explicit EetMessage(QObject *parent = 0);
EetMessage(int code, const QString &message, QObject *parent = 0);
int code() const;
void setCode(int code);
QString message() const;
void setMessage(const QString &message);
private:
int m_code;
QString m_message;
};
typedef QList<EetMessage*> EetMessageList;
class EETCPPSHARED_EXPORT EetResult : public QObject
{
Q_OBJECT
public:
enum ResponseStatus
{
RESPONSE_OK = 0,
DATA_ERROR,
USER_CERT_ERROR,
SERVER_ERROR,
SERVICE_CERT_ERROR,
INVALID_SIGNATURE,
SSL_ERROR,
TIMEDOUT,
OFFLINE
};
explicit EetResult(QObject *parent = 0);
ResponseStatus status() const;
void setStatus(const ResponseStatus &status);
QString fik() const;
void setFik(const QString &fik);
EetMessageList warnings() const;
void setWarnings(const EetMessageList &warnings);
EetMessageList errors() const;
void setErrors(const EetMessageList &errors);
QUuid uuid() const;
void setUuid(const QUuid &uuid);
QDateTime reciveDate() const;
void setReciveDate(const QDateTime &reciveDate);
QString pkp() const;
void setPkp(const QString &pkp);
QString bkp() const;
void setBkp(const QString &bkp);
private:
ResponseStatus m_status;
QUuid m_uuid;
QDateTime m_reciveDate;
QString m_fik;
QString m_pkp;
QString m_bkp;
EetMessageList m_warnings;
EetMessageList m_errors;
};
#endif // EETRESULT_H
-309
View File
@@ -1,309 +0,0 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "eetsender.h"
#include "eetsigner.h"
#include "eettemplate.h"
#include <QDebug>
#include <QXmlQuery>
#include <QXmlResultItems>
#include <QTimer>
const QString EetSender::ms_nsDef = "declare namespace eet = \"http://fs.mfcr.cz/eet/schema/v3\";\n"
"declare namespace ds = \"http://www.w3.org/2000/09/xmldsig#\";\n"
"declare namespace wsu = \"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\";\n"
"declare namespace wsse = \"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\";\n"
"declare namespace senc = \"http://schemas.xmlsoap.org/soap/encoding/\";\n"
"declare namespace senv = \"http://schemas.xmlsoap.org/soap/envelope/\";\n";
bool EetSender::m_online = true;
EetSender::EetSender(QObject *parent) : QObject(parent)
{
m_signer = nullptr;
m_resut = nullptr;
m_checkSignature = true;
m_resut = new EetResult(this);
m_manager = new QNetworkAccessManager(this);
connect(m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
connect(m_manager, &QNetworkAccessManager::sslErrors, [this](QNetworkReply *rep, QList<QSslError>){
m_resut->setStatus(EetResult::SSL_ERROR);
emit sendFinished();
rep->deleteLater();
});
m_serviceUrl = PRODUCTION_URL;
m_timer = new QTimer(this);
m_timer->setInterval(1000);
m_timer->setSingleShot(true);
}
void EetSender::sendRequest(EetRequest *request)
{
if (m_signer == nullptr)
{
m_resut->setStatus(EetResult::USER_CERT_ERROR);
emit sendFinished();
return;
}
request->setUuidZpravy(QUuid::createUuid());
EetTemplate tempBody(BODY_TEMPLATE);
tempBody.setSigner(m_signer);
tempBody.setResult(m_resut);
QString strBody = tempBody.fillTemplate(request);
QByteArray digest = m_signer->sha256HashData(strBody.toUtf8());
QMap<QString, QString> val;
val["digest"] = QString(digest.toBase64());
EetTemplate tempSignature(SIGNATURE_TEMPLATE);
QString strSignature = tempSignature.fillTemplate(val);
QByteArray sign = m_signer->signData(strSignature.toUtf8());
val["signature"] = QString(sign.toBase64());
val["soap:Body"] = strBody;
val["certb64"] = m_signer->getCertificate();
EetTemplate tempRequest(REQUEST_TEMPLATE);
QString strRequest = tempRequest.fillTemplate(val);
QNetworkRequest req(QUrl(m_serviceUrl.toStdString().c_str()));
qDebug() << strRequest;
if (m_online)
{
QNetworkReply *rep = m_manager->post(req, strRequest.toUtf8());
m_timer->start();
connect(m_timer, &QTimer::timeout, [this, rep](){
rep->abort();
m_resut->setStatus(EetResult::TIMEDOUT);
emit sendFinished();
});
}
else
{
m_resut->setStatus(EetResult::OFFLINE);
m_timer->stop();
emit sendFinished();
}
}
void EetSender::setupSigner(const QString &certPath, const QString &passwd)
{
if (m_signer != nullptr)
{
delete m_signer;
}
m_signer = new EetSigner(this);
m_signer->setup(certPath, QCA::SecureArray(passwd.toUtf8()));
}
void EetSender::setCheckSignature(bool checkSignature)
{
m_checkSignature = checkSignature;
}
bool EetSender::checkSignature() const
{
return m_checkSignature;
}
void EetSender::setPlayground(bool pg)
{
m_serviceUrl = (pg ? PLAYGROUND_URL : PRODUCTION_URL);
}
EetResult *EetSender::resut() const
{
return m_resut;
}
void EetSender::setTimeout(int timeout)
{
m_timer->setInterval(timeout);
}
bool EetSender::verifySignature(const QByteArray &repData)
{
QString queryString("//senv:Envelope/senv:Header/wsse:Security/ds:Signature/ds:SignedInfo");
QString signedInfo, certB64, signatureB64;
QXmlQuery q;
q.setFocus(QString(repData));
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&signedInfo);
QStringList list = signedInfo.split("\n");
signedInfo = "<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n";
for (int i = 1; i < list.length(); i++)
{
QString line = list[i];
line = line.replace(QRegExp("^(\\s+)<([A-Za-z]+)([A-Za-z0-9-\"=/#:\\.\\ ]+)/>"), "\\1<\\2\\3></\\2>");
signedInfo += line + "\n";
}
signedInfo = signedInfo.trimmed();
signedInfo = signedInfo.replace(" ", " ");
queryString = "//senv:Envelope/senv:Header/wsse:Security/wsse:BinarySecurityToken/text()";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&certB64);
queryString = "//senv:Envelope/senv:Header/wsse:Security/ds:Signature/ds:SignatureValue/text()";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&signatureB64);
QCA::ConvertResult res;
QCA::Certificate cert = QCA::Certificate::fromDER(QByteArray::fromBase64(certB64.toUtf8()), &res);
if (res != QCA::ConvertGood)
{
m_resut->setStatus(EetResult::SERVICE_CERT_ERROR);
return false;
}
QCA::PublicKey pubKey = cert.subjectPublicKey();
if (!pubKey.canVerify())
{
m_resut->setStatus(EetResult::SERVICE_CERT_ERROR);
return false;
}
bool signValid = pubKey.verifyMessage(QCA::MemoryRegion(signedInfo.toUtf8()), QByteArray::fromBase64(signatureB64.toUtf8()), QCA::EMSA3_SHA256);
if (!signValid)
{
m_resut->setStatus(EetResult::INVALID_SIGNATURE);
return false;
}
return true;
}
void EetSender::replyFinished(QNetworkReply *reply)
{
m_timer->stop();
if (reply->error() != QNetworkReply::NoError)
{
m_resut->setStatus(EetResult::SERVER_ERROR);
emit sendFinished();
reply->deleteLater();
return;
}
QByteArray repData = reply->readAll();
qDebug() << QString(repData);
QXmlQuery q;
q.setFocus(QString(repData));
QXmlResultItems items;
QString result;
QString queryString = "//senv:Envelope/senv:Body/eet:Odpoved/eet:Chyba";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&items);
EetMessageList errors;
QXmlItem item = items.next();
while (!item.isNull())
{
EetMessage *mesg = new EetMessage(m_resut);
queryString = "./@kod/data(.)";
q.setQuery(ms_nsDef + queryString);
q.setFocus(item);
q.evaluateTo(&result);
result = result.trimmed();
mesg->setCode(result.toInt());
queryString = "./string()";
q.setQuery(ms_nsDef + queryString);
q.setFocus(item);
q.evaluateTo(&result);
result = result.trimmed();
mesg->setMessage(result);
errors.append(mesg);
item = items.next();
}
m_resut->setErrors(errors);
if (m_checkSignature && errors.isEmpty() && !verifySignature(repData))
{
emit sendFinished();
return;
}
queryString = "//senv:Envelope/senv:Body/eet:Odpoved/eet:Hlavicka/@uuid_zpravy/data(.)";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&result);
result = result.trimmed();
m_resut->setUuid(QUuid(result));
queryString = "//senv:Envelope/senv:Body/eet:Odpoved/eet:Hlavicka/@dat_prij/data(.)";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&result);
result = result.trimmed();
m_resut->setReciveDate(QDateTime::fromString(result));
queryString = "//senv:Envelope/senv:Body/eet:Odpoved/eet:Potvrzeni/@fik/data(.)";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&result);
result = result.trimmed();
m_resut->setFik(result);
queryString = "//senv:Envelope/senv:Body/eet:Odpoved/eet:Varovani";
q.setQuery(ms_nsDef + queryString);
q.evaluateTo(&items);
EetMessageList warnings;
item = items.next();
while (!item.isNull())
{
EetMessage *mesg = new EetMessage(m_resut);
queryString = "./@kod_varov/data(.)";
q.setQuery(ms_nsDef + queryString);
q.setFocus(item);
q.evaluateTo(&result);
result = result.trimmed();
mesg->setCode(result.toInt());
queryString = "./string()";
q.setQuery(ms_nsDef + queryString);
q.setFocus(item);
q.evaluateTo(&result);
result = result.trimmed();
mesg->setMessage(result);
warnings.append(mesg);
item = items.next();
}
m_resut->setWarnings(warnings);
if (errors.isEmpty())
{
m_resut->setReciveDate(QDateTime::currentDateTime());
}
else
{
m_resut->setStatus(EetResult::DATA_ERROR);
}
emit sendFinished();
reply->deleteLater();
}
-86
View File
@@ -1,86 +0,0 @@
#ifndef EETSENDER_H
#define EETSENDER_H
#include <QObject>
#include <QTimer>
#include "eetcpp_global.h"
#include "eetrequest.h"
#include "eetresult.h"
#define PRODUCTION_URL "https://prod.eet.cz/eet/services/EETServiceSOAP/v3"
#define PLAYGROUND_URL "https://pg.eet.cz/eet/services/EETServiceSOAP/v3"
class QNetworkAccessManager;
class QNetworkReply;
class EetSigner;
/**
* @brief The EetSender class Performs send actions.
*/
class EETCPPSHARED_EXPORT EetSender : public QObject
{
Q_OBJECT
public:
explicit EetSender(QObject *parent = 0);
/**
* @brief Sends request to EET web service
* @param request EET data
*/
void sendRequest(EetRequest *request);
/**
* @brief Setup certificate for signing EET request data.
* @param certPath Path to certificate p12 file. File must contain private key.
* @param passwd Password for accesing p12 file.
*/
void setupSigner(const QString &certPath, const QString &passwd);
/**
* @brief Sets verifing signature of response data.
* @param checkSignature true if you wish to verify.
*/
void setCheckSignature(bool checkSignature);
/**
* @brief Returns signature verifing state.
* @return true if verify.
*/
bool checkSignature() const;
/**
* @brief Sets comunication with playground EET portal.
* @param pg true if you wish to communicate with playground portal.
*/
void setPlayground(bool pg);
/**
* @brief Returns comunication result.
* @return result
*/
EetResult *resut() const;
void setTimeout(int timeout);
static bool m_online;
private:
static const QString ms_nsDef;
EetSigner *m_signer;
QNetworkAccessManager *m_manager;
QTimer *m_timer;
bool m_checkSignature;
EetResult *m_resut;
QString m_serviceUrl;
bool verifySignature(const QByteArray &repData);
private slots:
void replyFinished(QNetworkReply *reply);
signals:
void sendFinished();
public slots:
};
#endif // EETSENDER_H
-70
View File
@@ -1,70 +0,0 @@
#include "eetsigner.h"
#include <QDebug>
#include <QStringList>
EetSigner::EetSigner(QObject *parent) : QObject(parent)
{
}
QByteArray EetSigner::signData(const QByteArray &data)
{
if (!QCA::isSupported("sha256"))
{
return QByteArray();
}
if (m_result != QCA::ConvertGood || m_bundle.isNull())
{
return QByteArray();
}
QCA::PrivateKey privKey = m_bundle.privateKey();
return privKey.signMessage(QCA::MemoryRegion(data), QCA::EMSA3_SHA256);
}
QByteArray EetSigner::sha1HashData(const QByteArray &data)
{
if (!QCA::isSupported("sha1"))
{
return QByteArray();
}
return QCA::Hash("sha1").hash(QCA::MemoryRegion(data)).toByteArray();
}
QByteArray EetSigner::sha256HashData(const QByteArray &data)
{
if (!QCA::isSupported("sha256"))
{
return QByteArray();
}
return QCA::Hash("sha256").hash(QCA::MemoryRegion(data)).toByteArray();
}
QString EetSigner::getCertificate()
{
if (!QCA::isSupported("cert"))
{
return "";
}
if (m_result != QCA::ConvertGood || m_bundle.isNull())
{
return "";
}
if (!m_bundle.certificateChain().isEmpty())
{
return QString(m_bundle.certificateChain().primary().toDER().toBase64());
}
return "";
}
void EetSigner::setup(const QString &certPath, const QCA::SecureArray &certPasswd)
{
m_bundle = QCA::KeyBundle::fromFile(certPath, certPasswd, &m_result);
}
-30
View File
@@ -1,30 +0,0 @@
#ifndef EETSIGNER_H
#define EETSIGNER_H
#include <QObject>
#include <QByteArray>
#include <Qca-qt5/QtCrypto/QtCrypto>
class EetSigner : public QObject
{
Q_OBJECT
public:
explicit EetSigner(QObject *parent = 0);
QByteArray signData(const QByteArray &data);
QByteArray sha1HashData(const QByteArray &data);
QByteArray sha256HashData(const QByteArray &data);
QString getCertificate();
void setup(const QString &certPath, const QCA::SecureArray &certPasswd);
private:
QCA::Initializer m_qcaInit;
QCA::KeyBundle m_bundle;
QCA::ConvertResult m_result;
signals:
public slots:
};
#endif // EETSIGNER_H
-147
View File
@@ -1,147 +0,0 @@
#include "eetresult.h"
#include "eettemplate.h"
#include <QFile>
#include <QTextStream>
EetTemplate::EetTemplate(QObject *parent) : QObject(parent)
{
m_result = nullptr;
}
EetTemplate::EetTemplate(const QString &tmp, QObject *parent) : QObject(parent)
{
QFile tmpFile(tmp);
tmpFile.open(QFile::ReadOnly | QFile::Text);
QTextStream stream(&tmpFile);
m_template = stream.readAll();
m_result = nullptr;
}
QString EetTemplate::fillTemplate(EetRequest *request)
{
m_template = fillTemplateInternal(request, nullptr);
QString pkp = getPkpString(request);
QByteArray signedPkp = m_signer->signData(pkp.toUtf8());
QByteArray sha1Bkp = m_signer->sha1HashData(signedPkp);
QString base64Sign(signedPkp.toBase64());
QString base16bkp(sha1Bkp.toHex());
QString splitedBkp;
for (int i = 0; i < base16bkp.length(); i++)
{
if (i > 0 && i % 8 == 0)
{
splitedBkp += "-";
}
splitedBkp += base16bkp[i];
}
if (m_result != nullptr)
{
m_result->setPkp(base64Sign);
m_result->setBkp(splitedBkp.toUpper());
}
QMap<QString, QString> varMap;
varMap["pkp"] = base64Sign;
varMap["bkp"] = splitedBkp.toUpper();
return fillTemplate(varMap);
}
QString EetTemplate::fillTemplate(QMap<QString, QString> map)
{
return fillTemplateInternal(nullptr, &map);
}
QString EetTemplate::fillTemplateInternal(EetRequest *request, QMap<QString, QString> *map)
{
QString ret;
QString phStart;
if (request != nullptr)
{
phStart = "@{";
}
else
{
phStart = "${";
}
for (int i = 0; i < m_template.size(); i++)
{
QChar c = m_template[i];
if (!m_template.mid(i).startsWith(phStart))
{
if ((i > 0 && c == ' ' && m_template[i - 1] == ' '))
{
continue;
}
else
{
ret += c;
continue;
}
}
int p = m_template.indexOf("}", i + 2);
if (p < 0)
{
return ret;
}
QString var = m_template.mid(i + 2, p - i - 2);
QString value;
if (request != nullptr)
{
value = request->property(var.toStdString().c_str()).toString();
}
else
{
value = map->value(var, NULL_VAL);
}
if (value != NULL_VAL)
{
QString attrib;
if (request != NULL)
{
attrib = QString("%1=\"%2\"").arg(var, value);
}
else
{
attrib = value;
}
ret += attrib;
}
i = p;
}
return ret.replace(QRegExp("\" >"), "\">");
}
QString EetTemplate::getPkpString(EetRequest *request)
{
QString ret = QString("%1|%2|%3|%4|%5|%6").arg(request->property("dic_popl").toString(),
request->property("id_provoz").toString(),
request->property("id_pokl").toString(),
request->property("porad_cis").toString(),
request->property("dat_trzby").toString(),
request->property("celk_trzba").toString());
return ret;
}
void EetTemplate::setResult(EetResult *result)
{
m_result = result;
}
-41
View File
@@ -1,41 +0,0 @@
#ifndef EETTEMPLATE_H
#define EETTEMPLATE_H
#include <QObject>
#include <QString>
#include <QMap>
#include "eetrequest.h"
#include "eetsigner.h"
#define BODY_TEMPLATE ":/res/template_body.txt"
#define REQUEST_TEMPLATE ":/res/template_request.txt"
#define SIGNATURE_TEMPLATE ":/res/template_signature.txt"
class EetResult;
class EetTemplate : public QObject
{
Q_OBJECT
public:
explicit EetTemplate(QObject *parent = 0);
EetTemplate(const QString &tmp, QObject *parent = 0);
QString fillTemplate(EetRequest *request);
QString fillTemplate(QMap<QString, QString> map);
void setSigner(EetSigner *signer) { m_signer = signer; }
void setResult(EetResult *result);
signals:
public slots:
private:
QString m_template;
QString fillTemplateInternal(EetRequest *request, QMap<QString, QString> *map);
QString getPkpString(EetRequest *request);
EetSigner *m_signer;
EetResult *m_result;
};
#endif // EETTEMPLATE_H
+133
View File
@@ -0,0 +1,133 @@
#include "eetv4client.h"
#include "eetv4signer.h"
#include "eetv4soap.h"
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <QUuid>
namespace
{
void setError(QString *error, const QString &message)
{
if (error != nullptr)
{
*error = message;
}
}
}
EetV4Client::EetV4Client(EetV4ClientConfig config)
: m_config(std::move(config))
{
}
bool EetV4Client::sendSale(const EetV4SaleRequest &request,
const QString &pkcs12Path,
const QByteArray &pkcs12Password,
EetV4Response *response,
QString *error) const
{
if (response == nullptr)
{
setError(error, QStringLiteral("A response output object is required."));
return false;
}
*response = {};
if (!m_config.endpoint.isValid() || m_config.endpoint.scheme() != QLatin1String("https") ||
m_config.endpoint.host().isEmpty())
{
setError(error, QStringLiteral("EET endpoint must be a valid HTTPS URL."));
return false;
}
if (m_config.timeoutMilliseconds <= 0)
{
setError(error, QStringLiteral("EET timeout must be positive."));
return false;
}
const QByteArray bodyId = QByteArrayLiteral("id-") +
request.messageUuid.toString(QUuid::WithoutBraces).toLatin1();
QString soapError;
const QByteArray unsignedSoap = EetV4Soap::serializeUnsigned(request, bodyId, &soapError);
if (unsignedSoap.isEmpty())
{
setError(error, soapError);
return false;
}
QString signingError;
const QByteArray signedSoap = EetV4Signer::signSoap(
unsignedSoap, pkcs12Path, pkcs12Password, &signingError);
if (signedSoap.isEmpty())
{
setError(error, signingError);
return false;
}
QNetworkAccessManager networkAccessManager;
QNetworkRequest networkRequest(m_config.endpoint);
if (!m_config.sslConfiguration.isNull())
{
networkRequest.setSslConfiguration(m_config.sslConfiguration);
}
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader,
QStringLiteral("text/xml; charset=utf-8"));
networkRequest.setRawHeader("Accept", "text/xml");
QNetworkReply *reply = networkAccessManager.post(networkRequest, signedSoap);
QEventLoop eventLoop;
QTimer timeout;
timeout.setSingleShot(true);
bool timedOut = false;
QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
QObject::connect(&timeout, &QTimer::timeout, reply, [&] {
timedOut = true;
reply->abort();
});
timeout.start(m_config.timeoutMilliseconds);
eventLoop.exec();
timeout.stop();
if (timedOut)
{
setError(error, QStringLiteral("EET request timed out."));
reply->deleteLater();
return false;
}
if (reply->error() != QNetworkReply::NoError)
{
setError(error, reply->errorString());
reply->deleteLater();
return false;
}
const QByteArray responseSoap = reply->readAll();
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
QString verificationError;
if (!EetV4Signer::verifySoap(responseSoap, &verificationError))
{
setError(error, statusCode >= 300
? QStringLiteral("EET endpoint returned HTTP status %1 and an invalid "
"SOAP signature: %2.")
.arg(statusCode)
.arg(verificationError)
: verificationError);
return false;
}
QString responseError;
if (!EetV4ResponseParser::parseSoap(responseSoap, response, &responseError))
{
setError(error, statusCode >= 300
? QStringLiteral("EET endpoint returned HTTP status %1: %2.")
.arg(statusCode)
.arg(responseError)
: responseError);
return false;
}
return true;
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef EETV4CLIENT_H
#define EETV4CLIENT_H
#include <QUrl>
#include <QSslConfiguration>
#include "eetcpp_global.h"
#include "eetv4request.h"
#include "eetv4response.h"
struct EETCPPSHARED_EXPORT EetV4ClientConfig
{
QUrl endpoint;
int timeoutMilliseconds{30000};
QSslConfiguration sslConfiguration;
};
class EETCPPSHARED_EXPORT EetV4Client
{
public:
explicit EetV4Client(EetV4ClientConfig config);
bool sendSale(const EetV4SaleRequest &request,
const QString &pkcs12Path,
const QByteArray &pkcs12Password,
EetV4Response *response,
QString *error = nullptr) const;
private:
EetV4ClientConfig m_config;
};
#endif // EETV4CLIENT_H
+118
View File
@@ -0,0 +1,118 @@
#include "eetv4request.h"
#include <QRegularExpression>
namespace
{
void addError(QStringList *errors, const QString &message)
{
if (errors != nullptr)
{
errors->append(message);
}
}
bool isValidEic(const QString &value)
{
static const QRegularExpression expression(QStringLiteral("^CZ[0-9]{8,10}$"));
return expression.match(value).hasMatch();
}
bool isValidRestrictedString(const QString &value, int maxLength)
{
static const QRegularExpression expression(QStringLiteral("^[0-9A-Za-z.,:;/#_\\- ]+$"));
return value.size() >= 1 && value.size() <= maxLength && expression.match(value).hasMatch();
}
}
EetV4Amount::EetV4Amount(qint64 cents)
: m_cents(cents)
{
}
bool EetV4Amount::fromString(const QString &value, EetV4Amount *amount, QString *error)
{
static const QRegularExpression expression(
QStringLiteral("^(?:(?:0|-?[1-9][0-9]{0,7})\\.[0-9]{2}|-0\\.(?:0[1-9]|[1-9][0-9]))$"));
if (!expression.match(value).hasMatch())
{
if (error != nullptr)
{
*error = QStringLiteral("Amount must contain two decimal places and fit the EET range.");
}
return false;
}
const bool negative = value.startsWith('-');
const QString absolute = negative ? value.mid(1) : value;
const QStringList parts = absolute.split('.');
const qint64 whole = parts.at(0).toLongLong();
const qint64 cents = whole * 100 + parts.at(1).toLongLong();
if (amount != nullptr)
{
*amount = EetV4Amount(negative ? -cents : cents);
}
return true;
}
qint64 EetV4Amount::cents() const
{
return m_cents;
}
QString EetV4Amount::toString() const
{
const bool negative = m_cents < 0;
const qint64 absolute = negative ? -m_cents : m_cents;
return QStringLiteral("%1%2.%3")
.arg(negative ? QStringLiteral("-") : QString())
.arg(absolute / 100)
.arg(absolute % 100, 2, 10, QLatin1Char('0'));
}
bool EetV4SaleRequest::validate(QStringList *errors) const
{
bool valid = true;
const auto require = [&valid, errors](bool condition, const QString &message) {
if (!condition)
{
valid = false;
addError(errors, message);
}
};
require(!messageUuid.isNull(), QStringLiteral("messageUuid must not be null."));
require(sentAt.isValid() && sentAt.timeSpec() != Qt::LocalTime,
QStringLiteral("sentAt must be a valid timestamp with an explicit offset."));
require(saleAt.isValid() && saleAt.timeSpec() != Qt::LocalTime,
QStringLiteral("saleAt must be a valid timestamp with an explicit offset."));
require(isValidEic(taxpayerEic),
QStringLiteral("taxpayerEic must match CZ followed by 8 to 10 digits."));
require(authorizingTaxpayerEic.isEmpty() || isValidEic(authorizingTaxpayerEic),
QStringLiteral("authorizingTaxpayerEic has an invalid format."));
require(!multipleTaxpayerAuthorization || !authorizingTaxpayerEic.isEmpty(),
QStringLiteral("authorizingTaxpayerEic is required for multiple taxpayer authorization."));
require(unitId >= 1 && unitId <= 999999999,
QStringLiteral("unitId must be between 1 and 999999999."));
require(isValidRestrictedString(cashRegisterId, 20),
QStringLiteral("cashRegisterId has an invalid format or length."));
require(isValidRestrictedString(receiptNumber, 25),
QStringLiteral("receiptNumber has an invalid format or length."));
return valid;
}
QString EetV4SaleRequest::formatDateTime(const QDateTime &dateTime)
{
const int offset = dateTime.offsetFromUtc();
const QChar sign = offset < 0 ? QLatin1Char('-') : QLatin1Char('+');
const int absoluteOffset = qAbs(offset);
return dateTime.toString(QStringLiteral("yyyy-MM-dd'T'HH:mm:ss"))
+ sign
+ QStringLiteral("%1:%2")
.arg(absoluteOffset / 3600, 2, 10, QLatin1Char('0'))
.arg((absoluteOffset % 3600) / 60, 2, 10, QLatin1Char('0'));
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef EETV4REQUEST_H
#define EETV4REQUEST_H
#include <QDateTime>
#include <QString>
#include <QStringList>
#include <QUuid>
#include "eetcpp_global.h"
class EETCPPSHARED_EXPORT EetV4Amount
{
public:
EetV4Amount() = default;
static bool fromString(const QString &value, EetV4Amount *amount, QString *error = nullptr);
qint64 cents() const;
QString toString() const;
private:
explicit EetV4Amount(qint64 cents);
qint64 m_cents{0};
};
class EETCPPSHARED_EXPORT EetV4SaleRequest
{
public:
QUuid messageUuid{QUuid::createUuid()};
QDateTime sentAt;
QDateTime saleAt;
bool firstSubmission{true};
bool verificationMode{false};
QString taxpayerEic;
QString authorizingTaxpayerEic;
bool multipleTaxpayerAuthorization{false};
qint32 unitId{0};
QString cashRegisterId;
QString receiptNumber;
EetV4Amount totalSale;
EetV4Amount intendedSettlement;
EetV4Amount settlement;
bool hasIntendedSettlement{false};
bool hasSettlement{false};
bool validate(QStringList *errors = nullptr) const;
static QString formatDateTime(const QDateTime &dateTime);
};
#endif // EETV4REQUEST_H
+310
View File
@@ -0,0 +1,310 @@
#include "eetv4response.h"
#include <QRegularExpression>
#include <QXmlStreamReader>
namespace
{
constexpr auto SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
constexpr auto EetNamespace = "http://fs.gov.cz/eet/schema/v4";
void setError(QString *error, const QString &message)
{
if (error != nullptr)
{
*error = message;
}
}
bool isElement(const QXmlStreamReader &reader, const char *name, const char *namespaceUri)
{
return reader.isStartElement() &&
reader.name() == QLatin1String(name) &&
reader.namespaceUri() == QLatin1String(namespaceUri);
}
bool parseBoolean(const QString &value, bool *result)
{
if (value == QLatin1String("true") || value == QLatin1String("1"))
{
*result = true;
return true;
}
if (value == QLatin1String("false") || value == QLatin1String("0"))
{
*result = false;
return true;
}
return false;
}
bool parseDateTime(const QString &value, QDateTime *result)
{
const QDateTime parsed = QDateTime::fromString(value, Qt::ISODate);
if (!parsed.isValid() || value.size() < 20 ||
!(value.endsWith(QLatin1Char('Z')) || value.at(value.size() - 6) == QLatin1Char('+') ||
value.at(value.size() - 6) == QLatin1Char('-')))
{
return false;
}
*result = parsed;
return true;
}
bool parseUuid(const QString &value)
{
static const QRegularExpression expression(
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-"
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"));
return expression.match(value).hasMatch();
}
bool parsePok(const QString &value)
{
static const QRegularExpression expression(
QStringLiteral("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-"
"[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}-[0-9a-fA-F]{2}$"));
return expression.match(value).hasMatch();
}
bool parseResponseBody(QXmlStreamReader &reader, EetV4Response *response, QString *error)
{
bool hasHeader = false;
bool hasResult = false;
while (reader.readNextStartElement())
{
if (isElement(reader, "Hlavicka", EetNamespace))
{
if (hasHeader)
{
setError(error, QStringLiteral("EET response contains duplicate Hlavicka."));
return false;
}
hasHeader = true;
const auto attributes = reader.attributes();
const QString uuid = attributes.value(QStringLiteral("uuid_zpravy")).toString();
if (!uuid.isEmpty() && !parseUuid(uuid))
{
setError(error, QStringLiteral("EET response contains an invalid message UUID."));
return false;
}
response->messageUuid = uuid;
const QString receivedAt = attributes.value(QStringLiteral("dat_prij")).toString();
const QString rejectedAt = attributes.value(QStringLiteral("dat_odmit")).toString();
if (!receivedAt.isEmpty() && !parseDateTime(receivedAt, &response->receivedAt))
{
setError(error, QStringLiteral("EET response contains an invalid receipt timestamp."));
return false;
}
if (!rejectedAt.isEmpty() && !parseDateTime(rejectedAt, &response->rejectedAt))
{
setError(error, QStringLiteral("EET response contains an invalid rejection timestamp."));
return false;
}
reader.skipCurrentElement();
}
else if (isElement(reader, "Potvrzeni", EetNamespace))
{
if (hasResult)
{
setError(error, QStringLiteral("EET response contains multiple results."));
return false;
}
hasResult = true;
const auto attributes = reader.attributes();
const QString pok = attributes.value(QStringLiteral("pok")).toString();
if (!parsePok(pok))
{
setError(error, QStringLiteral("EET response contains an invalid POK."));
return false;
}
response->pok = pok;
response->status = EetV4ResponseStatus::Success;
const QString test = attributes.value(QStringLiteral("test")).toString();
if (!test.isEmpty() && !parseBoolean(test, &response->test))
{
setError(error, QStringLiteral("EET response contains an invalid test flag."));
return false;
}
reader.skipCurrentElement();
}
else if (isElement(reader, "Chyba", EetNamespace))
{
if (hasResult)
{
setError(error, QStringLiteral("EET response contains multiple results."));
return false;
}
hasResult = true;
const auto attributes = reader.attributes();
bool ok = false;
const int code = attributes.value(QStringLiteral("kod")).toInt(&ok);
if (!ok || code < -999 || code > 999)
{
setError(error, QStringLiteral("EET response contains an invalid error code."));
return false;
}
response->errorCode = code;
response->message = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
response->status = EetV4ResponseStatus::Error;
const QString test = attributes.value(QStringLiteral("test")).toString();
if (!test.isEmpty() && !parseBoolean(test, &response->test))
{
setError(error, QStringLiteral("EET response contains an invalid test flag."));
return false;
}
}
else if (isElement(reader, "Varovani", EetNamespace))
{
bool ok = false;
const int code = reader.attributes().value(QStringLiteral("kod_varov")).toInt(&ok);
if (!ok || code < 1 || code > 999)
{
setError(error, QStringLiteral("EET response contains an invalid warning code."));
return false;
}
response->warningCodes.append(code);
reader.skipCurrentElement();
}
else
{
reader.skipCurrentElement();
}
}
if (!hasHeader || !hasResult)
{
setError(error, QStringLiteral("EET response is missing Hlavicka or a result."));
return false;
}
return true;
}
bool parseFault(QXmlStreamReader &reader, EetV4Response *response, QString *error)
{
QString code;
QString reason;
QString detail;
while (reader.readNextStartElement())
{
if (reader.name() == QLatin1String("faultcode") ||
reader.name() == QLatin1String("Value"))
{
code = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else if (reader.name() == QLatin1String("faultstring") ||
reader.name() == QLatin1String("Text"))
{
reason = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else if (reader.name() == QLatin1String("Detail"))
{
detail = reader.readElementText(QXmlStreamReader::IncludeChildElements).trimmed();
}
else
{
reader.skipCurrentElement();
}
}
response->status = EetV4ResponseStatus::SoapFault;
response->message = reason.isEmpty() ? detail : reason;
if (!code.isEmpty())
{
response->message.prepend(code + QStringLiteral(": "));
}
if (response->message.isEmpty())
{
setError(error, QStringLiteral("SOAP Fault does not contain a reason."));
return false;
}
return true;
}
}
bool EetV4ResponseParser::parseSoap(const QByteArray &soap,
EetV4Response *response,
QString *error)
{
if (response == nullptr)
{
setError(error, QStringLiteral("A response output object is required."));
return false;
}
*response = {};
QXmlStreamReader reader(soap);
bool inBody = false;
bool hasPayload = false;
while (!reader.atEnd())
{
reader.readNext();
if (!reader.isStartElement())
{
continue;
}
if (reader.name() == QLatin1String("Envelope"))
{
if (reader.namespaceUri() != QLatin1String(SoapNamespace))
{
setError(error, QStringLiteral("SOAP Envelope has an invalid namespace."));
return false;
}
}
else if (isElement(reader, "Body", SoapNamespace))
{
if (inBody)
{
setError(error, QStringLiteral("SOAP response contains duplicate Body."));
return false;
}
inBody = true;
while (reader.readNextStartElement())
{
if (hasPayload)
{
setError(error, QStringLiteral("SOAP Body contains multiple payloads."));
return false;
}
hasPayload = true;
if (isElement(reader, "Odpoved", EetNamespace))
{
if (!parseResponseBody(reader, response, error))
{
return false;
}
}
else if (reader.name() == QLatin1String("Fault") &&
reader.namespaceUri() == QLatin1String(SoapNamespace))
{
if (!parseFault(reader, response, error))
{
return false;
}
}
else
{
setError(error, QStringLiteral("SOAP Body contains an unknown payload."));
return false;
}
}
}
else if (reader.name() == QLatin1String("Header") &&
reader.namespaceUri() == QLatin1String(SoapNamespace))
{
reader.skipCurrentElement();
}
else
{
reader.skipCurrentElement();
}
}
if (reader.hasError())
{
setError(error, reader.errorString());
return false;
}
if (!inBody || !hasPayload)
{
setError(error, QStringLiteral("SOAP response does not contain a payload."));
return false;
}
return true;
}
+43
View File
@@ -0,0 +1,43 @@
#ifndef EETV4RESPONSE_H
#define EETV4RESPONSE_H
#include <QDateTime>
#include <QList>
#include <QString>
#include "eetcpp_global.h"
enum class EetV4ResponseStatus
{
Success,
Error,
SoapFault
};
struct EETCPPSHARED_EXPORT EetV4Response
{
EetV4ResponseStatus status = EetV4ResponseStatus::Error;
QString message;
QString messageUuid;
QDateTime receivedAt;
QDateTime rejectedAt;
QString pok;
int errorCode = 0;
bool test = false;
QList<int> warningCodes;
bool isSuccess() const
{
return status == EetV4ResponseStatus::Success;
}
};
class EETCPPSHARED_EXPORT EetV4ResponseParser
{
public:
static bool parseSoap(const QByteArray &soap,
EetV4Response *response,
QString *error = nullptr);
};
#endif // EETV4RESPONSE_H
+413
View File
@@ -0,0 +1,413 @@
#include "eetv4signer.h"
#include "eetv4soap.h"
#include <QFile>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <openssl/pkcs12.h>
#include <openssl/err.h>
#include <openssl/crypto.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/provider.h>
#endif
#include <openssl/x509.h>
#include <xmlsec/app.h>
#include <xmlsec/crypto.h>
#include <xmlsec/templates.h>
#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/openssl/crypto.h>
#include <mutex>
namespace
{
constexpr auto SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";
constexpr auto XmlDsigNamespace = "http://www.w3.org/2000/09/xmldsig#";
constexpr auto WsseNamespace = "http://docs.oasis-open.org/wss/2004/01/"
"oasis-200401-wss-wssecurity-secext-1.0.xsd";
constexpr auto WsuNamespace = "http://docs.oasis-open.org/wss/2004/01/"
"oasis-200401-wss-wssecurity-utility-1.0.xsd";
constexpr auto X509ValueType = "http://docs.oasis-open.org/wss/2004/01/"
"oasis-200401-wss-x509-token-profile-1.0#X509v3";
constexpr auto Base64Encoding = "http://docs.oasis-open.org/wss/2004/01/"
"oasis-200401-wss-soap-message-security-1.0#Base64Binary";
constexpr auto BodyId = "Id";
constexpr auto CertificateId = "eet-certificate";
bool initializeXmlSec()
{
OSSL_PROVIDER_load(nullptr, "default");
return xmlSecCheckVersion() == 1 &&
xmlSecInit() >= 0 &&
xmlSecOpenSSLAppInit(nullptr) >= 0 &&
xmlSecCryptoInit() >= 0;
}
bool ensureXmlSecInitialized()
{
static std::once_flag flag;
static bool initialized = false;
std::call_once(flag, [] { initialized = initializeXmlSec(); });
return initialized;
}
void ensureOpenSslProvider()
{
static std::once_flag flag;
std::call_once(flag, [] {
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
OSSL_PROVIDER_load(nullptr, "default");
OSSL_PROVIDER_load(nullptr, "legacy");
#endif
});
}
void setError(QString *error, const QString &message)
{
if (error != nullptr)
{
*error = message;
}
}
QByteArray certificateDer(const QByteArray &pkcs12, const QByteArray &password, QString *error)
{
BIO *bio = BIO_new_mem_buf(pkcs12.constData(), pkcs12.size());
if (bio == nullptr)
{
setError(error, QStringLiteral("Cannot create a PKCS#12 memory buffer."));
return {};
}
PKCS12 *container = d2i_PKCS12_bio(bio, nullptr);
BIO_free(bio);
if (container == nullptr)
{
setError(error, QStringLiteral("The PKCS#12 file is invalid."));
return {};
}
EVP_PKEY *privateKey = nullptr;
X509 *certificate = nullptr;
STACK_OF(X509) *caCertificates = nullptr;
const bool parsed = PKCS12_parse(container, password.constData(), &privateKey,
&certificate, &caCertificates) == 1;
PKCS12_free(container);
EVP_PKEY_free(privateKey);
sk_X509_pop_free(caCertificates, X509_free);
if (!parsed || certificate == nullptr)
{
X509_free(certificate);
char buffer[256] = {};
const unsigned long reason = ERR_get_error();
if (reason != 0)
{
ERR_error_string_n(reason, buffer, sizeof(buffer));
}
setError(error, QStringLiteral("The PKCS#12 file cannot be opened with the supplied password (%1).")
.arg(QString::fromLatin1(buffer)));
return {};
}
const int length = i2d_X509(certificate, nullptr);
QByteArray der;
if (length > 0)
{
der.resize(length);
unsigned char *destination = reinterpret_cast<unsigned char *>(der.data());
if (i2d_X509(certificate, &destination) != length)
{
der.clear();
}
}
X509_free(certificate);
if (der.isEmpty())
{
setError(error, QStringLiteral("The certificate could not be encoded."));
}
return der;
}
xmlNodePtr findChild(xmlNodePtr parent, const char *name, const char *namespaceUri)
{
for (xmlNodePtr child = parent == nullptr ? nullptr : parent->children;
child != nullptr; child = child->next)
{
if (child->type == XML_ELEMENT_NODE &&
xmlStrEqual(child->name, BAD_CAST name) &&
child->ns != nullptr &&
xmlStrEqual(child->ns->href, BAD_CAST namespaceUri))
{
return child;
}
}
return nullptr;
}
xmlNodePtr findDescendant(xmlNodePtr parent, const char *name, const char *namespaceUri)
{
if (parent == nullptr)
{
return nullptr;
}
for (xmlNodePtr child = parent->children; child != nullptr; child = child->next)
{
if (child->type == XML_ELEMENT_NODE &&
xmlStrEqual(child->name, BAD_CAST name) &&
child->ns != nullptr &&
xmlStrEqual(child->ns->href, BAD_CAST namespaceUri))
{
return child;
}
if (xmlNodePtr descendant = findDescendant(child, name, namespaceUri);
descendant != nullptr)
{
return descendant;
}
}
return nullptr;
}
}
QByteArray EetV4Signer::signSoap(const QByteArray &unsignedSoap,
const QString &pkcs12Path,
const QByteArray &pkcs12Password,
QString *error)
{
if (unsignedSoap.isEmpty() || pkcs12Path.isEmpty())
{
setError(error, QStringLiteral("SOAP data and PKCS#12 path are required."));
return {};
}
ensureOpenSslProvider();
QFile pkcs12File(pkcs12Path);
if (!pkcs12File.open(QIODevice::ReadOnly))
{
setError(error, QStringLiteral("Cannot read the PKCS#12 file."));
return {};
}
const QByteArray pkcs12 = pkcs12File.readAll();
const QByteArray certificate = certificateDer(pkcs12, pkcs12Password, error);
if (certificate.isEmpty())
{
return {};
}
if (!ensureXmlSecInitialized())
{
setError(error, QStringLiteral("XML security initialization failed."));
return {};
}
xmlDocPtr document = xmlReadMemory(unsignedSoap.constData(), unsignedSoap.size(),
nullptr, "UTF-8", XML_PARSE_NONET);
if (document == nullptr)
{
setError(error, QStringLiteral("The unsigned SOAP document is not valid XML."));
return {};
}
QByteArray result;
xmlNodePtr envelope = xmlDocGetRootElement(document);
xmlNodePtr header = findChild(envelope, "Header", SoapNamespace);
xmlNodePtr body = findChild(envelope, "Body", SoapNamespace);
if (header == nullptr || body == nullptr)
{
setError(error, QStringLiteral("SOAP Envelope must contain Header and Body."));
xmlFreeDoc(document);
return {};
}
xmlAttrPtr bodyIdAttribute = xmlHasNsProp(body, BAD_CAST BodyId, BAD_CAST WsuNamespace);
if (bodyIdAttribute == nullptr || bodyIdAttribute->children == nullptr)
{
setError(error, QStringLiteral("SOAP Body does not contain a wsu:Id."));
xmlFreeDoc(document);
return {};
}
QByteArray bodyReference = QByteArray("#") +
QByteArray(reinterpret_cast<const char *>(
bodyIdAttribute->children->content));
const xmlChar *ids[] = { BAD_CAST BodyId, nullptr };
xmlSecAddIDs(document, nullptr, ids);
xmlNsPtr wsse = xmlSearchNsByHref(document, envelope, BAD_CAST WsseNamespace);
xmlNsPtr wsu = xmlSearchNsByHref(document, envelope, BAD_CAST WsuNamespace);
if (wsse == nullptr || wsu == nullptr)
{
setError(error, QStringLiteral("SOAP security namespaces are missing."));
xmlFreeDoc(document);
return {};
}
xmlNodePtr security = xmlNewNode(wsse, BAD_CAST "Security");
xmlNodePtr token = xmlNewChild(security, wsse, BAD_CAST "BinarySecurityToken",
BAD_CAST certificate.toBase64().constData());
xmlSetNsProp(token, wsu, BAD_CAST "Id", BAD_CAST CertificateId);
xmlNewProp(token, BAD_CAST "EncodingType", BAD_CAST Base64Encoding);
xmlNewProp(token, BAD_CAST "ValueType", BAD_CAST X509ValueType);
xmlAddChild(header, security);
xmlNodePtr signature = xmlSecTmplSignatureCreateNsPref(
document, xmlSecTransformExclC14NId, xmlSecTransformRsaSha256Id,
nullptr, BAD_CAST "ds");
if (signature == nullptr)
{
setError(error, QStringLiteral("Cannot create the XML signature template."));
xmlFreeDoc(document);
return {};
}
xmlNodePtr reference = xmlSecTmplSignatureAddReference(
signature, xmlSecTransformSha256Id, nullptr,
BAD_CAST bodyReference.constData(), nullptr);
if (reference == nullptr ||
xmlSecTmplReferenceAddTransform(reference, xmlSecTransformExclC14NId) == nullptr)
{
setError(error, QStringLiteral("Cannot create the SOAP Body signature reference."));
xmlFreeDoc(document);
return {};
}
xmlNodePtr keyInfo = xmlSecTmplSignatureEnsureKeyInfo(signature, nullptr);
xmlNodePtr tokenReference = xmlNewChild(keyInfo, wsse, BAD_CAST "SecurityTokenReference", nullptr);
xmlNodePtr tokenReferenceValue = xmlNewChild(tokenReference, wsse, BAD_CAST "Reference", nullptr);
xmlNewProp(tokenReferenceValue, BAD_CAST "URI", BAD_CAST "#eet-certificate");
xmlNewProp(tokenReferenceValue, BAD_CAST "ValueType", BAD_CAST X509ValueType);
xmlAddChild(security, signature);
xmlSecKeyPtr key = xmlSecCryptoAppPkcs12LoadMemory(
reinterpret_cast<const xmlSecByte *>(pkcs12.constData()), pkcs12.size(),
pkcs12Password.constData(), nullptr, nullptr);
if (key == nullptr)
{
setError(error, QStringLiteral("XML security could not load the PKCS#12 key."));
xmlFreeDoc(document);
return {};
}
xmlSecDSigCtxPtr context = xmlSecDSigCtxCreate(nullptr);
if (context == nullptr)
{
setError(error, QStringLiteral("Cannot create the XML signature context."));
xmlSecKeyDestroy(key);
xmlFreeDoc(document);
return {};
}
context->signKey = key;
const bool signedSuccessfully = xmlSecDSigCtxSign(context, signature) == 0;
xmlSecDSigCtxDestroy(context);
if (!signedSuccessfully)
{
setError(error, QStringLiteral("XML security could not sign the SOAP Body."));
xmlFreeDoc(document);
return {};
}
xmlChar *serialized = nullptr;
int serializedSize = 0;
xmlDocDumpMemoryEnc(document, &serialized, &serializedSize, "UTF-8");
if (serialized != nullptr && serializedSize > 0)
{
result = QByteArray(reinterpret_cast<const char *>(serialized), serializedSize);
}
xmlFree(serialized);
xmlFreeDoc(document);
if (result.isEmpty())
{
setError(error, QStringLiteral("Cannot serialize the signed SOAP document."));
}
else if (result.size() > EetV4Soap::MaximumMessageSize)
{
result.clear();
setError(error, QStringLiteral("Signed SOAP message exceeds the 12 KiB EET limit."));
}
return result;
}
bool EetV4Signer::verifySoap(const QByteArray &signedSoap, QString *error)
{
if (signedSoap.isEmpty())
{
setError(error, QStringLiteral("Signed SOAP data is required."));
return false;
}
ensureOpenSslProvider();
if (!ensureXmlSecInitialized())
{
setError(error, QStringLiteral("XML security initialization failed."));
return false;
}
xmlDocPtr document = xmlReadMemory(signedSoap.constData(), signedSoap.size(),
nullptr, "UTF-8", XML_PARSE_NONET);
if (document == nullptr)
{
setError(error, QStringLiteral("The signed SOAP document is not valid XML."));
return false;
}
xmlNodePtr envelope = xmlDocGetRootElement(document);
xmlNodePtr body = findChild(envelope, "Body", SoapNamespace);
xmlNodePtr signature = findDescendant(envelope, "Signature", XmlDsigNamespace);
xmlNodePtr token = findDescendant(envelope, "BinarySecurityToken", WsseNamespace);
if (body == nullptr || signature == nullptr || token == nullptr)
{
setError(error, QStringLiteral("Signed SOAP is missing Body, Signature, or certificate."));
xmlFreeDoc(document);
return false;
}
xmlAttrPtr bodyId = xmlHasNsProp(body, BAD_CAST BodyId, BAD_CAST WsuNamespace);
if (bodyId == nullptr || bodyId->children == nullptr)
{
setError(error, QStringLiteral("SOAP Body does not contain a wsu:Id."));
xmlFreeDoc(document);
return false;
}
const xmlChar *ids[] = { BAD_CAST BodyId, nullptr };
xmlSecAddIDs(document, nullptr, ids);
xmlChar *tokenContent = xmlNodeGetContent(token);
const QByteArray certificate = tokenContent == nullptr
? QByteArray()
: QByteArray::fromBase64(
QByteArray(reinterpret_cast<const char *>(tokenContent)));
xmlFree(tokenContent);
if (certificate.isEmpty())
{
setError(error, QStringLiteral("The embedded certificate is invalid."));
xmlFreeDoc(document);
return false;
}
xmlSecKeyPtr key = xmlSecCryptoAppKeyLoadMemory(
reinterpret_cast<const xmlSecByte *>(certificate.constData()), certificate.size(),
xmlSecKeyDataFormatCertDer, nullptr, nullptr, nullptr);
if (key == nullptr)
{
setError(error, QStringLiteral("XML security could not load the embedded certificate."));
xmlFreeDoc(document);
return false;
}
xmlSecDSigCtxPtr context = xmlSecDSigCtxCreate(nullptr);
if (context == nullptr)
{
setError(error, QStringLiteral("Cannot create the XML verification context."));
xmlSecKeyDestroy(key);
xmlFreeDoc(document);
return false;
}
context->signKey = key;
const bool verified = xmlSecDSigCtxVerify(context, signature) == 0 &&
context->status == xmlSecDSigStatusSucceeded;
xmlSecDSigCtxDestroy(context);
xmlFreeDoc(document);
if (!verified)
{
setError(error, QStringLiteral("SOAP XML signature verification failed."));
}
return verified;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef EETV4SIGNER_H
#define EETV4SIGNER_H
#include <QByteArray>
#include <QString>
#include "eetcpp_global.h"
class EETCPPSHARED_EXPORT EetV4Signer
{
public:
static QByteArray signSoap(const QByteArray &unsignedSoap,
const QString &pkcs12Path,
const QByteArray &pkcs12Password,
QString *error = nullptr);
static bool verifySoap(const QByteArray &signedSoap,
QString *error = nullptr);
};
#endif // EETV4SIGNER_H
+68
View File
@@ -0,0 +1,68 @@
#include "eetv4soap.h"
#include "eetv4xml.h"
#include <QRegularExpression>
namespace
{
QByteArray withoutXmlDeclaration(const QByteArray &xml)
{
const int declarationEnd = xml.indexOf("?>");
return declarationEnd >= 0 ? xml.mid(declarationEnd + 2).trimmed() : xml.trimmed();
}
bool isValidBodyId(const QByteArray &bodyId)
{
static const QRegularExpression expression(QStringLiteral("^[A-Za-z_][A-Za-z0-9_.-]{0,63}$"));
return expression.match(QString::fromLatin1(bodyId)).hasMatch();
}
}
QByteArray EetV4Soap::serializeUnsigned(const EetV4SaleRequest &request,
const QByteArray &bodyId,
QString *error)
{
if (!isValidBodyId(bodyId))
{
if (error != nullptr)
{
*error = QStringLiteral("SOAP Body ID has an invalid format.");
}
return {};
}
QString saleError;
const QByteArray saleXml = EetV4Xml::serializeSale(request, &saleError);
if (saleXml.isEmpty())
{
if (error != nullptr)
{
*error = saleError;
}
return {};
}
QByteArray output;
output.reserve(saleXml.size() + 512);
output.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
output.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\""
" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">");
output.append("<soap:Header/>");
output.append("<soap:Body wsu:Id=\"");
output.append(bodyId);
output.append("\">");
output.append(withoutXmlDeclaration(saleXml));
output.append("</soap:Body></soap:Envelope>");
if (output.size() > MaximumMessageSize)
{
if (error != nullptr)
{
*error = QStringLiteral("SOAP message exceeds the 12 KiB EET limit.");
}
return {};
}
return output;
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef EETV4SOAP_H
#define EETV4SOAP_H
#include <QByteArray>
#include <QString>
#include "eetcpp_global.h"
#include "eetv4request.h"
class EETCPPSHARED_EXPORT EetV4Soap
{
public:
static constexpr int MaximumMessageSize = 12 * 1024;
static QByteArray serializeUnsigned(const EetV4SaleRequest &request,
const QByteArray &bodyId = QByteArrayLiteral("id-body"),
QString *error = nullptr);
};
#endif // EETV4SOAP_H
+79
View File
@@ -0,0 +1,79 @@
#include "eetv4xml.h"
#include <QXmlStreamWriter>
namespace
{
QString booleanValue(bool value)
{
return value ? QStringLiteral("true") : QStringLiteral("false");
}
}
QByteArray EetV4Xml::serializeSale(const EetV4SaleRequest &request, QString *error)
{
QStringList validationErrors;
if (!request.validate(&validationErrors))
{
if (error != nullptr)
{
*error = validationErrors.join(QStringLiteral(" "));
}
return {};
}
QByteArray output;
QXmlStreamWriter writer(&output);
writer.setAutoFormatting(false);
writer.writeStartDocument(QStringLiteral("1.0"));
writer.writeStartElement(QStringLiteral("Trzba"));
writer.writeDefaultNamespace(QStringLiteral("http://fs.gov.cz/eet/schema/v4"));
writer.writeStartElement(QStringLiteral("Hlavicka"));
writer.writeAttribute(QStringLiteral("uuid_zpravy"), request.messageUuid.toString(QUuid::WithoutBraces));
writer.writeAttribute(QStringLiteral("dat_odesl"), EetV4SaleRequest::formatDateTime(request.sentAt));
writer.writeAttribute(QStringLiteral("prvni_zaslani"), booleanValue(request.firstSubmission));
if (request.verificationMode)
{
writer.writeAttribute(QStringLiteral("overeni"), QStringLiteral("true"));
}
writer.writeEndElement();
writer.writeStartElement(QStringLiteral("Data"));
writer.writeAttribute(QStringLiteral("eic_popl"), request.taxpayerEic);
if (!request.authorizingTaxpayerEic.isEmpty())
{
writer.writeAttribute(QStringLiteral("eic_poverujiciho"), request.authorizingTaxpayerEic);
}
if (request.multipleTaxpayerAuthorization)
{
writer.writeAttribute(QStringLiteral("povereni_vice_popl"), QStringLiteral("true"));
}
writer.writeAttribute(QStringLiteral("id_jednotky"), QString::number(request.unitId));
writer.writeAttribute(QStringLiteral("id_pokl"), request.cashRegisterId);
writer.writeAttribute(QStringLiteral("porad_cis"), request.receiptNumber);
writer.writeAttribute(QStringLiteral("dat_trzby"), EetV4SaleRequest::formatDateTime(request.saleAt));
writer.writeAttribute(QStringLiteral("celk_trzba"), request.totalSale.toString());
if (request.hasIntendedSettlement)
{
writer.writeAttribute(QStringLiteral("urceno_cerp_zuct"), request.intendedSettlement.toString());
}
if (request.hasSettlement)
{
writer.writeAttribute(QStringLiteral("cerp_zuct"), request.settlement.toString());
}
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
if (writer.hasError())
{
if (error != nullptr)
{
*error = QStringLiteral("Failed to serialize the EET v4 XML document.");
}
return {};
}
return output;
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef EETV4XML_H
#define EETV4XML_H
#include <QByteArray>
#include <QString>
#include "eetcpp_global.h"
#include "eetv4request.h"
class EETCPPSHARED_EXPORT EetV4Xml
{
public:
static QByteArray serializeSale(const EetV4SaleRequest &request, QString *error = nullptr);
};
#endif // EETV4XML_H
-54
View File
@@ -1,54 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-01-07T15:25:00
#
#-------------------------------------------------
win32 {
QCA_PATH = d:/prac/qt/lib/qca
}
QT += network xmlpatterns
QT -= gui
CONFIG += c++11
TARGET = EetCpp
TEMPLATE = lib
DEFINES += EETCPP_LIBRARY
release:DESTDIR = $$PWD/../bin/release
debug:DESTDIR = $$PWD/../bin/debug
SOURCES += eetcpp.cpp \
eetrequest.cpp \
eettemplate.cpp \
eetsender.cpp \
eetsigner.cpp \
eetresult.cpp
HEADERS += eetcpp.h\
eetcpp_global.h \
eetrequest.h \
eettemplate.h \
eetsender.h \
eetsigner.h \
eetresult.h
unix {
target.path = /usr/lib
INSTALLS += target
}
win32 {
INCLUDEPATH += $$QCA_PATH/include
}
win32:CONFIG(release, debug|release): LIBS += -L$$QCA_PATH -lqca-qt5
else:win32:CONFIG(debug, debug|release): LIBS += -L$$QCA_PATH -lqca-qt5d
else:unix: LIBS += -lqca-qt5
RESOURCES += \
resource.qrc
-10
View File
@@ -1,10 +0,0 @@
<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TheBody" xml:id="TheBody">
<eet:Trzba xmlns:eet="http://fs.mfcr.cz/eet/schema/v3">
<eet:Hlavicka @{dat_odesl} @{overeni} @{prvni_zaslani} @{uuid_zpravy}></eet:Hlavicka>
<eet:Data @{celk_trzba} @{cerp_zuct} @{cest_sluz} @{dan1} @{dan2} @{dan3} @{dat_trzby} @{dic_popl} @{dic_poverujiciho} @{id_pokl} @{id_provoz} @{porad_cis} @{pouzit_zboz1} @{pouzit_zboz2} @{pouzit_zboz3} @{rezim} @{urceno_cerp_zuct} @{zakl_dan1} @{zakl_dan2} @{zakl_dan3} @{zakl_nepodl_dph}></eet:Data>
<eet:KontrolniKody>
<eet:pkp cipher="RSA2048" digest="SHA256" encoding="base64">${pkp}</eet:pkp>
<eet:bkp digest="SHA1" encoding="base16">${bkp}</eet:bkp>
</eet:KontrolniKody>
</eet:Trzba>
</soap:Body>
-36
View File
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
soap:mustUnderstand="1">
<wsse:BinarySecurityToken
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
wsu:Id="TheCert">${certb64}</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="TheSignature">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#TheBody">
<ds:Transforms>
<ds:Transform Algorithm='http://www.w3.org/2001/10/xml-exc-c14n#'/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>${digest}</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>${signature}</ds:SignatureValue>
<ds:KeyInfo Id="TheKeyInfo">
<wsse:SecurityTokenReference wsu:Id="TheSecurityTokenReference">
<wsse:Reference URI="#TheCert" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
${soap:Body}
</soap:Envelope>
-13
View File
@@ -1,13 +0,0 @@
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"></ec:InclusiveNamespaces>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod>
<ds:Reference URI="#TheBody">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>${digest}</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
-7
View File
@@ -1,7 +0,0 @@
<RCC>
<qresource prefix="/">
<file>res/template_body.txt</file>
<file>res/template_request.txt</file>
<file>res/template_signature.txt</file>
</qresource>
</RCC>