@ -4,14 +4,19 @@ import (
"bytes"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
"github.com/BurntSushi/toml"
@ -29,10 +34,13 @@ type requestData struct {
}
type responseData struct {
Certificate string ` json:"certificate" `
Certificate string ` json:"certificate" `
CAChain [ ] string ` json:"ca_chain" `
}
func ( h * signCertificate ) sign ( csrPem string , commonName string ) ( certPem string , err error ) {
var caCertificates [ ] * x509 . Certificate
func ( h * signCertificate ) sign ( csrPem string , commonName string ) ( certPem string , caChain [ ] string , err error ) {
log . Printf ( "received CSR for %s:\n\n%s" , commonName , csrPem )
subjectDN := fmt . Sprintf ( "/CN=%s" , commonName )
var csrFile * os . File
@ -68,7 +76,23 @@ func (h *signCertificate) sign(csrPem string, commonName string) (certPem string
log . Print ( cmdErr . String ( ) )
return
}
certPem = out . String ( )
var block * pem . Block
if block , _ = pem . Decode ( out . Bytes ( ) ) ; block == nil {
err = fmt . Errorf ( "could not decode pem" )
return
}
var certificate * x509 . Certificate
if certificate , err = x509 . ParseCertificate ( block . Bytes ) ; err != nil {
return
}
certPem = string ( pem . EncodeToMemory ( & pem . Block {
Type : "CERTIFICATE" ,
Bytes : certificate . Raw ,
} ) )
caChain , err = h . getCAChain ( certificate )
return
}
@ -83,7 +107,8 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
var err error
var requestBody requestData
var certificate string
var responseData responseData
if err = json . NewDecoder ( r . Body ) . Decode ( & requestBody ) ; err != nil {
log . Print ( err )
@ -91,14 +116,14 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
certificate , err = h . sign ( requestBody . Csr , requestBody . CommonName )
responseData. Certificate , responseData . CAChain , err = h . sign ( requestBody . Csr , requestBody . CommonName )
if err != nil {
http . Error ( w , "Could not sign certificate" , http . StatusInternalServerError )
return
}
var jsonBytes [ ] byte
if jsonBytes , err = json . Marshal ( & responseData {Certificate : certificate } ); err != nil {
if jsonBytes , err = json . Marshal ( & responseData ); err != nil {
log . Print ( err )
}
@ -107,6 +132,39 @@ func (h *signCertificate) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func ( * signCertificate ) getCAChain ( certificate * x509 . Certificate ) ( [ ] string , error ) {
result := make ( [ ] string , 0 )
appendCert := func ( cert * x509 . Certificate ) {
result = append (
result ,
string ( pem . EncodeToMemory ( & pem . Block { Bytes : cert . Raw , Type : "CERTIFICATE" } ) ) )
log . Debugf ( "added %s to cachain" , result [ len ( result ) - 1 ] )
}
var previous * x509 . Certificate
for {
if len ( caCertificates ) == 0 {
return result , nil
}
for _ , caCert := range caCertificates {
if previous == nil {
if bytes . Equal ( caCert . RawSubject , certificate . RawIssuer ) {
previous = caCert
appendCert ( caCert )
}
} else if bytes . Equal ( previous . RawSubject , previous . RawIssuer ) {
return result , nil
} else if bytes . Equal ( caCert . RawSubject , previous . RawIssuer ) {
previous = caCert
appendCert ( caCert )
} else {
log . Debugf ( "skipped certificate %s" , caCert . Subject )
}
}
}
}
type indexHandler struct {
Bundle * i18n . Bundle
}
@ -158,26 +216,33 @@ func (i *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ID : "StatusLoading" ,
Other : "Loading ..." ,
} } )
sendCSRButtonLabel := localizer . MustLocalize ( & i18n . LocalizeConfig { DefaultMessage : & i18n . Message {
ID : "SendCSRButtonLabel" ,
Other : "Send signing request" ,
downloadLabel := localizer . MustLocalize ( & i18n . LocalizeConfig { DefaultMessage : & i18n . Message {
ID : "DownloadLabel" ,
Other : "Download" ,
} } )
downloadDescription := localizer . MustLocalize ( & i18n . LocalizeConfig { DefaultMessage : & i18n . Message {
ID : "DownloadDescription" ,
Other : "Your key material is ready for download. The downloadable file contains your private key and your" +
" certificate encrypted with your password. You can now use the file to install your certificate in your" +
" browser or other applications." ,
} } )
t := template . Must ( template . ParseFiles ( "templates/index.html" ) )
err := t . Execute ( w , map [ string ] interface { } {
"Title" : csrGenTitle ,
"NameLabel" : nameLabel ,
"NameHelpText" : nameHelpText ,
"PasswordLabel" : passwordLabel ,
"RSAKeySizeLegend" : rsaKeySizeLegend ,
"RSA3072Label" : rsa3072Label ,
"RSA2048Label" : rsa2048Label ,
"RSA4096Label" : rsa4096Label ,
"RSAHelpText" : rsaHelpText ,
"CSRButtonLabel" : csrButtonLabel ,
"StatusLoading" : statusLoading ,
"SendCSRButtonLabel" : sendCSRButtonLabel ,
csrf . TemplateTag : csrf . TemplateField ( r ) ,
"Title" : csrGenTitle ,
"NameLabel" : nameLabel ,
"NameHelpText" : nameHelpText ,
"PasswordLabel" : passwordLabel ,
"RSAKeySizeLegend" : rsaKeySizeLegend ,
"RSA3072Label" : rsa3072Label ,
"RSA2048Label" : rsa2048Label ,
"RSA4096Label" : rsa4096Label ,
"RSAHelpText" : rsaHelpText ,
"CSRButtonLabel" : csrButtonLabel ,
"StatusLoading" : statusLoading ,
"DownloadDescription" : downloadDescription ,
"DownloadLabel" : downloadLabel ,
csrf . TemplateTag : csrf . TemplateField ( r ) ,
} )
if err != nil {
log . Panic ( err )
@ -238,6 +303,26 @@ func generateRandomBytes(count int) []byte {
return randomBytes
}
func init ( ) {
var err error
caCertificates = make ( [ ] * x509 . Certificate , 2 )
for index , certFile := range [ ] string { "example_ca/sub/ca.crt.pem" , "example_ca/root/ca.crt.pem" } {
var certBytes [ ] byte
if certBytes , err = ioutil . ReadFile ( certFile ) ; err != nil {
log . Panic ( err )
}
var block * pem . Block
if block , _ = pem . Decode ( certBytes ) ; block == nil {
log . Panicf ( "no PEM data found in %s" , certFile )
return
}
if caCertificates [ index ] , err = x509 . ParseCertificate ( block . Bytes ) ; err != nil {
log . Panic ( err )
}
}
log . Infof ( "read %d CA certificates" , len ( caCertificates ) )
}
func main ( ) {
tlsConfig := & tls . Config {
CipherSuites : [ ] uint16 {
@ -258,7 +343,21 @@ func main() {
}
mux := http . NewServeMux ( )
csrfKey := generateRandomBytes ( 32 )
var csrfKey [ ] byte = nil
if csrfB64 , exists := os . LookupEnv ( "CSRF_KEY" ) ; exists {
csrfKey , _ = base64 . RawStdEncoding . DecodeString ( csrfB64 )
log . Info ( "read CSRF key from environment variable" )
}
if csrfKey == nil {
csrfKey = generateRandomBytes ( 32 )
log . Infof (
"generated new random CSRF key, set environment variable CSRF_KEY to %s to " +
"keep the same key for new sessions" ,
base64 . RawStdEncoding . EncodeToString ( csrfKey ) )
}
mux . Handle ( "/sign/" , & signCertificate { } )
mux . Handle ( "/" , & indexHandler { Bundle : bundle } )
fileServer := http . FileServer ( http . Dir ( "./public" ) )
@ -274,8 +373,22 @@ func main() {
WriteTimeout : 30 * time . Second ,
IdleTimeout : 30 * time . Second ,
}
err := server . ListenAndServeTLS ( "server.crt.pem" , "server.key.pem" )
if err != nil {
log . Fatal ( err )
go func ( ) {
err := server . ListenAndServeTLS ( "server.crt.pem" , "server.key.pem" )
if err != nil {
log . Fatal ( err )
}
} ( )
var hostPort string
if strings . HasPrefix ( server . Addr , ":" ) {
hostPort = fmt . Sprintf ( "localhost%s" , server . Addr )
} else {
hostPort = server . Addr
}
log . Infof ( "started web server on https://%s/" , hostPort )
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt , syscall . SIGTERM )
s := <- c
log . Infof ( "received %s, shutting down" , s )
_ = server . Close ( )
}