web-dev-qa-db-fra.com

Exportation de clés privées/publiques du certificat X509 vers PEM

existe-t-il un moyen pratique d'exporter des clés privées/publiques à partir d'un certificat .p12 au format PEM à l'aide de .NET Core ? Sans manipuler avec des octets à bas niveau? J'ai cherché sur Google pendant des heures et presque rien n'est utilisable dans .net ou ce n'est documenté nulle part.

Ayons un X509Certificate2

var cert = new X509Certificate2(someBytes, pass);
var privateKey = cert.GetRSAPrivateKey();
var publicKey = cert.GetRSAPublicKey();
// assume everything is fine so far

Et maintenant, je dois exporter les clés sous forme de deux clés PEM distinctes. J'ai déjà essayé PemWriter dans BouncyCastle mais les types ne sont pas compatibles avec System.Security.Cryptography de Core .. pas de chance.

--- Modifier ---

Un autre mot, je trouve un moyen d'écrire ceci:

$ openssl pkcs12 -in path/to/cert.p12 -out public.pub -clcerts -nokeys
$ openssl pkcs12 -in path/to/cert.p12 -out private.key -nocerts

Est-ce que quelqu'un a une idée?

Merci ...

6
rudolfdobias

La réponse est quelque part entre "non" et "pas vraiment".

Je vais supposer que vous ne voulez pas que le gunk de sortie p12 soit au sommet de public.pub et private.key.

public.pub est simplement le certificat. L'utilitaire de ligne de commande openssl préfère les données codées PEM. Nous allons donc écrire un certificat codé PEM (remarque: il s'agit d'un certificat et non d'une clé publique. Il contient une clé publique, mais n'en est pas un). :

using (var cert = new X509Certificate2(someBytes, pass))
{
    StringBuilder builder = new StringBuilder();
    builder.AppendLine("-----BEGIN CERTIFICATE-----");
    builder.AppendLine(
        Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
    builder.AppendLine("-----END CERTIFICATE-----");

    return builder.ToString();
}

La clé privée est plus difficile. En supposant que la clé soit exportable (ce qui n'est pas le cas si vous utilisez Windows ou macOS, car vous n'avez pas déclaré X509KeyStorageFlags.Exportable), vous pouvez obtenir les paramètres avec privateKey.ExportParameters(true). Mais maintenant, vous devez l'écrire.

Une clé privée RSA est écrite dans un fichier codé PEM dont la balise est "RSA PRIVATE KEY" et dont la charge utile est ASN.1 ( UIT-T X.680 ) RSAPrivateKey (PKCS # 1/ RFC3447 ), généralement codée en DER ( UIT-T X.690 ) - bien qu’elle ne soit pas signée, il n’ya pas de restriction DER particulière, mais de nombreux lecteurs peuvent supposer DER.

Ou bien, il peut s'agir d'une clé privée PKCS # 8 ( RFC 5208 ) PrivateKeyInfo (balise: "CLE PRIVÉE") ou d'une clé EncryptedPrivateKeyInfo (balise: "CLE cryptée privée"). Puisque EncryptedPrivateKeyInfo enveloppe PrivateKeyInfo, qui encapsule RSAPrivateKey, nous allons commencer par là.

  RSAPrivateKey ::= SEQUENCE {
      version           Version,
      modulus           INTEGER,  -- n
      publicExponent    INTEGER,  -- e
      privateExponent   INTEGER,  -- d
      prime1            INTEGER,  -- p
      prime2            INTEGER,  -- q
      exponent1         INTEGER,  -- d mod (p-1)
      exponent2         INTEGER,  -- d mod (q-1)
      coefficient       INTEGER,  -- (inverse of q) mod p
      otherPrimeInfos   OtherPrimeInfos OPTIONAL
  }

Maintenant ignorez la partie sur otherPrimeInfos. exponent1 est DP, exponent2 est DQ et coefficient est InverseQ.

Travaillons avec une clé RSA 384 bits pré-publiée .

La RFC 3447 dit que nous voulons la version = 0. Tout le reste vient de la structure.

// SEQUENCE (RSAPrivateKey)
30 xa [ya [za]]
   // INTEGER (Version=0)
   02 01
         00
   // INTEGER (modulus)
   // Since the most significant bit if the most significant content byte is set,
   // add a padding 00 byte.
   02 31
         00
         DA CC 22 D8 6E 67 15 75 03 2E 31 F2 06 DC FC 19
         2C 65 E2 D5 10 89 E5 11 2D 09 6F 28 82 AF DB 5B
         78 CD B6 57 2F D2 F6 1D B3 90 47 22 32 E3 D9 F5
   // INTEGER publicExponent
   02 03
         01 00 01
   // INTEGER (privateExponent)
   // high bit isn't set, so no padding byte
   02 30
         DA CC 22 D8 6E 67 15 75 03 2E 31 F2 06 DC FC 19
         2C 65 E2 D5 10 89 E5 11 2D 09 6F 28 82 AF DB 5B
         78 CD B6 57 2F D2 F6 1D B3 90 47 22 32 E3 D9 F5
   // INTEGER (prime1)
   // high bit is set, pad.
   02 19
         00
         FA DB D7 F8 A1 8B 3A 75 A4 F6 DF AE E3 42 6F D0
         FF 8B AC 74 B6 72 2D EF
   // INTEGER (prime2)
   // high bit is set, pad.
   02 19
         00
         DF 48 14 4A 6D 88 A7 80 14 4F CE A6 6B DC DA 50
         D6 07 1C 54 E5 D0 DA 5B
   // INTEGER (exponent1)
   // no padding
   02 18
         24 FF BB D0 DD F2 AD 02 A0 FC 10 6D B8 F3 19 8E
         D7 C2 00 03 8E CD 34 5D
   // INTEGER (exponent2)
   // padding required
   02 19
         00
         85 DF 73 BB 04 5D 91 00 6C 2D 45 9B E6 C4 2E 69
         95 4A 02 24 AC FE 42 4D
   // INTEGER (coefficient)
   // no padding
   02 18
         1A 3A 76 9C 21 26 2B 84 CA 9C A9 62 0F 98 D2 F4
         3E AC CC D4 87 9A 6F FD

Maintenant, nous comptons le nombre d'octets entrés dans la structure RSAPrivateKey. Je compte 0xF2 (242). Comme il est supérieur à 0x7F, nous devons utiliser un codage de longueur sur plusieurs octets: 81 F2.

Alors maintenant, avec le tableau d'octets 30 81 F2 02 01 00 ... 9A 6F FD, vous pouvez le convertir en multi-lignes Base64 et l'envelopper dans une armure PEM "RSA PRIVATE KEY". Mais vous voulez peut-être un PKCS # 8.

  PrivateKeyInfo ::= SEQUENCE {
    version                   Version,
    privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
    privateKey                PrivateKey,
    attributes           [0]  IMPLICIT Attributes OPTIONAL }

  Version ::= INTEGER
  PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
  PrivateKey ::= OCTET STRING

Faisons-le à nouveau ... La RFC dit que nous voulons aussi la version = 0 ici. AlgorithmIdentifier peut être trouvé dans RFC5280 .

// SEQUENCE (PrivateKeyInfo)
30 xa [ya [za]]
   // INTEGER (Version=0)
   02 01
         00
   // SEQUENCE (PrivateKeyAlgorithmIdentifier / AlgorithmIdentifier)
   30 xb [yb [zb]]
      // OBJECT IDENTIFIER id-rsaEncryption (1.2.840.113549.1.1.1)
      06 09 2A 86 48 86 F7 0D 01 01 01
      // NULL (per RFC 3447 A.1)
      05 00
   // OCTET STRING (aka byte[]) (PrivateKey)
   04 81 F5
      [the previous value here,
       note the length here is F5 because of the tag and length bytes of the payload]

Remplir les longueurs:

La série "b" est 13 (0x0D), car elle ne contient que des objets de longueur prédéterminée.

La série "a" est maintenant (2 + 1) + (2 + 13) + (3 + 0xF5) = 266 (0x010A).

30 82 01 0A  02 01 00 30  0D ...

Maintenant, vous pouvez utiliser PEM comme "clé privée".

Le chiffrer? C'est un jeu de balle complètement différent.

8
bartonjs

J'ai trouvé une solution qui fonctionne bien. Je ne pouvais pas trouver un exemple EXACT montrant comment passer du magasin de certificats au fichier pem dans Windows. Certes, cela peut ne pas fonctionner avec certains certificats, mais si vous travaillez avec un certificat que vous avez créé vous-même (par exemple, si vous avez simplement besoin d'une sécurité entre deux machines que l'utilisateur final ne verra pas), c'est un bon moyen de revenons à pem/pk (style linux).

J'ai utilisé les utilitaires trouvés à http://www.bouncycastle.org/csharp/

X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);

X509Certificate2 caCert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "3C97BF2632ACAB5E35B48CB94927C4A7D20BBEBA", true)[0];


RSACryptoServiceProvider pkey = (RSACryptoServiceProvider)caCert.PrivateKey;


AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(pkey);
using (TextWriter tw = new StreamWriter("C:\\private.pem"))
{
    PemWriter pw = new PemWriter(tw);
    pw.WriteObject(keyPair.Private);
    tw.Flush();
}
0
CarComp