package main import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "flag" "fmt" "log" "net" "net/http" "net/url" "os" "text/tabwriter" ) func cloneCertificateTemplate(orig *x509.Certificate) *x509.Certificate { return &x509.Certificate{ SerialNumber: orig.SerialNumber, SignatureAlgorithm: orig.SignatureAlgorithm, Issuer: orig.Issuer, Subject: orig.Subject, NotBefore: orig.NotBefore, NotAfter: orig.NotAfter, KeyUsage: orig.KeyUsage, ExtKeyUsage: orig.ExtKeyUsage, UnknownExtKeyUsage: orig.UnknownExtKeyUsage, BasicConstraintsValid: orig.BasicConstraintsValid, IsCA: orig.IsCA, MaxPathLen: orig.MaxPathLen, MaxPathLenZero: orig.MaxPathLenZero, DNSNames: orig.DNSNames, EmailAddresses: orig.EmailAddresses, IPAddresses: orig.IPAddresses, URIs: orig.URIs, PolicyIdentifiers: orig.PolicyIdentifiers, CRLDistributionPoints: orig.CRLDistributionPoints, OCSPServer: orig.OCSPServer, IssuingCertificateURL: orig.IssuingCertificateURL, ExtraExtensions: orig.Extensions, SubjectKeyId: orig.SubjectKeyId, AuthorityKeyId: orig.AuthorityKeyId, } } func cloneCertificate(urlStr string) (certFilename, keyFilename string) { writer := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', tabwriter.AlignRight) parsedURL, err := url.Parse(urlStr) if err != nil { log.Fatalf("[err] invalid url: %v", err) } host := parsedURL.Host if parsedURL.Port() == "" { host = host + ":443" } domain := parsedURL.Host if h, _, err := net.SplitHostPort(parsedURL.Host); err == nil { domain = h } certFilename = domain + "_clone.pem" keyFilename = domain + "_clone.key" conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatalf("[err] failed to connect to %s: %v", host, err) } defer conn.Close() state := conn.ConnectionState() if len(state.PeerCertificates) == 0 { log.Fatal("[err] no certificates found on the target server") } origCert := state.PeerCertificates[0] template := cloneCertificateTemplate(origCert) rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatalf("[err] failed to generate rsa key: %v", err) } priv := rsaKey pub := &rsaKey.PublicKey parent := cloneCertificateTemplate(origCert) parent.Subject = origCert.Issuer certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv) if err != nil { log.Fatalf("[err] failed to create fake certificate: %v", err) } certOut, err := os.Create(certFilename) if err != nil { log.Fatalf("[err] failed to open %s for writing: %v", certFilename, err) } defer certOut.Close() if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil { log.Fatalf("[err] failed to write certificate: %v", err) } keyOut, err := os.Create(keyFilename) if err != nil { log.Fatalf("[err] failed to open %s for writing: %v", keyFilename, err) } defer keyOut.Close() if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaKey)}); err != nil { log.Fatalf("[err] failed to write private key: %v", err) } fmt.Fprintln(writer, "url\tcloned cert\tprivate key") fmt.Fprintf(writer, "%s\t%s\t%s", host, certFilename, keyFilename) writer.Flush() fmt.Println() fmt.Println() fmt.Println("[inf] start an https server to test cloned certificate with:") fmt.Printf("$ %s -cert %s -key %s -port 8000\n", os.Args[0], certFilename, keyFilename) fmt.Println() fmt.Println("[inf] manually inspect and diff the original certificate and cloned certificate with:") fmt.Printf("$ openssl s_client -connect %s /dev/null | openssl x509 -noout -text > %s_original.txt\n", host, parsedURL.Host) fmt.Printf("$ openssl x509 -in %s -noout -text > %s_clone.txt\n", certFilename, certFilename) fmt.Println("$ diff *.txt") return } func runHTTPSServer(certPath, keyPath string, portNumber string) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, world!") }) fmt.Printf("[inf] starting https server on https://127.0.0.1:%s\n", portNumber) err := http.ListenAndServeTLS("127.0.0.1:"+portNumber, certPath, keyPath, nil) if err != nil { log.Fatalf("[err] failed to start httpsserver: %v", err) } } func main() { urlFlag := flag.String("url", "", "target https url to clone certificate from (e.g. https://google.com)") certFlag := flag.String("cert", "", "path to certificate file to use for a test https server") keyFlag := flag.String("key", "", "path to key file to use for a test https server") portFlag := flag.String("port", "8000", "port to use for a test https server") flag.Parse() if *certFlag != "" || *keyFlag != "" { if *certFlag == "" || *keyFlag == "" { log.Fatal("[err] both -cert and -key must be supplied to run the HTTPS server") } runHTTPSServer(*certFlag, *keyFlag, *portFlag) return } if *urlFlag != "" { cloneCertificate(*urlFlag) return } flag.Usage() os.Exit(1) }