Digitální podepisování PDF souborů v C#, část 2

Jan Holan       27. 9. 2011       Bezpečnost       4857 zobrazení

V příspěvku Digitální podepisování PDF souborů v C# jsem se věnoval tomu jak podepsat PDF soubor. Uvedený příklad k podpisu použil certifikát uložený v souboru pfx. Někdy je ale výhodné načíst certifikát přímo z uložiště certifikátu Windows. Pro podepsání PDF souboru takto načteným certifikátem ale nelze použít postup uvedený v prvním příkladu, proto musíme použít jiný kód.

Kód obsahuje metodu SignPdf, která načte certifikát (do typu X509Certificate2) z uložiště pro aktuálního uživatele podle Thumbprint (otisku) certifikátu. Poté se načte PDF soubor, podepíše se (metoda SignDocument) a vráceným obsahem se přepíše původní PDF soubor.

public static void SignPdf(string fileName, string certThumbprint)
{
    //Get certificate
    //Open the currently logged-in user certificate store
    var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);

    //Select a certificate from the certificate store
    var certs = store.Certificates.Find(X509FindType.FindByThumbprint, GetCertThumbprint(certThumbprint), true);
    store.Close();

    //Verify that a certificate exists 
    if (certs.Count == 0)
    {
        MessageBox.Show("Nelze najít určený certifikát v Current user certificate store!", "Sign PDF", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
    }

    //Open Pdf document
    byte[] pdfData = File.ReadAllBytes(fileName);

    //Sign the PDF document
    byte[] signedData = SignDocument(pdfData, certs[0]);

    File.WriteAllBytes(fileName, signedData);
}

private static string GetCertThumbprint(string certThumbprint)
{
    string thumbprint = certThumbprint.Replace(" ", "").ToUpperInvariant();
    if (thumbprint[0] == 8206)
    {
        thumbprint = thumbprint.Substring(1);
    }

    return thumbprint;
}

private static byte[] SignDocument(byte[] pdfData, X509Certificate2 cert)
{
    using (MemoryStream stream = new MemoryStream())
    {
        var reader = new PdfReader(pdfData);
        var stp = PdfStamper.CreateSignature(reader, stream, '\0');
        var sap = stp.SignatureAppearance;

        //Protect certain features of the document 
        stp.SetEncryption(null,
            Guid.NewGuid().ToByteArray(), //random password 
            PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY | PdfWriter.ALLOW_SCREENREADERS,
            PdfWriter.ENCRYPTION_AES_256);

        //Get certificate chain
        var cp = new Org.BouncyCastle.X509.X509CertificateParser();
        var certChain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(cert.RawData) };

        sap.SetCrypto(null, certChain, null, PdfSignatureAppearance.WINCER_SIGNED);

        //Set signature appearance
        BaseFont helvetica = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1250, BaseFont.EMBEDDED);
        Font font = new Font(helvetica, 12, iTextSharp.text.Font.NORMAL);
        sap.Layer2Font = font;
        sap.SetVisibleSignature(new iTextSharp.text.Rectangle(415, 100, 585, 40), 1, null);

        var dic = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
        //Set some stuff in the signature dictionary.
        dic.Date = new PdfDate(sap.SignDate);
        dic.Name = cert.Subject;    //Certificate name 
        if (sap.Reason != null)
        {
            dic.Reason = sap.Reason;
        }
        if (sap.Location != null)
        {
            dic.Location = sap.Location;
        }

        //Set the crypto dictionary 
        sap.CryptoDictionary = dic;

        //Set the size of the certificates and signature. 
        int csize = 4096; //Size of the signature - 4K

        //Reserve some space for certs and signatures
        var reservedSpace = new Dictionary<PdfName, int>();
        reservedSpace[PdfName.CONTENTS] = csize * 2 + 2; //*2 because binary data is stored as hex strings. +2 for end of field
        sap.PreClose(reservedSpace);    //Actually reserve it 

        //Build the signature 
        HashAlgorithm sha = new SHA1CryptoServiceProvider();

        var sapStream = sap.RangeStream;
        int read = 0;
        byte[] buff = new byte[8192];
        while ((read = sapStream.Read(buff, 0, 8192)) > 0)
        {
            sha.TransformBlock(buff, 0, read, buff, 0);
        }
        sha.TransformFinalBlock(buff, 0, 0);

        byte[] pk = SignMsg(sha.Hash, cert, false);

        //Put the certs and signature into the reserved buffer 
        byte[] outc = new byte[csize];
        Array.Copy(pk, 0, outc, 0, pk.Length);

        //Put the reserved buffer into the reserved space 
        PdfDictionary certificateDictionary = new PdfDictionary();
        certificateDictionary.Put(PdfName.CONTENTS, new PdfString(outc).SetHexWriting(true));

        //Write the signature 
        sap.Close(certificateDictionary);
        //Close the stamper and save it 
        stp.Close();

        reader.Close();

        //Return the saved pdf 
        return stream.GetBuffer();
    }
}

private static byte[] SignMsg(Byte[] msg, X509Certificate2 cert, bool detached)
{
    //Place message in a ContentInfo object. This is required to build a SignedCms object. 
    ContentInfo contentInfo = new ContentInfo(msg);

    //Instantiate SignedCms object with the ContentInfo above. 
    //Has default SubjectIdentifierType IssuerAndSerialNumber. 
    SignedCms signedCms = new SignedCms(contentInfo, detached);

    //Formulate a CmsSigner object for the signer. 
    CmsSigner cmsSigner = new CmsSigner(cert);  //First cert in the chain is the signer cert

    //Do the whole certificate chain. This way intermediate certificates get sent across as well.
    cmsSigner.IncludeOption = X509IncludeOption.ExcludeRoot;

    //Sign the CMS/PKCS #7 message. The second argument is needed to ask for the pin. 
    signedCms.ComputeSignature(cmsSigner, false);

    //Encode the CMS/PKCS #7 message. 
    return signedCms.Encode();
}

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback