Klonen von (x.509) Zertifikaten

vorhergehende Artikel in: PKI-X.509-CA Java Security
25.01.2026

Es existieren diverse Angebote in den Weiten des Internet, die dabei unterstützen (sollen), Zertifikate zu klonen...

Sofern es sich - wie bei dem Code, den ich hier präsentieren werde - nicht um eine bloße intellektuelle Herausforderung handelt, muss man sich fragen: Warum tun Menschen so etwas und kann daraus ein Security-Risiko entstehen?

Zunächst einmal ist es durchaus möglich, dass es zu einem Security-Risiko wird: Nämlich dann, wenn der Vertrauenswürdigkeit des Ausstellers nicht geprüft wird. In diesem Fall wird die Benutzung von Zertifikaten von einer Maßnahme, die die Identität des Kommunikationspartners nachweist und die Vertraulichkeit der Nachricht sicherstellt zu einer, die nur noch die Vertraulichkeit sicherstellen soll.

Dies wird aber durch eine - dann sehr einfach durchführbare - Man-im-the-Middle-Attacke ebenfalls ausgehebelt.

Man kann durch eine entsprechende Gestaltung des Zertifikats und speziell der enthaltenen Extensions diese Gefahr senken - allerdings muss man sich dann auch fragen lassen, warum man die viel einfachere und bewährte Schutzmaßnahme der Prüfung der Vertrauenswürdigkeit nicht einfach anwendet?

Natürlich ist es komplex, einen Truststore zu pflegen, wenn viele Kommunikationspartner verwaltet werden müssen, die auf self-signed Zertifikate setzen. Man muss ja jeden einzelnen der Kommunikationspartner im Truststore nachpflegen. Praktisch unmöglich wird das, wenn die Anzahl der Kommunikationspartner sich ändert und immer wider neue hinzukommen können und alte die Kommunikation aufgeben.

Aber auch hier gibt es eine einfache Lösung: Nutze keine self-signed Zertifikate, sondern eine PKI, die es dann erlaubt, einen oder wenige Trust-Anchors im Truststore zu verankern!

Kommen wir aber nun zur eigentlichen Frage: Wie schwierig ist es, ein Zertifikat zu klonen? Vorausschicken möchte ich, dass dies nur dann benötigt wird, wenn die vorliegende Implementierung irgendwelche selbst ausgedachten Verifikationsideen implementiert, die vom Inhalt des Zertifikats abhängen. Besonders schlimm wäre hier zum Beispiel die Prüfung anhand bestimmter Inhalte im Distinguished Name (DN).

Das Klonen selbst ist sehr einfach: Der hier präsentierte Code erledigt das unter Zuhilfenahme von Bouncycastle in Java sehr effizient:

import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

public class CloneCertificate extends java.lang.Object { public static final String HASH_ALGO = "SHA256"; public static java.lang.String input="<specify full path of certificate to clone!>" public static java.lang.String output="<specify full path of certificate clone!>"

public CloneCertificate() throws CertificateException, IOException, OperatorCreationException, NoSuchAlgorithmException { super(); java.util.Collection<X509Certificate> certs=de.elbosso.util.security.Utilities.loadCertificates(new java.io.File(input)); X509Certificate cert=certs.iterator().next(); System.out.println(cert.getNotBefore()); System.out.println(cert.getNotAfter());

final var keyPair = generateKey();

final var privateKey = keyPair.getPrivate();

final var contentSigner = new JcaContentSignerBuilder(HASH_ALGO + "with" + privateKey.getAlgorithm()).build(privateKey); final var x500Name = cert.getSubjectX500Principal(); final var certificateBuilder = new JcaX509v3CertificateBuilder(x500Name, cert.getSerialNumber(), cert.getNotBefore(), cert.getNotAfter(), x500Name, cert.getPublicKey()); //.copyAndAddExtension() java.util.Set<java.lang.String> oids=cert.getCriticalExtensionOIDs(); System.out.println(oids.size()); byte[] ref=cert.getExtensionValue(Extension.authorityKeyIdentifier.toString()); for(java.lang.String oid:oids) { if(oid.equals(Extension.authorityKeyIdentifier.toString())==false) certificateBuilder.copyAndAddExtension(new ASN1ObjectIdentifier(oid),true,cert); } java.util.Set<java.lang.String> ncOids=cert.getNonCriticalExtensionOIDs(); System.out.println(ncOids.size()); for(java.lang.String oid:ncOids) { if(oid.equals(Extension.authorityKeyIdentifier.toString())==false) certificateBuilder.copyAndAddExtension(new ASN1ObjectIdentifier(oid),false,cert); }

//.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyId(keyPair.getPublic())) if(ref!=null) certificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, createAuthorityKeyId(cert.getPublicKey())); //.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));

X509Certificate newCert=(X509Certificate)new JcaX509CertificateConverter() .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner));

java.io.ByteArrayOutputStream baos=new java.io.ByteArrayOutputStream(); java.io.OutputStreamWriter writer = new java.io.OutputStreamWriter(baos); PEMWriter pem = new PEMWriter(writer); pem.writeObject(newCert); // force the pem write to flush it's data - kind of abnoxious you have to do that pem.flush(); pem.close(); baos.close(); System.out.println(baos.toString()); java.io.PrintWriter pw=new java.io.PrintWriter(new java.io.File(output)); pw.println(baos.toString()); pw.close(); } public static void main(java.lang.String[] args) throws CertificateException, IOException, OperatorCreationException, NoSuchAlgorithmException { new CloneCertificate(); } public static KeyPair generateKey() throws NoSuchAlgorithmException { final int size = 2048; final var generator = KeyPairGenerator.getInstance("RSA"); final var secureRandom = SecureRandom.getInstance("NativePRNG"/* + "Blocking" */);

// this is expensive! generator.initialize(size, secureRandom);

return generator.generateKeyPair(); } public static AuthorityKeyIdentifier createAuthorityKeyId(final PublicKey publicKey) throws OperatorCreationException { final var publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); final var digCalc = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); return new X509ExtensionUtils((digCalc)).createAuthorityKeyIdentifier(publicKeyInfo); } }

Ich habe diesen Code mit einem recht komplexen Zertifikat getestet, das ich auch als Test für diverse andere Angebote aus dem Internet benutzt habe. Alle von mir getesteten Alternativen haben es nicht geschafft, das hier präsentierte Zertifikat erfolgreich zu klonen. Der mittels des oben abgebildeten Code entstandene clone weist einige Abweichungen zum Original auf - ich habe zur besseren Illustration die Extensions im Klon entsprechend der Anordnung im Original manuell geordnet und mittels git diff --color 0D_decoded.txt 0D_clone_decoded_reordered.txt ein diff erzeugt:

 [1mdiff --git a/tests/0D_decoded.txt b/tests/0D_clone_decoded_reordered.txt [m
 [1mindex a040953..d406ce1 100644 [m
 [1m--- a/tests/0D_decoded.txt [m
 [1m+++ b/tests/0D_clone_decoded_reordered.txt [m
 [36m@@ -1,16 +1,16 @@ [m
 Certificate: [m
     Data: [m
         Version: 3 (0x2) [m
         Serial Number: 13 (0xd) [m
         Signature Algorithm: sha256WithRSAEncryption [m
 [31m-        Issuer: C = DE, O = Damaschkestr. 11, OU = Arbeitszimmer, CN = testcomp [m
 [32m+ [m [32m        Issuer: C = DE, ST = Thueringen, L = Rudolstadt, O = Damaschkestr. 11, OU = Arbeitszimmer, CN = sd [m
         Validity [m
             Not Before: Apr 28 13:49:14 2023 GMT [m
             Not After : Apr 28 23:59:59 2026 GMT [m
         Subject: C = DE, ST = Thueringen, L = Rudolstadt, O = Damaschkestr. 11, OU = Arbeitszimmer, CN = sd [m
         Subject Public Key Info: [m
             Public Key Algorithm: rsaEncryption [m
                 Public-Key: (4096 bit) [m
                 Modulus: [m
                     00:ca:cd:f6:c9:82:4e:22:c3:c9:9d:5a:a3:1d:8d: [m
                     49:11:c5:47:ad:2f:0f:59:68:c1:c0:2d:56:34:02: [m
 [36m@@ -51,21 +51,21 @@ [m  [mCertificate: [m
         X509v3 extensions: [m
             X509v3 Key Usage: critical [m
                 Digital Signature, Key Encipherment [m
             X509v3 Basic Constraints:  [m
                 CA:FALSE [m
             X509v3 Extended Key Usage:  [m
                 E-mail Protection, Microsoft Encrypted File System, TLS Web Client Authentication, Microsoft Smartcard Login [m
             X509v3 Subject Key Identifier:  [m
                 6A:66:0F:D3:48:43:25:EF:C2:BB:A1:61:5E:2F:78:44:4D:9F:2C:62 [m
             X509v3 Authority Key Identifier:  [m
 [31m-                E0:53:E5:67:98:FC:47:A8:5D:0B:F1:A9:79:6C:BC:33:20:C3:55:DD [m
 [32m+ [m [32m                6A:66:0F:D3:48:43:25:EF:C2:BB:A1:61:5E:2F:78:44:4D:9F:2C:62 [m
             Authority Information Access:  [m
                 CA Issuers - URI:https://elbosso.github.io/x509//test3-ca.crt [m
             X509v3 CRL Distribution Points:  [m
                 Full Name: [m
                   URI:https://elbosso.github.io/x509//test3-ca.crl [m
             1.2.3.4.5.6.7.8.9:  [m
                 0.0...+en.......4Ug. [m
             CT Precertificate SCTs:  [m
                 Signed Certificate Timestamp: [m
                     Version   : v1 (0x0) [m
 [36m@@ -77,63 +77,52 @@ [m  [mCertificate: [m
                                 30:45:02:20:60:6E:10:AE:5C:2D:5A:1B:0A:ED:49:DC: [m
                                 49:37:F4:8D:E7:1A:4E:97:84:E9:C2:08:DF:BF:E9:EF: [m
                                 53:6C:F7:F2:02:21:00:BE:B2:9C:72:D7:D0:6D:61:D0: [m
                                 6B:DB:38:A0:69:46:9A:A8:6F:E1:2E:18:BB:7C:C4:56: [m
                                 89:A2:C0:18:7E:F5:A5 [m
             X509v3 Subject Alternative Name:  [m
                 IP Address:10.10.10.13, IP Address:10.10.10.14, IP Address:10.10.10.17, DNS:expect.example.com, DNS:ca.example.com, othername: 1.2.3.4.5.6.7.2::<unsupported>, URI:http://my.url.here/, email:q@q.qq, DirName:/C=DE/O=EMA/OU=Security/CN=ServerName [m
             1.3.6.1.4.1.311.20.2:  [m
                 ..descriptionb [m
             S/MIME Capabilities:  [m
 [31m-                0|0...+....0...`.H.e....0...+....0...`.H.e...)0...`.H.e...*0...`.H.e...+0...`.H.e....0...`.H.e....0...`.H.e....0
..*.H..
..... [m
 [32m+ [m [32m                0|0...+....0...`.H.e....0...+....0...`.H.e...)0...`.H.e...*0...`.H.e...+0...`.H.e....0...`.H.e....0...`.H.e....0 [m
 [32m+ [m [32m..*.H.. [m
 [32m+ [m [32m..... [m
             1.2.3.4.5.6.7.1:  [m
                 . [m
 UTF8STRING [m
             1.2.3.4.5.6.7.2:  [m
                 ..PRINTABLESTRING [m
             1.2.3.4.5.6.7.3:  [m
                 ..IA5STRING [m
             1.2.3.4.5.6.7.4:  [m
 [31m-                .
VISIBLESTRING [m
 [32m+ [m [32m                . [m
 [32m+ [m [32mVISIBLESTRING [m
             1.2.3.4.5.6.7.5:  [m
                 ..9604152030Z [m
             1.2.3.4.5.6.7.6:  [m
                 ..2210101418+0400 [m
             1.2.3.4.5.6.7.7:  [m
                 ...."3DUfw........ [m
             1.2.3.4.5.6.7.8:  [m
                 .#.0x04112233445566778899aabbccddeeff [m
             1.2.3.4.5.6.7.9:  [m
                 ..202210101418+0400 [m
             1.2.3.4.5.6.7.10:  [m
                 ....."3DUfw........ [m
     Signature Algorithm: sha256WithRSAEncryption [m
     Signature Value: [m
 [31m-        27:79:cc:14:22:57:1a:56:bd:e8:b3:e7:8d:6a:66:4c:94:a1: [m
 [31m-        6c:d1:d6:89:ae:fa:32:b9:02:c8:c0:58:0c:0f:ba:2f:c8:ee: [m
 [31m-        db:c2:87:57:14:8c:08:af:b6:83:d3:bc:6e:ae:e3:77:28:bd: [m
 [31m-        90:ad:14:94:41:ea:99:90:b6:c9:b7:22:5b:0a:77:d4:68:e1: [m
 [31m-        21:6b:3c:7f:4b:66:c0:9c:7e:c1:ae:d9:8f:55:ec:03:66:b1: [m
 [31m-        09:0f:ee:f8:38:ee:af:91:e0:7d:97:ef:e1:43:4b:3a:5a:3c: [m
 [31m-        f2:0e:85:15:5a:3b:51:09:98:5c:3b:cf:d5:56:26:e4:fe:ce: [m
 [31m-        b6:10:ac:ec:54:32:dc:56:d6:48:87:3d:22:b2:ea:22:a9:14: [m
 [31m-        f3:1b:2b:0b:59:c6:3a:62:4b:9f:f3:be:5f:5d:2f:f4:1b:3e: [m
 [31m-        3c:05:f1:48:52:ab:02:cb:8b:d8:e5:6c:14:47:a2:4b:54:07: [m
 [31m-        bb:f8:20:ea:b6:12:dc:ef:d5:b6:d8:86:a8:69:35:ee:4f:9b: [m
 [31m-        74:2f:b7:fe:bd:7b:ac:8f:c6:e6:fa:4f:89:8c:b4:05:4b:d9: [m
 [31m-        c8:d7:3a:9a:98:07:b1:64:83:ad:c5:ec:1c:20:f6:4b:b3:a3: [m
 [31m-        09:61:12:97:22:fd:cf:e9:b3:59:2c:64:39:cf:63:9b:2a:a7: [m
 [31m-        dd:db:7f:c0:e1:61:6f:7b:45:75:9b:f9:35:9e:62:83:6e:88: [m
 [31m-        9a:ac:be:d2:a8:68:1d:ae:af:15:88:3c:24:ef:88:4d:a5:00: [m
 [31m-        65:c2:6d:4b:85:31:cc:20:8c:eb:3a:40:81:7a:f0:9d:dd:ed: [m
 [31m-        30:c1:6c:de:bd:00:60:93:cb:8c:5c:d6:ad:b7:38:40:5e:da: [m
 [31m-        70:d1:2e:94:ab:a2:9f:62:a2:27:f8:53:78:d4:0c:09:99:66: [m
 [31m-        b8:85:75:7f:30:ae:a8:10:6b:0d:91:3b:27:50:6a:56:7c:70: [m
 [31m-        d3:c4:05:96:51:29:8f:fd:41:44:c1:73:71:48:e3:1f:38:ae: [m
 [31m-        3d:c0:a6:c0:ef:6e:54:24:c2:91:14:7e:e7:63:b2:7d:ce:c0: [m
 [31m-        9c:6d:9a:b8:b2:a9:93:df:97:99:97:d4:5b:3b:eb:8c:ba:41: [m
 [31m-        af:ce:d1:46:99:86:81:81:24:bf:f0:6f:a8:e1:65:b2:b4:b1: [m
 [31m-        05:13:80:58:d0:a8:26:66:0a:fa:e3:16:7f:8b:12:e6:52:7d: [m
 [31m-        aa:7c:7f:e9:71:65:ba:50:7b:bf:ae:83:15:af:8d:52:56:1d: [m
 [31m-        4b:42:f1:15:51:ad:fd:c1:ee:f6:b4:c9:de:71:12:cb:8b:52: [m
 [31m-        3e:c0:32:6c:3c:53:19:d8:03:8d:b3:8b:7e:42:1d:d5:dd:ef: [m
 [31m-        42:a7:eb:5a:99:fc:4f:09 [m
 [32m+ [m [32m        a3:dd:c6:71:d0:20:07:5d:44:e4:f9:81:0a:ea:a4:a2:5d:60: [m
 [32m+ [m [32m        90:66:13:8f:71:89:1e:29:d8:ae:44:0e:10:75:ec:e1:34:eb: [m
 [32m+ [m [32m        35:13:ac:be:f1:85:5f:06:bf:e4:1e:25:59:f6:97:a9:ec:87: [m
 [32m+ [m [32m        0d:cb:94:1f:b4:c7:d8:8c:6a:99:a1:60:8a:97:b9:a0:59:ab: [m
 [32m+ [m [32m        c3:dd:b4:e4:be:19:cb:b1:f0:f7:5a:78:1b:7d:d4:96:14:f4: [m
 [32m+ [m [32m        83:85:eb:11:91:4b:79:9b:b6:df:fa:55:f3:63:88:b5:c1:f3: [m
 [32m+ [m [32m        56:3b:d7:67:2c:e9:b0:c3:89:6c:3d:0d:bf:9c:ac:10:dc:be: [m
 [32m+ [m [32m        f0:bf:81:97:cd:5b:4f:7d:c2:31:cc:a7:5b:b5:31:39:5d:49: [m
 [32m+ [m [32m        19:b5:6f:45:ff:a9:70:bd:8e:26:ba:2c:61:b1:09:be:ad:27: [m
 [32m+ [m [32m        76:9a:2a:2a:0f:4a:07:e7:2b:e6:cd:61:40:16:ed:aa:f9:26: [m
 [32m+ [m [32m        2f:fc:66:cc:06:87:97:fa:c7:17:7d:52:77:78:d2:42:e3:ac: [m
 [32m+ [m [32m        99:a4:02:86:a0:be:ac:3a:cc:84:d4:bf:a4:e9:c9:4c:4d:c7: [m
 [32m+ [m [32m        5d:12:73:fb:cb:08:1b:35:92:36:81:bd:c4:2b:ce:22:e7:a8: [m
 [32m+ [m [32m        d0:f2:40:32:dd:51:d2:7f:41:a1:a1:90:d6:4a:5b:d3:32:71: [m
 [32m+ [m [32m        48:b7:71:43 [m

Man sieht zunächst einmal die Unterschiede, die durch den Prozess des Klonens auftreten müssen: Der DN des Herausgebers ebenso wie der X509v3 Authority Key Identifier und der WErt der Signatur müssen sich unterscheiden, da wir ja einen anderen Schlüssel als den des Original verwenden müssen. Interessant ist der Unterschied in der proprietären Extension S/MIME Capabilities - hier wie in der ebenfalls proprietären Extension 1.2.3.4.5.6.7.4 scheint der Prozess des Klonens wegen des Vorhandenseins des Zeichens 0xD ins Schleudern zu kommen.

Man sieht auch sehr schön, dass das Klonen mit steigender Komplexität des Zertifikats immer schwieriger wird: Das Original enthielt die Extension CT Precertificate SCTs, die aber für selbstsignierte Zertifikate Unsinn ist und also aus dem Klon ausgefiltert werden müsste. Desgleichen findet man die Extensions Authority Information Access und X509v3 CRL Distribution Points, deren URLs natürlich jetzt auf die falsche Location zeigen - hier müsste der Angreifer eigentlich die URLs auf eigene umbiegen, damit das Gesamtbild wieder konsistent wird. Auf der anderen Seite: Ein Ziel, das die Authentizität des Absenders einer Nachricht nicht prüft, wird wahrscheinlich auch keinen Abgleich des Zertifikats mit der Online-Referenz durchführen - ebensowenig wie die Kontrolle des Revocation-Status, weswegen ein Angreifer diese Extensions einfach aus dem Klon entfernen würde...

Die PEM-kodierten Zertifikate als Original und Klon habe ich der Vollständigkeit haltber ebenfalls angefügt.

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • neues GitHub-Projekt: s3storagefrontend

    25.01.2021

    Neulich brauchte ich die Möglichkeit, eine Datei schnell auf einen anderen Rechner zu transferieren. Früher hätte man einen FTP-Server oder einen Samba-Server aufgesetzt und das dann damit erledigt. Ich kam ins Überlegen...

    Weiterlesen

Neueste Artikel

  • Asymmetrische Kryptographie

    Ich habe mich mit der Idee schon länger getragen: Nochmal einen Rundumschlag zu asymmetrischer Kryptographie zu machen. Dabei werde ich mich auf Demonstrationen der einzelnen Konzepte und Operationen mit Beispielcode konzentrieren und zu jedem der vorgestellten Konzepte mehr oder weniger ausführlich bezüglich der Einsatzszenarien und Vor- und Nachteile Stellung beziehen

    Weiterlesen
  • Hibernation in Ubuntu 2025

    Nachdem nun der endgültige Todesstoß für Windows 10 erfolgt ist (Ich weiß, ich weiß - Microsoft hat den Support um ein Jahr ausgeweitet. Das kommt aber nicht in Frage, weil man dafür ein Microsoft-Konto eröffnen muss!), habe ich mein einziges noch verbliebenes Windows-System (die Steambox) vom Netzwerk getrennt habe, habe ich mich nach anderen Optionen umgesehen und bin auf ein offenes Problem gestoßen

    Weiterlesen
  • LinkCollections 2025 XII

    Nach der letzten losen Zusammenstellung (für mich) interessanter Links aus den Tiefen des Internet von 2025 folgt hier gleich die nächste:

    Weiterlesen

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen Codeberg-Repositories findet man hier.