diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6882dea5c..396d6d22c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -82,13 +82,24 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ - name: Expose Android NDK env
+ shell: bash
+ run: |
+ echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
+ echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
+ - name: Cache vcpkg
+ uses: actions/cache@v4
+ with:
+ path: ${{ github.workspace }}/vcpkg_cache
+ key: vcpkg-${{ matrix.target }}-${{ hashFiles('.github/workflows/*', 'vcpkg.json', 'vcpkg-ports/**', 'CMakeLists.txt', '**/CMakeLists.txt', 'CMakePresets.json') }}
- name: Prepare vcpkg
uses: lukka/run-vcpkg@v11
with:
- vcpkgGitCommitId: 031ad89ce6c575df35a8e58707ad2c898446c63e
+ vcpkgGitCommitId: 98e7cd3a7ba579efc543f8854af800d033031eae
vcpkgJsonGlob: ./vcpkg.json
runVcpkgInstall: true
env:
+ VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg_cache,readwrite
VCPKG_DEFAULT_TRIPLET: ${{ matrix.triplet }}
- name: Build
run: |
@@ -179,14 +190,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ - name: Cache vcpkg
+ uses: actions/cache@v4
+ with:
+ path: ${{ github.workspace }}/vcpkg_cache
+ key: vcpkg-${{ matrix.toolset }}-${{ matrix.platform }}-${{ hashFiles('.github/workflows/*', 'vcpkg.json', 'vcpkg-ports/**', 'CMakeLists.txt', '**/CMakeLists.txt', 'CMakePresets.json') }}
- name: Prepare vcpkg
uses: lukka/run-vcpkg@v11
with:
- vcpkgGitCommitId: 031ad89ce6c575df35a8e58707ad2c898446c63e
+ vcpkgGitCommitId: 98e7cd3a7ba579efc543f8854af800d033031eae
vcpkgJsonGlob: ./vcpkg.json
runVcpkgInstall: true
runVcpkgFormatString: "[`install`, `--recurse`, `--clean-after-build`, `--x-install-root`, `$[env.VCPKG_INSTALLED_DIR]`, `--triplet`, `$[env.VCPKG_DEFAULT_TRIPLET]`, `--x-feature`, `tests`]"
env:
+ VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg_cache,readwrite
VCPKG_DEFAULT_TRIPLET: ${{ matrix.platform }}-windows
VCPKG_INSTALLED_DIR: ${{ github.workspace }}/vcpkg_installed_${{ matrix.platform }}
- name: Install dependencies
diff --git a/examples/android/build.gradle b/examples/android/build.gradle
index 8c27726bb..f19275001 100644
--- a/examples/android/build.gradle
+++ b/examples/android/build.gradle
@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.12.0'
+ classpath 'com.android.tools.build:gradle:8.12.2'
}
}
diff --git a/examples/ios/libdigidocpp/Info.plist b/examples/ios/libdigidocpp/Info.plist
index ff9a875d5..547dd08f4 100644
--- a/examples/ios/libdigidocpp/Info.plist
+++ b/examples/ios/libdigidocpp/Info.plist
@@ -5,8 +5,6 @@
CFBundleDocumentTypes
- CFBundleTypeIconFiles
-
CFBundleTypeName
DigiDoc signed document
CFBundleTypeRole
diff --git a/libdigidocpp.wxs b/libdigidocpp.wxs
index 918195bd7..306fc336f 100644
--- a/libdigidocpp.wxs
+++ b/libdigidocpp.wxs
@@ -80,10 +80,6 @@
-
-
-
-
@@ -105,7 +101,7 @@
-
+
diff --git a/prepare_osx_build_environment.sh b/prepare_osx_build_environment.sh
index 69e12ed00..ae2fad9a5 100755
--- a/prepare_osx_build_environment.sh
+++ b/prepare_osx_build_environment.sh
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
-OPENSSL_DIR=openssl-3.5.1
+OPENSSL_DIR=openssl-3.5.2
XMLSEC_DIR=xmlsec1-1.3.7
ARGS="$@"
@@ -59,6 +59,7 @@ function xmlsec {
cd ${XMLSEC_DIR}
patch -Np1 -i ../vcpkg-ports/xmlsec/xmlsec1-1.3.5.legacy.patch
patch -Np1 -i ../vcpkg-ports/xmlsec/xmlsec1-1.3.7.rsapss.patch
+ patch -Np1 -i ../vcpkg-ports/xmlsec/xmlsec1-1.3.7.ecdsa-sig.patch
case "${ARGS}" in
*iphone*) CONFIGURE="--host=aarch64-apple-darwin --enable-static --disable-shared --without-libxslt" ;;
*) CONFIGURE="--disable-static --enable-shared" ;;
diff --git a/src/XMLDocument.h b/src/XMLDocument.h
index 33dda5a26..33d4bf617 100644
--- a/src/XMLDocument.h
+++ b/src/XMLDocument.h
@@ -30,6 +30,7 @@
#include
#include
#include
+#include
#include
@@ -45,7 +46,7 @@ static std::vector from_base64(std::string_view data)
std::vector result(EVP_DECODE_LENGTH(data.size()), 0);
size_t dataPos = 0;
int size = 0;
- auto ctx = make_unique_ptr(EVP_ENCODE_CTX_new(), EVP_ENCODE_CTX_free);
+ auto ctx = make_unique_ptr(EVP_ENCODE_CTX_new());
EVP_DecodeInit(ctx.get());
for(auto pos = data.find_first_of(whitespace);
@@ -71,7 +72,7 @@ static std::vector from_base64(std::string_view data)
static std::string to_base64(const std::vector &data)
{
std::string result(EVP_ENCODE_LENGTH(data.size()), 0);
- auto ctx = make_unique_ptr(EVP_ENCODE_CTX_new(), EVP_ENCODE_CTX_free);
+ auto ctx = make_unique_ptr(EVP_ENCODE_CTX_new());
EVP_EncodeInit(ctx.get());
int size{};
if(EVP_EncodeUpdate(ctx.get(), (unsigned char*)result.data(), &size, data.data(), int(data.size())) < 1)
@@ -305,19 +306,27 @@ struct XMLDocument: public unique_free_t, public XMLNode
static XMLDocument openStream(std::istream &is, const XMLName &name = {}, bool hugeFile = false)
{
- auto ctxt = make_unique_ptr(xmlCreateIOParserCtxt(nullptr, nullptr, [](void *context, char *buffer, int len) -> int {
+ auto ctxt = make_unique_ptr(xmlCreateIOParserCtxt(nullptr, nullptr, [](void *context, char *buffer, int len) -> int {
auto *is = static_cast(context);
is->read(buffer, len);
return is->good() || is->eof() ? int(is->gcount()) : -1;
- }, nullptr, &is, XML_CHAR_ENCODING_NONE), xmlFreeParserCtxt);
- ctxt->linenumbers = 1;
+ }, nullptr, &is, XML_CHAR_ENCODING_NONE));
+#if VERSION_CHECK(XMLSEC_VERSION_MAJOR, XMLSEC_VERSION_MINOR, XMLSEC_VERSION_SUBMINOR) >= VERSION_CHECK(1, 3, 0)
+ ctxt->options |= xmlSecParserGetDefaultOptions();
+#else
ctxt->options |= XML_PARSE_NOENT|XML_PARSE_DTDLOAD|XML_PARSE_DTDATTR|XML_PARSE_NONET;
+#endif
ctxt->loadsubset |= XML_DETECT_IDS|XML_COMPLETE_ATTRS;
if(hugeFile)
ctxt->options |= XML_PARSE_HUGE;
auto result = xmlParseDocument(ctxt.get());
if(result != 0 || !ctxt->wellFormed)
- THROW("%s", ctxt->lastError.message);
+ {
+ if(const xmlError *lastError = xmlCtxtGetLastError(ctxt.get()))
+ THROW("%s", lastError->message);
+ else
+ THROW("Failed to parse XML document from stream");
+ }
return {ctxt->myDoc, name};
}
@@ -358,11 +367,11 @@ struct XMLDocument: public unique_free_t, public XMLNode
}
else if(!algo.empty())
THROW("Unsupported canonicalization method '%.*s'", int(algo.size()), algo.data());
- auto buf = make_unique_ptr(xmlOutputBufferCreateIO([](void *context, const char *buffer, int len) {
+ auto buf = make_unique_ptr(xmlOutputBufferCreateIO([](void *context, const char *buffer, int len) {
auto *digest = static_cast(context);
digest->update(pcxmlChar(buffer), size_t(len));
return len;
- }, nullptr, const_cast(&digest), nullptr), xmlOutputBufferClose);
+ }, nullptr, const_cast(&digest), nullptr));
int size = xmlC14NExecute(get(), [](void *root, xmlNodePtr node, xmlNodePtr parent) constexpr noexcept {
if(root == node)
return 1;
@@ -396,14 +405,14 @@ struct XMLDocument: public unique_free_t, public XMLNode
void validateSchema(const std::string &schemaPath) const
{
- auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()), xmlSchemaFreeParserCtxt);
+ auto parser = make_unique_ptr(xmlSchemaNewParserCtxt(schemaPath.c_str()));
if(!parser)
THROW("Failed to create schema parser context %s", schemaPath.c_str());
xmlSchemaSetParserErrors(parser.get(), schemaValidationError, schemaValidationWarning, nullptr);
- auto schema = make_unique_ptr(xmlSchemaParse(parser.get()), xmlSchemaFree);
+ auto schema = make_unique_ptr(xmlSchemaParse(parser.get()));
if(!schema)
THROW("Failed to parse schema %s", schemaPath.c_str());
- auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()), xmlSchemaFreeValidCtxt);
+ auto validate = make_unique_ptr(xmlSchemaNewValidCtxt(schema.get()));
if(!validate)
THROW("Failed to create schema validation context %s", schemaPath.c_str());
Exception e(EXCEPTION_PARAMS("Failed to XML with schema"));
@@ -414,12 +423,12 @@ struct XMLDocument: public unique_free_t, public XMLNode
static bool verifySignature(XMLNode signature, [[maybe_unused]] Exception *e = {}) noexcept
{
- auto mngr = make_unique_ptr(xmlSecKeysMngrCreate(), xmlSecKeysMngrDestroy);
+ auto mngr = make_unique_ptr(xmlSecKeysMngrCreate());
if(!mngr)
return false;
if(xmlSecCryptoAppDefaultKeysMngrInit(mngr.get()) < 0)
return false;
- auto ctx = make_unique_ptr(xmlSecDSigCtxCreate(mngr.get()), xmlSecDSigCtxDestroy);
+ auto ctx = make_unique_ptr(xmlSecDSigCtxCreate(mngr.get()));
if(!ctx)
return false;
ctx->keyInfoReadCtx.flags |= XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS;
diff --git a/src/crypto/X509Cert.cpp b/src/crypto/X509Cert.cpp
index a2a2d5f62..6bc7f2fe3 100644
--- a/src/crypto/X509Cert.cpp
+++ b/src/crypto/X509Cert.cpp
@@ -29,15 +29,18 @@
using namespace digidoc;
using namespace std;
+// RFC 3739: NameRegistrationAuthorities is SEQUENCE OF GeneralName
+using NameRegistrationAuthorities = STACK_OF(GENERAL_NAME);
+
/**
* SemanticsInformation ::= SEQUENCE {
* semanticsIdentifier OBJECT IDENTIFIER OPTIONAL,
* nameRegistrationAuthorities NameRegistrationAuthorities OPTIONAL
* }
*/
-using SemanticsInformation = struct SemanticsInformation_st {
+struct SemanticsInformation {
ASN1_OBJECT *semanticsIdentifier;
- //NameRegistrationAuthorities nameRegistrationAuthorities
+ NameRegistrationAuthorities *nameRegistrationAuthorities;
};
DECLARE_ASN1_FUNCTIONS(SemanticsInformation)
@@ -52,7 +55,7 @@ DECLARE_ASN1_FUNCTIONS(QcType)
* statementId OBJECT IDENTIFIER,
* statementInfo ANY DEFINED BY statementId OPTIONAL}
*/
-using QCStatement = struct QCStatement_st {
+struct QCStatement {
ASN1_OBJECT *statementId;
#ifndef TEMPLATE
ASN1_TYPE *statementInfo;
@@ -297,8 +300,7 @@ string X509Cert::serial() const
return {};
if(auto bn = make_unique_ptr(ASN1_INTEGER_to_BN(X509_get_serialNumber(cert.get()), nullptr)))
{
- auto openssl_free = [](char *data) { OPENSSL_free(data); };
- if(auto str = unique_ptr(BN_bn2dec(bn.get()), openssl_free))
+ if(auto str = make_unique_ptr(BN_bn2dec(bn.get()), [](char *data) { OPENSSL_free(data); }))
return str.get();
}
return {};
@@ -316,15 +318,19 @@ string X509Cert::issuerName(const string &obj) const
return toString(obj);
}
+template
+constexpr auto X509Cert::extension(int nid) const noexcept
+{
+ return make_unique_cast(cert ? X509_get_ext_d2i(cert.get(), nid, nullptr, nullptr) : nullptr);
+}
+
/**
* Returns current certificate key usage bits
*/
vector X509Cert::keyUsage() const
{
vector usage;
- if(!cert)
- return usage;
- auto keyusage = make_unique_cast(X509_get_ext_d2i(cert.get(), NID_key_usage, nullptr, nullptr));
+ auto keyusage = extension(NID_key_usage);
if(!keyusage)
return usage;
@@ -342,9 +348,7 @@ vector X509Cert::keyUsage() const
vector X509Cert::certificatePolicies() const
{
vector pol;
- if(!cert)
- return pol;
- auto cp = make_unique_cast(X509_get_ext_d2i(cert.get(), NID_certificate_policies, nullptr, nullptr));
+ auto cp = extension(NID_certificate_policies);
if(!cp)
return pol;
for(int i = 0; i < sk_POLICYINFO_num(cp.get()); ++i)
@@ -375,12 +379,8 @@ vector X509Cert::qcStatements() const
if(oid == QC_SYNTAX2)
{
#ifndef TEMPLATE
- if(!s->statementInfo)
- continue;
- auto si = make_unique_cast(ASN1_item_unpack(s->statementInfo->value.sequence, ASN1_ITEM_rptr(SemanticsInformation)));
- if(!si)
- continue;
- result.push_back(toOID(si->semanticsIdentifier));
+ if(auto si = make_unique_cast(ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(SemanticsInformation), s->statementInfo)))
+ result.push_back(toOID(si->semanticsIdentifier));
#else
result.push_back(toOID(s->statementInfo.semanticsInformation->semanticsIdentifier));
#endif
@@ -388,17 +388,13 @@ vector X509Cert::qcStatements() const
else if(oid == QC_QCT)
{
#ifndef TEMPLATE
- if(!s->statementInfo)
- continue;
- auto qct = make_unique_cast(ASN1_item_unpack(s->statementInfo->value.sequence, ASN1_ITEM_rptr(QcType)));
+ auto qct = make_unique_cast(ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(QcType), s->statementInfo));
if(!qct)
continue;
for(int j = 0; j < sk_ASN1_OBJECT_num(qct.get()); ++j)
- {
result.push_back(toOID(sk_ASN1_OBJECT_value(qct.get(), j)));
#else
#endif
- }
}
else
result.push_back(std::move(oid));
@@ -487,9 +483,7 @@ X509* X509Cert::handle() const
*/
bool X509Cert::isCA() const
{
- if(!cert)
- return false;
- auto cons = make_unique_cast(X509_get_ext_d2i(cert.get(), NID_basic_constraints, nullptr, nullptr));
+ auto cons = extension(NID_basic_constraints);
return cons && cons->ca > 0;
}
@@ -532,7 +526,11 @@ X509Cert& X509Cert::operator =(X509Cert &&other) noexcept = default;
*/
bool X509Cert::operator ==(X509 *other) const
{
- return operator==(X509Cert(other));
+ if(cert.get() == other)
+ return true;
+ if(!cert || !other)
+ return false;
+ return X509_cmp(cert.get(), other) == 0;
}
/**
@@ -540,11 +538,7 @@ bool X509Cert::operator ==(X509 *other) const
*/
bool X509Cert::operator ==(const X509Cert &other) const
{
- if(cert == other.cert)
- return true;
- if(!cert || !other.cert)
- return false;
- return X509_cmp(cert.get(), other.cert.get()) == 0;
+ return operator==(other.cert.get());
}
/**
@@ -556,8 +550,8 @@ bool X509Cert::operator !=(const X509Cert &other) const
}
ASN1_SEQUENCE(SemanticsInformation) = {
- ASN1_OPT(SemanticsInformation, semanticsIdentifier, ASN1_OBJECT)
- //ASN1_OPT(SemanticsInformation, nameRegistrationAuthorities, NameRegistrationAuthorities)
+ ASN1_OPT(SemanticsInformation, semanticsIdentifier, ASN1_OBJECT),
+ ASN1_SEQUENCE_OF_OPT(SemanticsInformation, nameRegistrationAuthorities, GENERAL_NAME)
} ASN1_SEQUENCE_END(SemanticsInformation)
IMPLEMENT_ASN1_FUNCTIONS(SemanticsInformation)
diff --git a/src/crypto/X509Cert.h b/src/crypto/X509Cert.h
index c1b44f341..f6b819628 100644
--- a/src/crypto/X509Cert.h
+++ b/src/crypto/X509Cert.h
@@ -111,6 +111,8 @@ namespace digidoc
static std::string toOID(ASN1_OBJECT *obj);
template
std::string toString(const std::string &obj) const;
+ template
+ constexpr auto extension(int nid) const noexcept;
std::shared_ptr cert;
};
}
diff --git a/vcpkg-ports/xmlsec/portfile.cmake b/vcpkg-ports/xmlsec/portfile.cmake
index 8bd871339..7871dd094 100644
--- a/vcpkg-ports/xmlsec/portfile.cmake
+++ b/vcpkg-ports/xmlsec/portfile.cmake
@@ -11,6 +11,7 @@ vcpkg_from_github(
pkgconfig_fixes.patch
xmlsec1-1.3.5.legacy.patch
xmlsec1-1.3.7.rsapss.patch
+ xmlsec1-1.3.7.ecdsa-sig.patch
)
file(COPY "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt" DESTINATION "${SOURCE_PATH}")
diff --git a/vcpkg-ports/xmlsec/vcpkg.json b/vcpkg-ports/xmlsec/vcpkg.json
index 1adcff2da..f6dc95e03 100644
--- a/vcpkg-ports/xmlsec/vcpkg.json
+++ b/vcpkg-ports/xmlsec/vcpkg.json
@@ -1,6 +1,7 @@
{
"name": "xmlsec",
"version": "1.3.7",
+ "port-version": 1,
"description": "XML Security Library is a C library based on LibXML2. The library supports major XML security standards.",
"homepage": "https://www.aleksey.com/xmlsec/",
"license": "X11 AND MPL-1.1",
diff --git a/vcpkg-ports/xmlsec/xmlsec1-1.3.7.ecdsa-sig.patch b/vcpkg-ports/xmlsec/xmlsec1-1.3.7.ecdsa-sig.patch
new file mode 100644
index 000000000..97bc6da80
--- /dev/null
+++ b/vcpkg-ports/xmlsec/xmlsec1-1.3.7.ecdsa-sig.patch
@@ -0,0 +1,326 @@
+From 5a4eb6c10cb27718bc3bbe59998efaca83294cd9 Mon Sep 17 00:00:00 2001
+From: lsh123
+Date: Thu, 14 Aug 2025 08:21:43 -0700
+Subject: [PATCH] (xmlsec-openssl, xmlsec-gnutls, xmlsec-mscng) Added support
+ for longer than expected DSA and ECDSA sigantures to support broken Java
+ implementations. (#943)
+
+diff --git a/src/gnutls/signatures.c b/src/gnutls/signatures.c
+index 107ce5e7..d0b8a55e 100644
+--- a/src/gnutls/signatures.c
++++ b/src/gnutls/signatures.c
+@@ -591,11 +591,18 @@ xmlSecGnuTLSToDer(const gnutls_datum_t* src, gnutls_datum_t* dst, xmlSecSize siz
+
+
+ /* check size: we expect the r and s to be the same size and match the size of
+- * the key (RFC 6931); however some implementations (e.g. Java) cut leading zeros:
+- * https://github.com/lsh123/xmlsec/issues/228 */
+- if((src->size < 2 * size) && (src->size % 2 == 0)) {
++ * the key (RFC 6931) */
++ if(src->size == 2 * size) {
++ /* good, do nothing */
++ } else if((src->size < 2 * size) && (src->size % 2 == 0)) {
++ /* however some implementations (e.g. Java) cut leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/228 */
+ size = src->size / 2;
+- } else if(src->size != 2 * size) {
++ } else if((src->size > 2 * size) && (src->size % 2 == 0)) {
++ /* however some implementations (e.g. Java) add leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/941*/
++ size = src->size / 2;
++ } else {
+ xmlSecInternalError3("Invalid signature size", NULL,
+ "actual=%u; expected=" XMLSEC_SIZE_FMT, src->size, 2 * size);
+ return(-1);
+diff --git a/src/mscng/signatures.c b/src/mscng/signatures.c
+index 16253b93..d4c03d12 100644
+--- a/src/mscng/signatures.c
++++ b/src/mscng/signatures.c
+@@ -424,7 +424,7 @@ static int xmlSecMSCngSignatureSetKeyReq(xmlSecTransformPtr transform, xmlSecKe
+
+ /*
+ * https://www.w3.org/TR/xmldsig-core1/#sec-ECDSA
+-*
++*
+ * The output of the ECDSA algorithm consists of a pair of integers usually
+ * referred by the pair(r, s).The signature value consists of the base64
+ * encoding of the concatenation of two octet - streams that respectively result
+@@ -464,32 +464,60 @@ xmlSecMSCngSignatureFixBrokenJava(xmlSecMSCngSignatureCtxPtr ctx,
+ return(0);
+ }
+
+- /* check the size */
++ /* check the size: we expect the DSA/ECDSA r and s to be the same size and either have
++ * fixed size (DSA) or match the size of the key (ECDSA) */
+ if (2 * halfSize == dataSize) {
+ return(0);
+ }
+- else if ((dataSize > 2 * halfSize) || (dataSize % 2 != 0)) {
+- xmlSecInternalError3("xmlSecOpenSSLEvpSignatureEcdsaHalfLen", NULL,
+- "expectedSignLen=" XMLSEC_SIZE_FMT "; actualSignLen=" XMLSEC_SIZE_FMT, 2 * halfSize, dataSize);
+- return(-1);
+- }
+
+- /* let's fix it! */
+- res = (xmlSecByte*)xmlMalloc(2 * halfSize);
+- if (res == NULL) {
+- xmlSecMallocError(2 * halfSize, NULL);
++ if ((dataSize < 2 * halfSize) && (dataSize % 2 == 0)) {
++ /* however some implementations (e.g. Java) cut leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/228 */
++
++ /* let's fix it! */
++ res = (xmlSecByte*)xmlMalloc(2 * halfSize);
++ if (res == NULL) {
++ xmlSecMallocError(2 * halfSize, NULL);
++ return(-1);
++ }
++ memset(res, 0, 2 * halfSize);
++
++ /* add zeros at the beggining of both r and s */
++ offset = (2 * halfSize - dataSize) / 2;
++ memcpy(res + offset, data, dataSize / 2);
++ memcpy(res + halfSize + offset, data + dataSize / 2, dataSize / 2);
++
++ /* success */
++ (*out) = res;
++ (*outSize) = 2 * halfSize;
++ } else if ((dataSize > 2 * halfSize) && (dataSize % 2 == 0)) {
++ /* however some implementations (e.g. Java) add leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/941 */
++
++ /* let's fix it! */
++ res = (xmlSecByte*)xmlMalloc(2 * halfSize);
++ if (res == NULL) {
++ xmlSecMallocError(2 * halfSize, NULL);
++ return(-1);
++ }
++ memset(res, 0, 2 * halfSize);
++
++ /* remove zeros at the beggining of both r and s (note: we don't check if those
++ * are actually zeros, just hope for the best) */
++ offset = (dataSize - 2 * halfSize) / 2;
++ memcpy(res, data + offset, halfSize);
++ memcpy(res + halfSize, data + dataSize / 2 + offset, halfSize);
++
++ /* success */
++ (*out) = res;
++ (*outSize) = 2 * halfSize;
++ } else {
++ xmlSecInternalError3("xmlSecMSCngSignatureFixBrokenJava", NULL,
++ "expectedSignLen=" XMLSEC_SIZE_FMT "; actualSignLen=" XMLSEC_SIZE_FMT, 2 * halfSize, dataSize);
+ return(-1);
+ }
+- memset(res, 0, 2 * halfSize);
+-
+- /* add zeros at the beggining of both r and s */
+- offset = (2 * halfSize - dataSize) / 2;
+- memcpy(res + offset, data, dataSize / 2);
+- memcpy(res + halfSize + offset, data + dataSize / 2, dataSize / 2);
+
+- /* success */
+- (*out) = res;
+- (*outSize) = 2 * halfSize;
++ /* done */
+ return(0);
+ }
+
+@@ -538,8 +566,7 @@ xmlSecMSCngSignatureVerify(xmlSecTransformPtr transform,
+ pPaddingInfo = &pssPadingInfo;
+ } else {
+ /* we expect the DSA/ECDSA r and s to be the same size and either have fixed size (DSA) or match
+- * the size of the key (ECDSA); however some implementations (e.g. Java) cut leading zeros:
+- * https://github.com/lsh123/xmlsec/issues/228 */
++ * the size of the key (ECDSA); however some implementations (e.g. Java) cut or add leading zeros */
+ ret = xmlSecMSCngSignatureFixBrokenJava(ctx, data, dataSize, (const xmlSecByte**)&fixedData, &fixedDataSize);
+ if (ret < 0) {
+ xmlSecInternalError("xmlSecMSCngSignatureFixBrokenJava", xmlSecTransformGetName(transform));
+diff --git a/src/openssl/signatures.c b/src/openssl/signatures.c
+index 0c661470..bc8f7b57 100644
+--- a/src/openssl/signatures.c
++++ b/src/openssl/signatures.c
+@@ -1635,12 +1635,19 @@ xmlSecOpenSSLEvpSignatureDsa_XmlDSig2OpenSSL(const xmlSecTransformId transformId
+ }
+
+ /* check size: we expect the r and s to be the same size and match the size of
+- * the key (RFC 6931); however some implementations (e.g. Java) cut leading zeros:
+- * https://github.com/lsh123/xmlsec/issues/228 */
++ * the key (RFC 6931) */
+ XMLSEC_SAFE_CAST_SIZE_TO_INT(dataSize, signLen, goto done, NULL);
+- if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
++ if(signLen == 2 * signHalfLen) {
++ /* good, do nothing */
++ } else if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) cut leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/228 */
+ signHalfLen = signLen / 2;
+- } else if(signLen != 2 * signHalfLen) {
++ } else if((signLen > 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) add leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/941 */
++ signHalfLen = signLen / 2;
++ } else {
+ xmlSecInternalError3("xmlSecOpenSSLEvpSignatureDsaHalfLen", NULL,
+ "signLen=%d; signHalfLen=%d", signLen, signHalfLen);
+ goto done;
+@@ -1858,12 +1865,19 @@ xmlSecOpenSSLEvpSignatureEcdsa_XmlDSig2OpenSSL(xmlSecSize keySizeBits, const xml
+ XMLSEC_SAFE_CAST_SIZE_TO_INT((keySizeBits + 7) / 8, signHalfLen, goto done, NULL);
+
+ /* check size: we expect the r and s to be the same size and match the size of
+- * the key (RFC 6931); however some implementations (e.g. Java) cut leading zeros:
+- * https://github.com/lsh123/xmlsec/issues/228 */
++ * the key (RFC 6931) */
+ XMLSEC_SAFE_CAST_SIZE_TO_INT(dataSize, signLen, goto done, NULL);
+- if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
+- signHalfLen = signLen / 2;
+- } else if(signLen != 2 * signHalfLen) {
++ if(signLen == 2 * signHalfLen) {
++ /* good, do nothing */
++ } else if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) cut leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/228 */
++ signHalfLen = signLen / 2;
++ } else if((signLen > 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) add leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/941 */
++ signHalfLen = signLen / 2;
++ } else {
+ xmlSecInternalError3("xmlSecOpenSSLEvpSignatureEcdsaHalfLen", NULL,
+ "signLen=%d; signHalfLen=%d", signLen, signHalfLen);
+ goto done;
+diff --git a/src/openssl/signatures_legacy.c b/src/openssl/signatures_legacy.c
+index c7f53071..a57fd535 100644
+--- a/src/openssl/signatures_legacy.c
++++ b/src/openssl/signatures_legacy.c
+@@ -759,12 +759,19 @@ xmlSecOpenSSLSignatureLegacyEcdsaVerify(xmlSecOpenSSLSignatureLegacyCtxPtr ctx,
+ }
+
+ /* check size: we expect the r and s to be the same size and match the size of
+- * the key (RFC 6931); however some implementations (e.g. Java) cut leading zeros:
+- * https://github.com/lsh123/xmlsec/issues/228 */
++ * the key (RFC 6931) */
+ XMLSEC_OPENSSL_SAFE_CAST_SIZE_TO_SIZE_T(signSize, signLen, goto done, NULL);
+- if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
++ if(signLen == 2 * signHalfLen) {
++ /* good, do nothing */
++ } else if((signLen < 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) cut leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/228 */
+ signHalfLen = signLen / 2;
+- } else if(signLen != 2 * signHalfLen) {
++ } else if((signLen > 2 * signHalfLen) && (signLen % 2 == 0)) {
++ /* however some implementations (e.g. Java) add leading zeros:
++ * https://github.com/lsh123/xmlsec/issues/941*/
++ signHalfLen = signLen / 2;
++ } else {
+ xmlSecInvalidDataError("Signature length doesn't match key size", NULL);
+ goto done;
+ }
+diff --git a/tests/aleksey-xmldsig-01/enveloping-sha256-ecdsa-sha256_padded.xml b/tests/aleksey-xmldsig-01/enveloping-sha256-ecdsa-sha256_padded.xml
+new file mode 100644
+index 00000000..9ccd9f09
+--- /dev/null
++++ b/tests/aleksey-xmldsig-01/enveloping-sha256-ecdsa-sha256_padded.xml
+@@ -0,0 +1,80 @@
++
++
++
++
++
++
++
++ iDhYt78o294fA6pzQ7k44+eejrQMi+WX3l3UrUdtL1Q=
++
++
++ AHqXLYwxGqMPCXTruP6QrFfFsWAbX3lhi2A+dQkVXrlIAONNoCh2v61el13WO9ih
++LG9iOELiGYlOAfS0SZhYQ9hm
++
++
++MIID9zCCA2CgAwIBAgIJAK+ii7kzrdqsMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
++VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3Vy
++aXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEQMA4G
++A1UECxMHUm9vdCBDQTEWMBQGA1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3
++DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMCAXDTE0MDUyMzE3NTA1OVoYDzIxMTQw
++NDI5MTc1MDU5WjCBrjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx
++PTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJyYXJ5IChodHRwOi8vd3d3LmFsZWtz
++ZXkuY29tL3htbHNlYykxEDAOBgNVBAsTB1Jvb3QgQ0ExFjAUBgNVBAMTDUFsZWtz
++ZXkgU2FuaW4xITAfBgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTCBnzAN
++BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtY4MCNj/qrOzVuex1BD/PuCYTDDOLLVj
++tpKXQteQPqy0kgMwuQgRwdNnICIHQbnFKL40XoyACJVWKM7b0LkvWJNeyVzXPqEE
++9ZPmNxWGUjVcr7powT7v8V7S2QflUnr8ZvR4XWwkZJ9EYKNhenijgJ5yYDrXCWdv
++C+fnjBjv2LcCAwEAAaOCARcwggETMB0GA1UdDgQWBBQGtaSsp6p1ROoVnE/fBYNP
++ah7+CzCB4wYDVR0jBIHbMIHYgBQGtaSsp6p1ROoVnE/fBYNPah7+C6GBtKSBsTCB
++rjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhN
++TCBTZWN1cml0eSBMaWJyYXJ5IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNl
++YykxEDAOBgNVBAsTB1Jvb3QgQ0ExFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAf
++BgkqhkiG9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbYIJAK+ii7kzrdqsMAwGA1Ud
++EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEARpb86RP/ck55X+NunXeIX81i763b
++j7Z1VJwFbA/QfupzxnqJ2IP/lxC8YxJ3Bp2IJMI7rC9r0poa41ZxI5rGHip97Dpg
++sxPF9lkRUmKBBQjkICOq1w/4d2DRInBoqXttD+0WsqDfNDVK+7kSE07ytn3RzHCj
++j0gv0PdxmuCsR/E=
++
++MIIDzzCCAzigAwIBAgIJAK+ii7kzrdqtMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
++VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3Vy
++aXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEQMA4G
++A1UECxMHUm9vdCBDQTEWMBQGA1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3
++DQEJARYSeG1sc2VjQGFsZWtzZXkuY29tMCAXDTE0MDUyMzE3NTIzOFoYDzIxMTQw
++NDI5MTc1MjM4WjCBnDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx
++PTA7BgNVBAoTNFhNTCBTZWN1cml0eSBMaWJyYXJ5IChodHRwOi8vd3d3LmFsZWtz
++ZXkuY29tL3htbHNlYykxFjAUBgNVBAMTDUFsZWtzZXkgU2FuaW4xITAfBgkqhkiG
++9w0BCQEWEnhtbHNlY0BhbGVrc2V5LmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
++QQCyuvKJ2CuUPD33ghPt4Q8MilesHxVbbpyKfmabrYVpDGVDmOKKp337qJUZZ95K
++fwlXbR2j0zyKWJmvRxUx+PsTAgMBAAGjggFFMIIBQTAMBgNVHRMEBTADAQH/MCwG
++CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
++HQ4EFgQU/uTsUyTwlZXHELXhRLVdOWVa434wgeMGA1UdIwSB2zCB2IAUBrWkrKeq
++dUTqFZxP3wWDT2oe/guhgbSkgbEwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpD
++YWxpZm9ybmlhMT0wOwYDVQQKEzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDov
++L3d3dy5hbGVrc2V5LmNvbS94bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYD
++VQQDEw1BbGVrc2V5IFNhbmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3Nl
++eS5jb22CCQCvoou5M63arDANBgkqhkiG9w0BAQUFAAOBgQBuTAW63AgWqqUDPGi8
++BiXbdKHhFP4J8qgkdv5WMa6SpSWVgNgOYXkK/BSg1aSmQtGv8/8UvBRPoJnO4y0N
++jWUFf1ubOgUNmedYNLq7YbTp8yTGWeogCyM2xdWELMP8BMgQL0sP+MDAFMKO3itY
++mEWnCEsP15HKSTms54RNj7oJ+A==
++MIIDfzCCAymgAwIBAgIJAK+ii7kzrdq1MA0GCSqGSIb3DQEBBQUAMIGcMQswCQYD
++VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTE9MDsGA1UEChM0WE1MIFNlY3Vy
++aXR5IExpYnJhcnkgKGh0dHA6Ly93d3cuYWxla3NleS5jb20veG1sc2VjKTEWMBQG
++A1UEAxMNQWxla3NleSBTYW5pbjEhMB8GCSqGSIb3DQEJARYSeG1sc2VjQGFsZWtz
++ZXkuY29tMCAXDTE3MDIxNTIyMDgyMVoYDzIxMTcwMTIyMjIwODIxWjCBojELMAkG
++A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExPTA7BgNVBAoTNFhNTCBTZWN1
++cml0eSBMaWJyYXJ5IChodHRwOi8vd3d3LmFsZWtzZXkuY29tL3htbHNlYykxHDAa
++BgNVBAMTE0VDRFNBIHNlY3AyNTZyMSBLZXkxITAfBgkqhkiG9w0BCQEWEnhtbHNl
++Y0BhbGVrc2V5LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABG0uzqp1mHfR
++9zEMsYiIXkNPttZyj0aP8iE9wlKNp9x73kJnNmll8l47MZDWVjtrcyFn1XUzxH7q
++WVJYjPBGptSjggFFMIIBQTAMBgNVHRMEBTADAQH/MCwGCWCGSAGG+EIBDQQfFh1P
++cGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUeiw8r3+wiHw0
++5T+3qRhjfhAZu9kwgeMGA1UdIwSB2zCB2IAU/uTsUyTwlZXHELXhRLVdOWVa436h
++gbSkgbEwga4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMT0wOwYD
++VQQKEzRYTUwgU2VjdXJpdHkgTGlicmFyeSAoaHR0cDovL3d3dy5hbGVrc2V5LmNv
++bS94bWxzZWMpMRAwDgYDVQQLEwdSb290IENBMRYwFAYDVQQDEw1BbGVrc2V5IFNh
++bmluMSEwHwYJKoZIhvcNAQkBFhJ4bWxzZWNAYWxla3NleS5jb22CCQCvoou5M63a
++rTANBgkqhkiG9w0BAQUFAANBABs4Ie691BR30Ki9MM5jzGhWlnWs8SaGJkAlJxck
++hhVmvV/5cntLvuQNGPpZJtkY8cVIw2/G4XmBBad7lZcuOag=
++
++
++
+diff --git a/tests/testDSig.sh b/tests/testDSig.sh
+index 3d653346..863b1d2f 100755
+--- a/tests/testDSig.sh
++++ b/tests/testDSig.sh
+@@ -1119,6 +1119,14 @@ execDSigTest $res_success \
+ "ec x509" \
+ "--trusted-$cert_format $topfolder/keys/enveloped-ecdsa-java-bug-cert.$cert_format --enabled-key-data x509 --verification-gmt-time 2019-01-01+00:00:00"
+
++# see issue https://github.com/lsh123/xmlsec/issues/941 (another java bug)
++execDSigTest $res_success \
++ "" \
++ "aleksey-xmldsig-01/enveloping-sha256-ecdsa-sha256_padded" \
++ "sha256 ecdsa-sha256" \
++ "ec x509" \
++ "--insecure --enabled-key-data x509"
++
+ execDSigTest $res_success \
+ "" \
+ "aleksey-xmldsig-01/x509data-test" \
+--
+2.46.0
+
diff --git a/vcpkg.json b/vcpkg.json
index a57ca3ff2..01223ffad 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -15,7 +15,7 @@
"features": {
"tests": { "description": "Build tests", "dependencies": ["boost-test"] }
},
- "builtin-baseline": "031ad89ce6c575df35a8e58707ad2c898446c63e",
+ "builtin-baseline": "98e7cd3a7ba579efc543f8854af800d033031eae",
"vcpkg-configuration": {
"overlay-triplets": ["./vcpkg-triplets"],
"overlay-ports": ["vcpkg-ports/xmlsec"]