Skip to content

SignedContentBlob.GetRawSignature() breaks ECDSA signature verification: r/s not left-padded to IEEE P1363 length (PSPKI Get-EnterprisePKIHealthStatus reports false InvalidIssuer)  #124

Description

@Poldi321
## Summary

`SignedContentBlob.GetRawSignature()` converts a DER-encoded ECDSA/DSA signature
(`SEQUENCE { r INTEGER, s INTEGER }`) to a raw `r || s` concatenation by stripping the
leading zero (DER sign byte) from each component — but it does **not left-pad the
components back to the fixed curve field size** required by the IEEE P1363 format that
.NET `ECDsa.VerifyHash()` expects.

As a result, whenever the minimal DER encoding of `r` or `s` is shorter than the field
size, `CryptSigner.VerifyData(SignedContentBlob, PublicKey)` receives a signature of the
wrong length and returns `false` for a **cryptographically valid** signature.

Affected code (identical in v4.4.0 and current master):
`src/SysadminsLV.PKI/Cryptography/SignedContentBlob.cs`, `GetRawSignature()`:

    var asn = new Asn1Reader(Signature.Value);
    asn.MoveNext();
    var r = asn.GetPayload().ToList();
    if (r[0] == 0) { r.RemoveAt(0); }
    asn.MoveNext();
    var s = asn.GetPayload().ToList();
    if (s[0] == 0) { s.RemoveAt(0); }
    var signature = new List<Byte>(r);
    signature.AddRange(s);
    return signature.ToArray();

## Impact

- For **P-521** (`sha512ECDSA`) the order is ≈ 2^521, so each component is shorter than
  66 bytes with probability ≈ 1/2 → roughly **75 % of all P-521 signatures fail to verify**.
- P-256/P-384 are affected too (probability ≈ 1/256 per component).
- User-visible symptom: **PSPKI 4.4.1 `Get-EnterprisePKIHealthStatus` reports
  `Status: InvalidIssuer`** for a perfectly valid ECDSA-signed CRL
  (`__verifyCDP` → `X509CRL2.VerifySignature()` → this code path), while
  `certutil -urlfetch -verify` validates the same chain and CRL successfully.

## Reproduction (real-world data)

Environment: Windows Server (26100), Windows PowerShell 5.1, PSPKI 4.4.1
(SysadminsLV.PKI 4.4.0). Offline ECDSA P-521 root CA, CRL signed with `sha512ECDSA`.

The failing CRL's signature components:

- `r`: DER payload 66 bytes, starts `01 3e 9d …` (full field length — OK)
- `s`: DER payload 66 bytes, starts `00 ee 65 …` (leading zero is the DER sign byte;
  actual value is 65 bytes)

`GetRawSignature()` strips the sign byte from `s` and returns a **131-byte** blob.
`ECDsa.VerifyHash()` requires exactly 2 × 66 = **132 bytes** for P-521 → returns `false`.

Independent verification with plain .NET against the same CRL and issuer public key:

- correct IEEE P1363 conversion (each component left-padded to 66 bytes, 132 bytes
  total): `ECDsa.VerifyHash()` → **True**
- `GetRawSignature()`-style conversion (131 bytes): `ECDsa.VerifyHash()` → **False**

`Get-EnterprisePKIHealthStatus` consequently reports:

    Name        : TestRoot2
    ChainStatus : NoError
    CDP  http://…/TestRoot2.crl  Status: InvalidIssuer

while `certutil -urlfetch -verify` on the same chain completes successfully
("Leaf certificate revocation check passed").

## Suggested fix

`GetRawSignature()` needs to know the key/field size (or receive it from the caller,
e.g. `CryptSigner`, which has the public key and thus `KeySize`) and left-pad each
component:

    int fieldSize = (keySizeBits + 7) / 8;
    byte[] p1363 = new byte[2 * fieldSize];
    // rTrimmed/sTrimmed = payload with DER sign byte stripped
    Buffer.BlockCopy(rTrimmed, 0, p1363, fieldSize - rTrimmed.Length, rTrimmed.Length);
    Buffer.BlockCopy(sTrimmed, 0, p1363, 2 * fieldSize - sTrimmed.Length, sTrimmed.Length);
    return p1363;

The DSA branch has the same problem (fixed 20-byte components for SHA-1 DSA).

Side note: the `strict` parameter of `X509CRL2.VerifySignature(issuer, strict)` is
documented as "not implemented" — PSPKI's `Get-EnterprisePKIHealthStatus` passes `$true`
expecting issuer/subject name comparison.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug. An issue exist in our code.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions