Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support AIA #3

Merged
merged 4 commits into from
Feb 17, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 271 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package main

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"io"
"io/ioutil"
"log"
"math/big"
"net/http"
"strings"
"sync"
Expand All @@ -32,6 +38,7 @@ var (
rootPriv interface{}
rootCertPem []byte
rootCertPemString string
rootPrivPem []byte
tldCert []byte
tldPriv interface{}
tldCertPem []byte
Expand All @@ -51,6 +58,12 @@ var (
"resolver will be used.)")
dnsPortFlag = cflag.Int(flagGroup, "port", 53, "Use this port for "+
"DNS lookups.")
listenIP = cflag.String(flagGroup, "listen-ip", "127.127.127.127",
"Listen on this IP address.")
listenHTTPS = cflag.Bool(flagGroup, "listen-https", false,
"Listen on HTTPS (RFC 5280 Sec. 8 says you SHOULD NOT do this)")
generateCerts = cflag.Bool(flagGroup, "generate-certs", false,
"Generate certificates and exit")
)

func getCachedDomainCerts(commonName string) (string, bool) {
Expand Down Expand Up @@ -210,8 +223,8 @@ func lookupHandler(w http.ResponseWriter, req *http.Request) {
}
// Set qtype to TLSA
args = append(args, "TLSA")
// Set qname to TCP port 443 of requested hostname
args = append(args, "_443._tcp." + domain)
// Set qname to all protocols and all ports of requested hostname
args = append(args, "*." + domain)

result, err := qparams.Do(args)
if err != nil {
Expand All @@ -232,8 +245,8 @@ func lookupHandler(w http.ResponseWriter, req *http.Request) {
return
}
if dnsResponse.MsgHdr.Rcode == dns.RcodeNameError {
// TCP port 443 subdomain doesn't exist.
// That means the domain doesn't use DANE.
// Wildcard subdomain doesn't exist.
// That means the domain doesn't use Namecoin-form DANE.
// Return an empty cert list
return
}
Expand Down Expand Up @@ -273,6 +286,129 @@ func lookupHandler(w http.ResponseWriter, req *http.Request) {
}
}

func aiaHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/pkix-cert")

domain := req.FormValue("domain")

if domain == "Namecoin Root CA" {
io.WriteString(w, string(rootCert))

return
}

if domain == ".bit TLD CA" {
io.WriteString(w, string(tldCert))

return
}

domain = strings.TrimSuffix(domain, " Domain AIA Parent CA")

if strings.Contains(domain, " ") {
// CommonNames that contain a space are usually CA's. We
// already stripped the suffixes of Namecoin-formatted CA's, so
// if a space remains, just return.
w.WriteHeader(404)
return
}

qparams := qlib.DefaultParams()
qparams.Port = dnsPortFlag.Value()
qparams.Ad = true
qparams.Fallback = true
qparams.Tcp = true // Workaround for https://github.com/miekg/exdns/issues/19

args := []string{}
// Set the custom DNS server if requested
if dnsAddressFlag.Value() != "" {
args = append(args, "@" + dnsAddressFlag.Value())
}
// Set qtype to TLSA
args = append(args, "TLSA")
// Set qname to all protocols and all ports of requested hostname
args = append(args, "*." + domain)

result, err := qparams.Do(args)
if err != nil {
// A DNS error occurred.
log.Printf("qlib error: %s", err)
w.WriteHeader(500)
return
}
if result.ResponseMsg == nil {
// A DNS error occurred (nil response).
w.WriteHeader(500)
return
}
dnsResponse := result.ResponseMsg
if dnsResponse.MsgHdr.Rcode != dns.RcodeSuccess && dnsResponse.MsgHdr.Rcode != dns.RcodeNameError {
// A DNS error occurred (return code wasn't Success or NXDOMAIN).
w.WriteHeader(500)
return
}
if dnsResponse.MsgHdr.Rcode == dns.RcodeNameError {
// Wildcard subdomain doesn't exist.
// That means the domain doesn't use Namecoin-form DANE.
// Return an empty cert list
w.WriteHeader(404)
return
}
if dnsResponse.MsgHdr.AuthenticatedData == false && dnsResponse.MsgHdr.Authoritative == false {
// For security reasons, we only trust records that are
// authenticated (e.g. server is Unbound and has verified
// DNSSEC sigs) or authoritative (e.g. server is ncdns and is
// the owner of the requested zone). If neither is the case,
// then return an empty cert list.
w.WriteHeader(404)
return
}

pubSHA256Hex := req.FormValue("pubsha256")
pubSHA256, err := hex.DecodeString(pubSHA256Hex)
if err != nil {
// Requested public key hash is malformed.
w.WriteHeader(404)
return
}

for _, rr := range dnsResponse.Answer {
tlsa, ok := rr.(*dns.TLSA)
if !ok {
// Record isn't a TLSA record
continue
}

// CA not in user's trust store; public key; not hashed
if tlsa.Usage == 2 && tlsa.Selector == 1 && tlsa.MatchingType == 0 {
tlsaPubBytes, err := hex.DecodeString(tlsa.Certificate)
if err != nil {
// TLSA record is malformed
continue
}

tlsaPubSHA256 := sha256.Sum256(tlsaPubBytes)
if !bytes.Equal(pubSHA256, tlsaPubSHA256[:]) {
// TLSA record doesn't match requested public key hash
continue
}
} else {
// TLSA record isn't in the Namecoin CA form
continue
}

safeCert, err := safetlsa.GetCertFromTLSA(domain, tlsa, tldCert, tldPriv)
if err != nil {
// TODO: quiet this warning
log.Printf("GetCertFromTLSA: %s", err)
continue
}

io.WriteString(w, string(safeCert))
break
}
}

func getNewNegativeCAHandler(w http.ResponseWriter, req *http.Request) {
restrictCert, restrictPriv, err := safetlsa.GenerateTLDExclusionCA("bit", rootCert, rootPriv)
if err != nil {
Expand Down Expand Up @@ -362,6 +498,9 @@ func originalFromSerialHandler(w http.ResponseWriter, req *http.Request) {
func main() {
var err error

var listenCertPem []byte
var listenCertPemString string

config := easyconfig.Configurator{
ProgramName: "certdehydrate_dane_rest_api",
}
Expand All @@ -370,17 +509,131 @@ func main() {
log.Fatalf("Couldn't parse configuration: %s", err)
}

rootCert, rootPriv, err = safetlsa.GenerateRootCA("Namecoin")
if err != nil {
log.Fatalf("Couldn't generate root CA: %s", err)
if generateCerts.Value() {
rootCert, rootPriv, err = safetlsa.GenerateRootCA("Namecoin")
if err != nil {
log.Fatalf("Couldn't generate root CA: %s", err)
}

rootPrivBytes, err := x509.MarshalPKCS8PrivateKey(rootPriv)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}

rootCertPem = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rootCert,
})
rootCertPemString = string(rootCertPem)

rootPrivPem = pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: rootPrivBytes,
})

tldCert, tldPriv, err = safetlsa.GenerateTLDCA("bit", rootCert, rootPriv)
if err != nil {
log.Fatalf("Couldn't generate TLD CA: %s", err)
}

tldCertPem = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: tldCert,
})
tldCertPemString = string(tldCertPem)

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)

listenPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatalf("Unable to generate listening key: %s", err)
}

listenPrivBytes, err := x509.MarshalPKCS8PrivateKey(listenPriv)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}

listenTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "aia.x--nmc.bit",
SerialNumber: "Namecoin TLS Certificate",
},
NotBefore: time.Now().Add(-1 * time.Hour),
NotAfter: time.Now().Add(43800 * time.Hour),

KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,

DNSNames: []string{"aia.x--nmc.bit"},
}

tldCertParsed, err := x509.ParseCertificate(tldCert)
if err != nil {
log.Fatalf("Unable to parse TLD cert: %s", err)
}

listenCert, err := x509.CreateCertificate(rand.Reader, &listenTemplate, tldCertParsed, &listenPriv.PublicKey, tldPriv)
if err != nil {
log.Fatalf("Unable to create listening cert: %s", err)
}

listenCertPem = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: listenCert,
})
listenCertPemString = string(listenCertPem)

listenPrivPem := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: listenPrivBytes,
})

ioutil.WriteFile("root_cert.pem", rootCertPem, 0600)
ioutil.WriteFile("root_key.pem", rootPrivPem, 0600)

listenChainPemString := listenCertPemString + "\n\n" + tldCertPemString + "\n\n" + rootCertPemString
listenChainPem := []byte(listenChainPemString)

ioutil.WriteFile("listen_chain.pem", listenChainPem, 0600)
ioutil.WriteFile("listen_key.pem", listenPrivPem, 0600)

return
}

rootCertPem = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rootCert,
})
rootCertPem, err = ioutil.ReadFile("root_cert.pem")
if err != nil {
log.Fatalf("Unable to read root_cert.pem: %s", err)
}
rootCertPemString = string(rootCertPem)

rootCertBlock, _ := pem.Decode(rootCertPem)
if rootCertBlock == nil {
log.Fatalf("Unable to decode root_cert.pem: %s", err)
}

rootCert = rootCertBlock.Bytes

rootPrivPem, err = ioutil.ReadFile("root_key.pem")
if err != nil {
log.Fatalf("Unable to read root_key.pem: %s", err)
}

rootPrivBlock, _ := pem.Decode(rootPrivPem)
if rootPrivBlock == nil {
log.Fatalf("Unable to decode root_key.pem: %s", err)
}

rootPrivBytes := rootPrivBlock.Bytes

rootPriv, err = x509.ParsePKCS8PrivateKey(rootPrivBytes)
if err != nil {
log.Fatalf("Unable to parse root_key.pem: %s", err)
}

tldCert, tldPriv, err = safetlsa.GenerateTLDCA("bit", rootCert, rootPriv)
if err != nil {
log.Fatalf("Couldn't generate TLD CA: %s", err)
Expand All @@ -400,8 +653,14 @@ func main() {
originalCertCache = map[string][]cachedCert{}

http.HandleFunc("/lookup", lookupHandler)
http.HandleFunc("/aia", aiaHandler)
http.HandleFunc("/get-new-negative-ca", getNewNegativeCAHandler)
http.HandleFunc("/cross-sign-ca", crossSignCAHandler)
http.HandleFunc("/original-from-serial", originalFromSerialHandler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
if listenHTTPS.Value() {
log.Fatal(http.ListenAndServeTLS(listenIP.Value() + ":443",
"listen_chain.pem", "listen_key.pem", nil))
} else {
log.Fatal(http.ListenAndServe(listenIP.Value() + ":80", nil))
}
}