diff --git a/README.md b/README.md index 6c122c5..5c8ec62 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,15 @@ It includes parsers for taking strings/config options and enabling minimum TLS v It also includes some decent static defaults which can be used when creating a server. ``` import ( + "github.com/snowzach/certtools/autocert" "github.com/snowzach/certtools" ) +// Generate a self-signed certificate for localhost using static string as private key data (repeatable) +// This programatically generates the same certificate every time and is only for development use +cert, err = autocert.New(autocert.InsecureStringReader("static string")) + +// Build an http server using our self-signed cert and decent security ciphers and versions server := &http.Server{ Addr: ":8443", Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/autocert.go b/autocert.go deleted file mode 100644 index f5538b3..0000000 --- a/autocert.go +++ /dev/null @@ -1,128 +0,0 @@ -package certtools - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io" - "math/big" - "time" -) - -// InsecureGlobalStatic is a non-random byte reader that can be used to generaate an insecure private key -// This will generate the same bytes on every box (all zeros). It is horribly insecure. -type InsecureGlobalStatic struct{} - -func InsecureGlobalStaticReader() InsecureGlobalStatic { - return InsecureGlobalStatic{} -} - -func (r InsecureGlobalStatic) Read(s []byte) (int, error) { - // Set it to all zeros - l := len(s) - for x := 0; x < l; x++ { - s[x] = 0 - } - return l, nil -} - -// InsecureString is a non-random bytes reader that can be used to generate an insecure private key based on a provided string -// The upside of this is that the same string input should yield the same bytes so you can send in something like the hostname -// and it will generate the same output everytime you run your program. -// The downside is that it is very insecure and should only be used for testing -type InsecureString struct { - seed []byte - pos int - length int -} - -func InsecureStringReader(seed string) *InsecureString { - // Ensure there is at least one character in seed - if len(seed) == 0 { - seed = " " - } - return &InsecureString{ - seed: []byte(seed), - pos: 0, - length: len(seed), - } -} -func (r *InsecureString) Read(s []byte) (int, error) { - // Just repead the string over and over - l := len(s) - for x := 0; x < l; x++ { - s[x] = r.seed[r.pos%r.length] - r.pos++ - } - return l, nil -} - -// AutoCert generates a self-signed cert using the specified private key mechanism -func AutoCert(commonName string, orgName string, orgUnitName string, dnsNames []string, notBefore time.Time, notAfter time.Time, keyReader io.Reader) (tls.Certificate, error) { - - if commonName == "" { - return tls.Certificate{}, fmt.Errorf("commonName must not be blank") - } - - // Generate the key - privKey, err := ecdsa.GenerateKey(elliptic.P256(), keyReader) - if err != nil { - return tls.Certificate{}, fmt.Errorf("Could not generate private key: %v\n", err) - } - - // Build Cert - cert := x509.Certificate{ - SerialNumber: big.NewInt(0), - Subject: pkix.Name{ - CommonName: commonName, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - IsCA: true, - - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - - BasicConstraintsValid: true, - } - - // If dnsNames is nil, use common name - if dnsNames == nil { - cert.DNSNames = []string{commonName} - } else { - cert.DNSNames = dnsNames - } - - if orgName != "" { - cert.Subject.Organization = []string{orgName} - } - if orgUnitName != "" { - cert.Subject.OrganizationalUnit = []string{orgUnitName} - } - - // Create Cert - derBytes, err := x509.CreateCertificate(keyReader, &cert, &cert, &privKey.PublicKey, privKey) - if err != nil { - return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %s", err) - } - - pKeyBytes, err := x509.MarshalECPrivateKey(privKey) - if err != nil { - return tls.Certificate{}, fmt.Errorf("Failed to marshal private key: %s", err) - } - - certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - privBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: pKeyBytes}) - - tlsCert, err := tls.X509KeyPair(certBytes, privBytes) - if err != nil { - return tls.Certificate{}, fmt.Errorf("Failed to load key-pair: %s", err) - } - - return tlsCert, nil -} diff --git a/autocert/autocert.go b/autocert/autocert.go new file mode 100644 index 0000000..87be115 --- /dev/null +++ b/autocert/autocert.go @@ -0,0 +1,132 @@ +package autocert + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "net/url" + "time" +) + +type AutoCertOption func(*x509.Certificate) + +// AutoCert generates a self-signed cert using the specified keyReader for a source for private key generation +func New(keyReader io.Reader, opts ...AutoCertOption) (tls.Certificate, error) { + + // Generate the key + privKey, err := ecdsa.GenerateKey(elliptic.P256(), keyReader) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Could not generate private key: %v\n", err) + } + + // Build Cert + cert := x509.Certificate{ + SerialNumber: big.NewInt(0), + Subject: pkix.Name{ + CommonName: "localhost", + }, + + // Starting jan 1, 2010 for 100 years + NotBefore: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC).Add(100 * 365 * 24 * time.Hour), + + IsCA: true, + + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + + BasicConstraintsValid: true, + } + + // Apply the options + for _, f := range opts { + f(&cert) + } + + // Create Cert + derBytes, err := x509.CreateCertificate(keyReader, &cert, &cert, &privKey.PublicKey, privKey) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %s", err) + } + + pKeyBytes, err := x509.MarshalECPrivateKey(privKey) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to marshal private key: %s", err) + } + + certBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + privBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: pKeyBytes}) + + tlsCert, err := tls.X509KeyPair(certBytes, privBytes) + if err != nil { + return tls.Certificate{}, fmt.Errorf("Failed to load key-pair: %s", err) + } + + return tlsCert, nil +} + +// SerialNummber sets the serial number for the cert +func SerialNumber(sn int64) AutoCertOption { + return func(c *x509.Certificate) { + c.SerialNumber = big.NewInt(sn) + } +} + +// CommonName sets the commont name of the certificate +func CommonName(cn string) AutoCertOption { + return func(c *x509.Certificate) { + c.Subject = pkix.Name{ + CommonName: cn, + } + } +} + +// Organization sets the Organization(s) of the cert subject +func Organization(o []string) AutoCertOption { + return func(c *x509.Certificate) { + c.Subject.Organization = o + } +} + +// OrganizationalUnit sets the OrganizationalUnit(s) of the cert subject +func OrganizationalUnit(ou []string) AutoCertOption { + return func(c *x509.Certificate) { + c.Subject.OrganizationalUnit = ou + } +} + +// DNSNames sets the DNS names of the cert +func DNSNames(dnsNames []string) AutoCertOption { + return func(c *x509.Certificate) { + c.DNSNames = dnsNames + } +} + +// URIs sets the URIs of the cert +func URIs(uris []*url.URL) AutoCertOption { + return func(c *x509.Certificate) { + c.URIs = uris + } +} + +// IPAddresses sets the IPAddresses of the cert +func IPAddresses(ips []net.IP) AutoCertOption { + return func(c *x509.Certificate) { + c.IPAddresses = ips + } +} + +// ValidTimes sets the times in which this cert is valid +func ValidTimes(notBefore time.Time, notAfter time.Time) AutoCertOption { + return func(c *x509.Certificate) { + c.NotBefore = notBefore + c.NotAfter = notAfter + } +} diff --git a/autocert/certreaders.go b/autocert/certreaders.go new file mode 100644 index 0000000..b311039 --- /dev/null +++ b/autocert/certreaders.go @@ -0,0 +1,49 @@ +package autocert + +// InsecureGlobalStatic is a non-random byte reader that can be used to generaate an insecure private key +// This will generate the same bytes on every box (all zeros). It is horribly insecure. +type InsecureGlobalStatic struct{} + +func InsecureGlobalStaticReader() InsecureGlobalStatic { + return InsecureGlobalStatic{} +} + +func (r InsecureGlobalStatic) Read(s []byte) (int, error) { + // Set it to all zeros + l := len(s) + for x := 0; x < l; x++ { + s[x] = 0 + } + return l, nil +} + +// InsecureString is a non-random bytes reader that can be used to generate an insecure private key based on a provided string +// The upside of this is that the same string input should yield the same bytes so you can send in something like the hostname +// and it will generate the same output everytime you run your program. +// The downside is that it is very insecure and should only be used for testing +type InsecureString struct { + seed []byte + pos int + length int +} + +func InsecureStringReader(seed string) *InsecureString { + // Ensure there is at least one character in seed + if len(seed) == 0 { + seed = " " + } + return &InsecureString{ + seed: []byte(seed), + pos: 0, + length: len(seed), + } +} +func (r *InsecureString) Read(s []byte) (int, error) { + // Just repead the string over and over + l := len(s) + for x := 0; x < l; x++ { + s[x] = r.seed[r.pos%r.length] + r.pos++ + } + return l, nil +} diff --git a/example/example.go b/example/example.go index 05ed4d3..a5e7d82 100644 --- a/example/example.go +++ b/example/example.go @@ -6,10 +6,12 @@ import ( "fmt" "net" "net/http" + "net/url" "os" "time" "github.com/snowzach/certtools" + "github.com/snowzach/certtools/autocert" ) func main() { @@ -24,13 +26,24 @@ func main() { cn := flag.String("cn", hostname, "The common name for the certificate") o := flag.String("o", "", "The org for the certificate") ou := flag.String("ou", "", "The org unit for the certificate") + flag.Parse() + + uri, _ := url.Parse("https://" + hostname) // Good starting at unix epoch for 100 years - var notBefore time.Time + var notBefore = time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC) var notAfter time.Time = notBefore.Add(100 * 365 * 24 * time.Hour) // This will generate the same certificate every time it is run on the same host - cert, err := certtools.AutoCert(*cn, *o, *ou, nil, notBefore, notAfter, certtools.InsecureStringReader(hostname)) + cert, err := autocert.New(autocert.InsecureStringReader(hostname), + autocert.SerialNumber(1), + autocert.CommonName(*cn), + autocert.Organization([]string{*o}), + autocert.OrganizationalUnit([]string{*ou}), + autocert.URIs([]*url.URL{uri}), + autocert.DNSNames([]string{hostname}), + autocert.ValidTimes(notBefore, notAfter), + ) // Build the server and manually specify TLS Config server := &http.Server{