(BZSt DAC7) XML signieren XMLDSIG /höhere Framework Version?

1. Februar 2024 18:01

Moin, nachdem ich in viewtopic.php?f=68&t=38974 schon die ersten Probleme mit der BZSt-Anbindung hatte, bin ich nun auf das nächste gestoßen.
Folgendes Problem:

"Jede Massendaten-Lieferung, die über die DIP-Schnittstelle übertragen werden soll, muss durch den Kunden signiert werden. Zur Signierung der zu sendenden Daten wird derzeit die Enveloping-Signatur unterstützt. Hierbei ist die Signatur in der zu übertragenden Datei integriert, indem die Signatur als Wrapper um das Daten-XML herumgelegt wird. Die digitale Signatur wird durch den Benutzer mit seinem privaten Schlüssel, der auch für die Erzeugung des Zertifikats für den Freischaltungsantrag diente, erstellt."

Algorithmus soll http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1 sein (wenn das mal wieder Blödsinn ist wie bei der API, dann wohl eher nur RSA/SHA256 und nicht MGF1/PSS)

Folgendes habe ich dazu gefunden als Beispiel:
https://community.dynamics.com/forums/t ... e1f9a54bbc
https://community.dynamics.com/forums/t ... 10204cb76e

Bei Zweiterem gibt es nämlich folgende Zeile, die ich brauche
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA256Url;

Ich habe auch mal geschaut, ob das per BouncyCastle möglich ist, da wir das bereits aktiv haben, aber da scheint es nur Lösungen für Java zu geben und nicht für .NET/C#

Bei NAV 2016 in .NET 4 gibt es das allerdings noch gar nicht:
https://learn.microsoft.com/en-us/dotne ... asha256url

Das kam erst mit 4.6.2 dazu und die habe ich in NAV 2016 nicht.

Folgende Fragen ergeben sich dadurch nun:
- Kann ich irgendwie eine höhere .NET Framework-Version zum Laufen bekommen in NAV 2016?
- Wenn ich selbst eine DLL bauen würde in C# mit Nutzung der höheren Version, würde das irgendwie funktionieren? Ich habe von Problemen gelesen, wenn die DLL höher kompiliert ist als NAV eigentlich ermöglicht
- Jemand eine Idee, wie ich das sonst anders lösen könnte (evtl. doch per BouncyCastle) ohne eine ganze Anbindung zu zaubern mit "ich werfe rein und bekomme raus"?

Re: (BZSt DAC7) XML signieren XMLDSIG /höhere Framework Vers

23. April 2024 22:09

Moin,

ich habe mich jetzt extra hier registriert, da ich mich ebenfalls seit Wochen unter Schmerzen mit dem Thema BZSt/DAC7 herumschlage und Dir hoffentlich weiterhelfen kann.
Ich bearbeite das Thema in einem anderen Kontext als Dynamics und bin bei der Recherche auf Deinen Beitrag gestoßen.

Ich habe für die Signatur der zur übermittelnden XML-Datei ein Powershell-Skript geschrieben und bin ebenfalls über das Problem gestolpert, dass SHA256-MGF standardmäßig nicht unterstützt wird.
Dieser Beitrag hat mir hierzu entscheidend weitergeholfen.

Allerdings konnte ich den nicht 1:1 übernehmen, da
Code:
Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm);
in
Code:
void SetHashAlgorithm(string strName)
bei der Registrierung des Algorithmus einmal mit einem Wert für strName aufgerufen wird, der für den Fehler "ungültige OID" sorgt. Ich habe strName in den beiden Funktionen einfach mit "SHA256" hartcodiert.

Ich teile hier mal mein PowerShell-Skript ohne jegliche Gewährleistung. Damit kann ich zumindest eine fertige DAC7 XML-Datei so signieren, dass die DIP-Schnittstelle sie akzeptiert.
Code:
Add-Type -AssemblyName System.Security
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$dotNetClassid =  ("RsaSsaPss" + (Get-Random))
$CryptoAlgHelperCode = @"
using System;
using System.Security.Cryptography;
namespace CryptoAlgHelper
{

public class $dotNetClassid
{
    public static void RegisterSha256RsaMgf1()
    {       
        CryptoConfig.AddAlgorithm(typeof(RsaPssSha256SignatureDescription), "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1");
    }   

    public class RsaPssSha256SignatureDescription : SignatureDescription
    {
        public RsaPssSha256SignatureDescription()
        {
            using (var rsa = RSACng.Create())
            {
                this.KeyAlgorithm = rsa.GetType().AssemblyQualifiedName; // Does not like a simple algorithm name, but wants a type name (AssembyQualifiedName in Core)
            }
            this.DigestAlgorithm = "SHA256";
            this.FormatterAlgorithm = typeof(RsaPssSignatureFormatter).FullName;
            this.DeformatterAlgorithm = typeof(RsaPssSignatureDeformatter).FullName;
        }

        public override AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key)
        {
            var signatureFormatter = new RsaPssSignatureFormatter();
            signatureFormatter.SetKey(key);
            signatureFormatter.SetHashAlgorithm(this.DigestAlgorithm);
            return signatureFormatter;
        }

        public override AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key)
        {
            var signatureDeformatter = new RsaPssSignatureDeformatter();
            signatureDeformatter.SetKey(key);
            signatureDeformatter.SetHashAlgorithm(this.DigestAlgorithm);
            return signatureDeformatter;
        }

        public class RsaPssSignatureFormatter : AsymmetricSignatureFormatter
        {
            private RSA Key { get; set; }
            private string HashAlgorithmName { get; set; }

            public override void SetKey(AsymmetricAlgorithm key)
            {
                this.Key = (RSA)key;
            }

            public override void SetHashAlgorithm(string strName)
            {
                // Verify the name               
                strName="SHA256";
                Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm);                                               

                this.HashAlgorithmName = strName;
            }

            public override byte[] CreateSignature(byte[] rgbHash)
            {
                return this.Key.SignHash(rgbHash, new HashAlgorithmName(this.HashAlgorithmName), RSASignaturePadding.Pss);
            }
        }

        public class RsaPssSignatureDeformatter : AsymmetricSignatureDeformatter
        {
            private RSA Key { get; set; }
            private string HashAlgorithmName { get; set; }

            public override void SetKey(AsymmetricAlgorithm key)
            {
                this.Key = (RSA)key;
            }

            public override void SetHashAlgorithm(string strName)
            {
                // Verify the name
                strName="SHA256";
                Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm);               
                this.HashAlgorithmName = strName;
            }

            public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
            {
                return this.Key.VerifyHash(rgbHash, rgbSignature, new HashAlgorithmName(this.HashAlgorithmName), RSASignaturePadding.Pss);
            }
        }
    }
}
}
"@
Add-Type -TypeDefinition $CryptoAlgHelperCode -Language CSharp   

function Sign-XML {
    Param (
        [xml]$xmlSignee,       
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$signCertificate,
        [string]$dotNetClassid
    )

    Invoke-Expression "[CryptoAlgHelper.$dotNetClassid]::RegisterSha256RsaMgf1()"

    [System.Security.Cryptography.xml.SignedXml]$signedXml = $null
    $signedXml = New-Object System.Security.Cryptography.Xml.SignedXml -ArgumentList $xmlSignee
    $signedXml.SigningKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($signCertificate)
       
    $signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1";   

    $Reference = New-Object System.Security.Cryptography.Xml.Reference
    $Reference.Uri = ("#object")   
    $reference.DigestMethod = 'http://www.w3.org/2001/04/xmlenc#sha256'
    $signedXml.AddReference($Reference)   
   
    $keyInfo = New-Object System.Security.Cryptography.Xml.KeyInfo   
    $certData = New-Object System.Security.Cryptography.Xml.KeyInfoX509Data -ArgumentList $signCertificate
    [System.Security.Cryptography.X509Certificates.X500DistinguishedName]$dn = $signCertificate.SubjectName
   
    $certData.AddSubjectName($dn.Name)
    $keyInfo.AddClause($certData)   
       
    $signedXml.KeyInfo = $keyInfo
    $signedXml.ComputeSignature()
   
    [System.Xml.XmlElement]$xmlSignature = $signedXml.GetXml()
    return $xmlSignature
   
}

function Choose-SigningCert {

    $certPickerForm = New-Object System.Windows.Forms.Form
    $certPickerForm.Text = 'Signaturzertifikat wählen' 
    $certPickerForm.Size = New-Object System.Drawing.Size(440,280)
    $certPickerForm.StartPosition = 'CenterScreen' 

    $okButton = New-Object System.Windows.Forms.Button
    $okButton.Location = New-Object System.Drawing.Point(10,220)
    $okButton.Size = New-Object System.Drawing.Size(75,23)
    $okButton.Text = 'OK' 
    $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
    $certPickerForm.AcceptButton = $okButton
    $certPickerForm.Controls.Add($okButton)

    $cancelButton = New-Object System.Windows.Forms.Button
    $cancelButton.Location = New-Object System.Drawing.Point(90,220)
    $cancelButton.Size = New-Object System.Drawing.Size(75,23)
    $cancelButton.Text = 'Abbrechen' 
    $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
    $certPickerForm.CancelButton = $cancelButton
    $certPickerForm.Controls.Add($cancelButton)

    $label = New-Object System.Windows.Forms.Label
    $label.Location = New-Object System.Drawing.Point(10,20)
    $label.Size = New-Object System.Drawing.Size(280,20)
    $label.Text = 'Zertifikat für die Signatur auswählen:' 
    $certPickerForm.Controls.Add($label)

    $listBox = New-Object System.Windows.Forms.ListBox
    $listBox.Location = New-Object System.Drawing.Point(10,40)
    $listBox.Size = New-Object System.Drawing.Size(415,20)
    $listBox.Height = 180
    $listBox.HorizontalScrollbar = $true
   
    foreach ($cert in (Get-ChildItem -path cert:\CurrentUser\My)) {
           
        if ($cert.hasPrivateKey) {
         
            [void] $listBox.Items.Add($cert.Subject + ' (' + $cert.Thumbprint + ')') 
        }
    }
   
    $certPickerForm.Controls.Add($listBox)
    $certPickerForm.FormBorderStyle = 'FixedDialog'
    $certPickerForm.MaximizeBox = $false
    $certPickerForm.MinimizeBox = $false
    $topMost = New-Object 'System.Windows.Forms.Form' -Property @{TopMost=$true}

    if ($certPickerForm.ShowDialog($topMost) -eq [System.Windows.Forms.DialogResult]::OK) {

        return $listBox.SelectedItem
    }

    return ""
}

function Get-FileName($initialDirectory)

 [System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) |
 Out-Null

 $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
 $OpenFileDialog.initialDirectory = $initialDirectory
 $OpenFileDialog.filter = “XML-Dateien (*.xml)| *.xml”
 $OpenFileDialog.ShowDialog() | Out-Null
 return $OpenFileDialog.filename

}

function Get-SaveFileName([string] $initialDirectory){

    [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null

    $OpenFileDialog = New-Object System.Windows.Forms.SaveFileDialog
    $OpenFileDialog.initialDirectory = $initialDirectory
    $OpenFileDialog.filter = "XML-Dateien (*.xml)| *.xml"
    $OpenFileDialog.ShowDialog() |  Out-Null

    return $OpenFileDialog.filename
}


$xmlToSignFilePath = Get-FileName
if ($xmlToSignFilePath -eq '') {

    Write-Error "Could not select input file"
    return $false   
}


[xml]$xmlToSign = Get-Content -Path $xmlToSignFilePath
$xmlToSign.PreserveWhitespace = $false

$dipElement = $xmlToSign.dip
if ($dipElement -eq $null) {

    Write-Error "Could not find dip-Element in input file"
    return $false
}
    $xmlToSign.RemoveChild($dipElement)

$objectEnvelope = $xmlToSign.CreateElement('Object', 'http://www.w3.org/2000/09/xmldsig#')
    $objectEnvelopeId = $xmlToSign.CreateAttribute('Id')
    $objectEnvelopeId.Value = 'object'
    $objectEnvelope.Attributes.Append($objectEnvelopeId)

    $xmlToSign.AppendChild($objectEnvelope)
    $xmlToSign.object.AppendChild($dipElement)
   
[string]$signingCertThumprint = Choose-SigningCert
if ($signingCertThumprint -ne "" -and $signingCertThumprint -match '\((.+)\)') {

    $signingCertThumprint = $matches[1]
} else {

    Write-Error "Could not select Certificate"
    return $false
}
[System.Security.Cryptography.X509Certificates.X509Certificate2]$signingCert = Get-ChildItem -path cert:\CurrentUser\My | where{$_.Thumbprint -eq $signingCertThumprint}
[System.Xml.XmlElement]$xmlSignature = Sign-XML -xmlSignee $xmlToSign -signCert $signingCert -dotNetClassid $dotNetClassid

$objectElement = $xmlToSign.object.Clone()
$xmlToSign.RemoveChild($xmlToSign.object)
$xmlToSign.AppendChild($xmlSignature)
$xmlToSign.Signature.AppendChild($objectElement)

$outFileName = Get-SaveFileName
$xmltw =  New-Object System.Xml.XmlTextWriter($outFileName, (New-Object System.Text.UTF8Encoding($false)))
$xmlToSign.WriteTo($xmltw)
$xmltw.Close()

Re: (BZSt DAC7) XML signieren XMLDSIG /höhere Framework Vers

16. Oktober 2024 15:30

michael.schramm hat geschrieben:Ich teile hier mal mein PowerShell-Skript ohne jegliche Gewährleistung. Damit kann ich zumindest eine fertige DAC7 XML-Datei so signieren, dass die DIP-Schnittstelle sie akzeptiert.


Hey, vielen Dank für das Skript! Leider sorgt es bei mir für den Fehler E0501 - Die Signatur der Dip-XML-Datei ist ungültig / "INVALID signature -- core validation failed."
Ich bin mir nicht sicher woran es liegt. Am ehesten denke ich an dem Zertifikat.
Ich habe aus dem Portal eine PFX erhalten und gebe in der XML-Generierung als identityProvider dann BZST-CERT an und als Identifier die BZxx-Nummer. Meines Wissens müsste das richtig sein, wenn auch im Namen der PFX "Elster" vorkommt. Im Portal ist es auch als BZSt-Zertifikat angegeben.
Das Zertifikat habe ich mittels folgendem PS hinzugefügt:
Code:
$securePassword = Read-Host -Prompt "Enter PFX password" -AsSecureString
Import-PfxCertificate -FilePath "xxx_elster_xx.xx.2024_xx.xx.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password $securePassword

Komischerweise landen dadurch zwei Einträge in dem Speicher. Es ist aber auch egal welches ich dann auswähle in deinem Skript, es funktioniert beides leider nicht.
Hast du hier noch eine Idee, falls du meine Antwort überhaupt mitbekommst.

Danke