diff --git a/acme/crypto.go b/acme/crypto.go index a7110b0f..fc0ff288 100644 --- a/acme/crypto.go +++ b/acme/crypto.go @@ -6,6 +6,8 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "math/big" + "time" ) func generatePrivateKey(keyLength int) (*rsa.PrivateKey, error) { @@ -31,3 +33,49 @@ func pemEncode(data interface{}) []byte { return pem.EncodeToMemory(pemBlock) } + +func GetCertExpiration(cert []byte) (time.Time, error) { + pCert, err := x509.ParseCertificate(cert) + if err != nil { + return time.Time{}, err + } + + return pCert.NotAfter, nil +} + +func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) { + derBytes, err := generateDerCert(privKey, time.Time{}, domain) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil +} + +func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + zero := time.Time{} + if expiration == zero { + expiration = time.Now().Add(365) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "ACME Challenge TEMP", + }, + NotBefore: time.Now(), + NotAfter: expiration, + + KeyUsage: x509.KeyUsageKeyEncipherment, + BasicConstraintsValid: true, + DNSNames: []string{domain}, + } + + return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) +} diff --git a/acme/crypto_test.go b/acme/crypto_test.go index 49c72e0c..b1e8713f 100644 --- a/acme/crypto_test.go +++ b/acme/crypto_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rsa" "testing" + "time" ) func TestGeneratePrivateKey(t *testing.T) { @@ -50,6 +51,30 @@ func TestPEMEncode(t *testing.T) { } } +func TestCertExpiration(t *testing.T) { + privKey, err := generatePrivateKey(2048) + if err != nil { + t.Fatal("Error generating private key:", err) + } + + expiration := time.Now().Add(365) + expiration = expiration.Round(time.Second) + certBytes, err := generateDerCert(privKey, expiration, "test.com") + if err != nil { + t.Fatal("Error generating cert:", err) + } + + buf := bytes.NewBufferString("TestingRSAIsSoMuchFun") + + if ctime, err := GetCertExpiration(buf.Bytes()); err == nil { + t.Errorf("Expected getCertExpiration to return an error for garbage string but returned %v", ctime) + } + + if ctime, err := GetCertExpiration(certBytes); err != nil || ctime != expiration.UTC() { + t.Errorf("Expected getCertExpiration to return %v but returned: %v, err: %v", expiration.UTC(), ctime, err) + } +} + type MockRandReader struct { b *bytes.Buffer } diff --git a/acme/simple_http_challenge.go b/acme/simple_http_challenge.go index 099d5e78..9cb4ffdd 100644 --- a/acme/simple_http_challenge.go +++ b/acme/simple_http_challenge.go @@ -2,16 +2,13 @@ package acme import ( "crypto/rand" - "crypto/rsa" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" "encoding/json" "encoding/pem" "errors" "fmt" "io/ioutil" - "math/big" "net" "net/http" "strings" @@ -122,7 +119,7 @@ func (s *simpleHTTPChallenge) startHTTPSServer(domain string, token string) (net if err != nil { return nil, err } - tempCertPEM, err := generateCert(tempPrivKey, domain) + tempCertPEM, err := generatePemCert(tempPrivKey, domain) if err != nil { return nil, err } @@ -190,31 +187,3 @@ func getRandomString(length int) string { } return string(bytes) } - -func generateCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) { - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, err - } - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: "ACME Challenge TEMP", - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(365), - - KeyUsage: x509.KeyUsageKeyEncipherment, - BasicConstraintsValid: true, - DNSNames: []string{domain}, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey) - if err != nil { - return nil, err - } - - return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil -}