summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile16
-rw-r--r--domains.txt4
-rwxr-xr-xgetcerts28
-rw-r--r--pem2html.go142
5 files changed, 193 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..98b607b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+certs.*
+pem2html
+NET-*
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..432518b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+all: certs.html
+
+pem2html: %: %.go
+ go build $<
+
+certs.pem: getcerts domains.txt NET-crt.sh
+ ./getcerts $$(cat domains.txt) > $@
+
+certs.html: %.html: %.pem pem2html
+ ./pem2html < $< > $@
+
+NET-%:
+ touch $@
+
+.DELETE_ON_ERROR:
+.SECONDARY:
diff --git a/domains.txt b/domains.txt
new file mode 100644
index 0000000..390a92e
--- /dev/null
+++ b/domains.txt
@@ -0,0 +1,4 @@
+parabola.nu
+parabolagnulinux.org
+lukeshu.com
+team4272.com
diff --git a/getcerts b/getcerts
new file mode 100755
index 0000000..0191e2e
--- /dev/null
+++ b/getcerts
@@ -0,0 +1,28 @@
+#!/usr/bin/env ruby
+require 'nokogiri'
+require 'open-uri'
+
+certs = {}
+ARGV.each do |domain|
+ [ domain, "%.#{domain}" ].each do |pattern|
+ Nokogiri::XML(open("https://crt.sh/atom?identity=#{pattern}&exclude=expired")).css('feed > entry').each do |entry|
+ url = entry.css('id').first.text.split("#").first
+
+ updated = entry.css('updated').first.text
+
+ html = Nokogiri::HTML(entry.css('summary').first.text)
+ html.css('br').each{|br| br.replace("\n")}
+ pem = html.css('div').first.text
+
+ lines = pem.split("\n")
+ lines.insert(1, "X-Crt-Sh-Url: #{url}", "X-Crt-Sh-Updated: #{updated}")
+ pem = lines.join("\n")+"\n"
+
+ certs[url] = pem
+ end
+ end
+end
+
+certs.each do |url, pem|
+ print pem
+end
diff --git a/pem2html.go b/pem2html.go
new file mode 100644
index 0000000..be0405d
--- /dev/null
+++ b/pem2html.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "time"
+ "html/template"
+ "sort"
+)
+
+func handleErr(err error, str string, a ...interface{}) {
+ a = append([]interface{}{err}, a...)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, str, a...)
+ os.Exit(1)
+ }
+}
+
+func handleBool(ok bool, str string, a ...interface{}) {
+ if !ok {
+ fmt.Fprintf(os.Stderr, str, a...)
+ }
+}
+
+var tmpl = template.Must(template.New("pem2html").Parse(`<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>CT log</title>
+ <style>
+ html {
+ height: 100%;
+ }
+ body {
+ font-size: 10px;
+ font-family: monospace;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ }
+ body > * {
+ margin: auto;
+ }
+ table {
+ border-collapse: collapse;
+ }
+ td, th {
+ padding: 0.1em 0.25em;
+ border: solid 1px black;
+ white-space: nowrap;
+ }
+ td {
+ background-color: #F3F3F3;
+ }
+ td a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ color: black;
+ }
+ </style>
+</head>
+<body>
+<table>
+ <tr>
+ <th>Logged</th>
+ <th>NotBefore</th>
+ <th>NotAfter</th>
+ <th>Subject.CN</th>
+ <th>Issuer.O</th>
+ </tr>
+{{range $cert := .}}
+ <tr>
+ <td><a href="{{$cert.Url}}">{{$cert.Updated.Local.Format "2006-01-02 15:04:05"}}</a></td>
+ <td><a href="{{$cert.Url}}">{{$cert.X509.NotBefore.Local.Format "2006-01-02"}}</a></td>
+ <td><a href="{{$cert.Url}}">{{$cert.X509.NotAfter.Local.Format "2006-01-02"}}</a></td>
+ <td><a href="{{$cert.Url}}">{{$cert.X509.Subject.CommonName}}</a></td>
+ <td><a href="{{$cert.Url}}">{{$cert.X509.Issuer.Organization}}</a></td>
+ </tr>
+{{end}}
+</table>
+</body>
+</html>`))
+
+type Cert struct {
+ Url string
+ Updated time.Time
+ X509 *x509.Certificate
+}
+
+type Certs []Cert
+
+ // Len is the number of elements in the collection.
+func (l Certs) Len() int {
+ return len(l)
+}
+
+// Less reports whether the element with
+// index i should sort before the element with index j.
+func (l Certs) Less(i, j int) bool {
+ return l[i].Updated.UTC().After(l[j].Updated.UTC())
+}
+
+// Swap swaps the elements with indexes i and j.
+func (l Certs) Swap(i, j int) {
+ tmp := l[i]
+ l[i] = l[j]
+ l[j] = tmp
+}
+
+func main() {
+ data, err := ioutil.ReadAll(os.Stdin)
+ handleErr(err, "Error reading stdin: %v\n")
+
+ var certs Certs
+ for len(data) > 0 {
+ var certPem *pem.Block
+ certPem, data = pem.Decode(data)
+
+ var ok bool
+ var cert Cert
+
+ cert.Url, ok = certPem.Headers["X-Crt-Sh-Url"]
+ handleBool(ok, "Did not get X-Crt-Sh-Url\n")
+
+ str, ok := certPem.Headers["X-Crt-Sh-Updated"]
+ handleBool(ok, "Did not get X-Crt-Sh-Updated\n")
+ cert.Updated, err = time.Parse("2006-01-02T15:04:05Z", str)
+ handleErr(err, "Could not parse updated time")
+
+ cert.X509, err = x509.ParseCertificate(certPem.Bytes)
+ handleErr(err, "Error parsing cert: %v\n")
+
+ certs = append(certs, cert)
+ }
+
+ sort.Sort(certs)
+ tmpl.Execute(os.Stdout, certs)
+}