2019-03-11 17:56:48 +01:00
|
|
|
package certcrypto
|
2018-12-06 22:50:17 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2022-02-20 14:23:52 +00:00
|
|
|
"encoding/pem"
|
2018-12-06 22:50:17 +01:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestGeneratePrivateKey(t *testing.T) {
|
|
|
|
key, err := GeneratePrivateKey(RSA2048)
|
|
|
|
require.NoError(t, err, "Error generating private key")
|
|
|
|
|
|
|
|
assert.NotNil(t, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGenerateCSR(t *testing.T) {
|
2025-02-18 20:10:57 +01:00
|
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
|
2018-12-06 22:50:17 +01:00
|
|
|
require.NoError(t, err, "Error generating private key")
|
|
|
|
|
|
|
|
type expected struct {
|
|
|
|
len int
|
|
|
|
error bool
|
|
|
|
}
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
desc string
|
|
|
|
privateKey crypto.PrivateKey
|
2025-02-06 19:00:45 +01:00
|
|
|
opts CSROptions
|
2018-12-06 22:50:17 +01:00
|
|
|
expected expected
|
|
|
|
}{
|
|
|
|
{
|
2024-02-09 21:55:43 +01:00
|
|
|
desc: "without SAN (nil)",
|
2018-12-06 22:50:17 +01:00
|
|
|
privateKey: privateKey,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "lego.acme",
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 379},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
{
|
2024-02-09 21:55:43 +01:00
|
|
|
desc: "without SAN (empty)",
|
2018-12-06 22:50:17 +01:00
|
|
|
privateKey: privateKey,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "lego.acme",
|
|
|
|
SAN: []string{},
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 379},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "with SAN",
|
|
|
|
privateKey: privateKey,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "lego.acme",
|
|
|
|
SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"},
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 430},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "no domain",
|
|
|
|
privateKey: privateKey,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "",
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 359},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "no domain with SAN",
|
|
|
|
privateKey: privateKey,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "",
|
|
|
|
SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"},
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 409},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "private key nil",
|
|
|
|
privateKey: nil,
|
2025-02-06 19:00:45 +01:00
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "fizz.buzz",
|
|
|
|
MustStaple: true,
|
|
|
|
},
|
|
|
|
expected: expected{error: true},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
desc: "with email addresses",
|
|
|
|
privateKey: privateKey,
|
|
|
|
opts: CSROptions{
|
|
|
|
Domain: "example.com",
|
|
|
|
SAN: []string{"example.org"},
|
|
|
|
EmailAddresses: []string{"foo@example.com", "bar@example.com"},
|
|
|
|
},
|
2025-02-18 20:10:57 +01:00
|
|
|
expected: expected{len: 421},
|
2018-12-06 22:50:17 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range testCases {
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2025-02-06 19:00:45 +01:00
|
|
|
csr, err := CreateCSR(test.privateKey, test.opts)
|
2018-12-06 22:50:17 +01:00
|
|
|
|
|
|
|
if test.expected.error {
|
|
|
|
require.Error(t, err)
|
|
|
|
} else {
|
|
|
|
require.NoError(t, err, "Error generating CSR")
|
|
|
|
|
|
|
|
assert.NotEmpty(t, csr)
|
|
|
|
assert.Len(t, csr, test.expected.len)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPEMEncode(t *testing.T) {
|
2025-02-18 20:10:57 +01:00
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
2018-12-06 22:50:17 +01:00
|
|
|
require.NoError(t, err, "Error generating private key")
|
|
|
|
|
|
|
|
data := PEMEncode(key)
|
|
|
|
require.NotNil(t, data)
|
2022-08-22 17:05:31 +02:00
|
|
|
|
2025-02-18 20:10:57 +01:00
|
|
|
p, rest := pem.Decode(data)
|
|
|
|
|
|
|
|
assert.Equal(t, "RSA PRIVATE KEY", p.Type)
|
|
|
|
assert.Empty(t, rest)
|
|
|
|
assert.Empty(t, p.Headers)
|
2018-12-06 22:50:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestParsePEMCertificate(t *testing.T) {
|
|
|
|
privateKey, err := GeneratePrivateKey(RSA2048)
|
|
|
|
require.NoError(t, err, "Error generating private key")
|
|
|
|
|
|
|
|
expiration := time.Now().Add(365).Round(time.Second)
|
|
|
|
certBytes, err := generateDerCert(privateKey.(*rsa.PrivateKey), expiration, "test.com", nil)
|
|
|
|
require.NoError(t, err, "Error generating cert")
|
|
|
|
|
|
|
|
buf := bytes.NewBufferString("TestingRSAIsSoMuchFun")
|
|
|
|
|
|
|
|
// Some random string should return an error.
|
|
|
|
cert, err := ParsePEMCertificate(buf.Bytes())
|
|
|
|
require.Errorf(t, err, "returned %v", cert)
|
|
|
|
|
|
|
|
// A DER encoded certificate should return an error.
|
|
|
|
_, err = ParsePEMCertificate(certBytes)
|
|
|
|
require.Error(t, err, "Expected to return an error for DER certificates")
|
|
|
|
|
|
|
|
// A PEM encoded certificate should work ok.
|
|
|
|
pemCert := PEMEncode(DERCertificateBytes(certBytes))
|
|
|
|
cert, err = ParsePEMCertificate(pemCert)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, expiration.UTC(), cert.NotAfter)
|
|
|
|
}
|
|
|
|
|
2022-02-20 14:23:52 +00:00
|
|
|
func TestParsePEMPrivateKey(t *testing.T) {
|
|
|
|
privateKey, err := GeneratePrivateKey(RSA2048)
|
|
|
|
require.NoError(t, err, "Error generating private key")
|
|
|
|
|
|
|
|
pemPrivateKey := PEMEncode(privateKey)
|
|
|
|
|
2025-03-15 08:26:09 -04:00
|
|
|
// Decoding a key should work and create an identical RSA key to the original,
|
|
|
|
// ignoring precomputed values.
|
2022-02-20 14:23:52 +00:00
|
|
|
decoded, err := ParsePEMPrivateKey(pemPrivateKey)
|
|
|
|
require.NoError(t, err)
|
2025-03-15 08:26:09 -04:00
|
|
|
decodedRsaPrivateKey := decoded.(*rsa.PrivateKey)
|
|
|
|
require.True(t, decodedRsaPrivateKey.Equal(privateKey))
|
2022-02-20 14:23:52 +00:00
|
|
|
|
|
|
|
// Decoding a PEM block that doesn't contain a private key should error
|
|
|
|
_, err = ParsePEMPrivateKey(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE"}))
|
|
|
|
require.Errorf(t, err, "Expected to return an error for non-private key input")
|
|
|
|
|
|
|
|
// Decoding a PEM block that doesn't actually contain a key should error
|
|
|
|
_, err = ParsePEMPrivateKey(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY"}))
|
|
|
|
require.Errorf(t, err, "Expected to return an error for empty input")
|
|
|
|
|
|
|
|
// Decoding non-PEM input should return an error
|
|
|
|
_, err = ParsePEMPrivateKey([]byte("This is not PEM"))
|
|
|
|
require.Errorf(t, err, "Expected to return an error for non-PEM input")
|
|
|
|
}
|