aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go165
1 files changed, 165 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..8d9a57f
--- /dev/null
+++ b/main.go
@@ -0,0 +1,165 @@
+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 2>/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)
+}
+