Add some SCEP integration tests
parent
113a6dd8ab
commit
b0fabe1346
@ -0,0 +1,242 @@
|
||||
package sceptest
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/smallstep/pkcs7"
|
||||
"github.com/smallstep/scep"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/x509util"
|
||||
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
)
|
||||
|
||||
// reservePort "reserves" a TCP port by opening a listener on a random
|
||||
// port and immediately closing it. The port can then be assumed to be
|
||||
// available for running a server on.
|
||||
func reservePort(t *testing.T) (host, port string) {
|
||||
t.Helper()
|
||||
l, err := net.Listen("tcp", ":0")
|
||||
require.NoError(t, err)
|
||||
|
||||
address := l.Addr().String()
|
||||
err = l.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
host, port, err = net.SplitHostPort(address)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type client struct {
|
||||
caURL string
|
||||
caCert *x509.Certificate
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func createSCEPClient(t *testing.T, caURL string) (*client, error) {
|
||||
t.Helper()
|
||||
return &client{
|
||||
caURL: caURL,
|
||||
httpClient: http.DefaultClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) getCACert(t *testing.T) error {
|
||||
// return early if CA certificate already available
|
||||
if c.caCert != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Get(fmt.Sprintf("%s?operation=GetCACert&message=test", c.caURL))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading response body: %w", err)
|
||||
}
|
||||
|
||||
t.Log(string(body))
|
||||
|
||||
// SCEP CA/RA certificate selection. If there's only a single certificate, it will
|
||||
// be used as the CA certificate at all times. If there's multiple, the first certificate
|
||||
// is assumed to be the certificate of the recipient to encrypt messages to.
|
||||
switch ct := resp.Header.Get("Content-Type"); ct {
|
||||
case "application/x-x509-ca-cert":
|
||||
cert, err := x509.ParseCertificate(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing response body: %w", err)
|
||||
}
|
||||
if _, ok := cert.PublicKey.(*rsa.PublicKey); !ok {
|
||||
return fmt.Errorf("certificate has unexpected public key type %T", cert.PublicKey)
|
||||
}
|
||||
c.caCert = cert
|
||||
case "application/x-x509-ca-ra-cert":
|
||||
certs, err := scep.CACerts(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parsing response body: %w", err)
|
||||
}
|
||||
cert := certs[0]
|
||||
if _, ok := cert.PublicKey.(*rsa.PublicKey); !ok {
|
||||
return fmt.Errorf("certificate has unexpected public key type %T", cert.PublicKey)
|
||||
}
|
||||
c.caCert = cert
|
||||
default:
|
||||
return fmt.Errorf("unexpected content-type value %q", ct)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) requestCertificate(t *testing.T, commonName string, sans []string) (*x509.Certificate, error) {
|
||||
if err := c.getCACert(t); err != nil {
|
||||
return nil, fmt.Errorf("failed getting CA certificate: %w", err)
|
||||
}
|
||||
|
||||
signer, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating SCEP private key: %w", err)
|
||||
}
|
||||
|
||||
csr, err := x509util.CreateCertificateRequest(commonName, sans, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating CSR: %w", err)
|
||||
}
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
Subject: csr.Subject,
|
||||
PublicKey: signer.Public(),
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(1 * time.Hour),
|
||||
DNSNames: csr.DNSNames,
|
||||
IPAddresses: csr.IPAddresses,
|
||||
EmailAddresses: csr.EmailAddresses,
|
||||
URIs: csr.URIs,
|
||||
}
|
||||
|
||||
selfSigned, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating self signed certificate: %w", err)
|
||||
}
|
||||
selfSignedCertificate, err := x509.ParseCertificate(selfSigned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing self signed certificate: %w", err)
|
||||
}
|
||||
|
||||
msgTmpl := &scep.PKIMessage{
|
||||
TransactionID: "test-1",
|
||||
MessageType: scep.PKCSReq,
|
||||
SenderNonce: []byte("test-nonce-1"),
|
||||
Recipients: []*x509.Certificate{c.caCert},
|
||||
SignerCert: selfSignedCertificate,
|
||||
SignerKey: signer,
|
||||
}
|
||||
|
||||
msg, err := scep.NewCSRRequest(csr, msgTmpl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed creating SCEP PKCSReq message: %w", err)
|
||||
}
|
||||
|
||||
t.Log(string(msg.Raw))
|
||||
|
||||
u, err := url.Parse(c.caURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing CA URL: %w", err)
|
||||
}
|
||||
|
||||
opURL := u.ResolveReference(&url.URL{RawQuery: fmt.Sprintf("operation=PKIOperation&message=%s", url.QueryEscape(base64.StdEncoding.EncodeToString(msg.Raw)))})
|
||||
resp, err := c.httpClient.Get(opURL.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed get request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if ct := resp.Header.Get("Content-Type"); ct != "application/x-pki-message" {
|
||||
return nil, fmt.Errorf("received unexpected content type %q", ct)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading response body: %w", err)
|
||||
}
|
||||
|
||||
t.Log(string(body))
|
||||
|
||||
signedData, err := pkcs7.Parse(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing response body: %w", err)
|
||||
}
|
||||
|
||||
// TODO: verify the signature?
|
||||
|
||||
p7, err := pkcs7.Parse(signedData.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed decrypting inner p7: %w", err)
|
||||
}
|
||||
|
||||
content, err := p7.Decrypt(selfSignedCertificate, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed decrypting response: %w", err)
|
||||
}
|
||||
|
||||
p7, err = pkcs7.Parse(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing p7 content: %w", err)
|
||||
}
|
||||
|
||||
cert := p7.Certificates[0]
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
type testCAS struct {
|
||||
ca *minica.CA
|
||||
}
|
||||
|
||||
func (c *testCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
|
||||
cert, err := c.ca.SignCSR(req.CSR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed signing CSR: %w", err)
|
||||
}
|
||||
|
||||
return &apiv1.CreateCertificateResponse{
|
||||
Certificate: cert,
|
||||
CertificateChain: []*x509.Certificate{cert, c.ca.Intermediate},
|
||||
}, nil
|
||||
}
|
||||
func (c *testCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (c *testCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (c *testCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
|
||||
return &apiv1.GetCertificateAuthorityResponse{
|
||||
RootCertificate: c.ca.Root,
|
||||
IntermediateCertificates: []*x509.Certificate{c.ca.Intermediate},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ apiv1.CertificateAuthorityService = (*testCAS)(nil)
|
||||
var _ apiv1.CertificateAuthorityGetter = (*testCAS)(nil)
|
@ -0,0 +1,164 @@
|
||||
package sceptest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
)
|
||||
|
||||
func TestIssuesCertificateUsingSCEPWithDecrypterAndUpstreamCAS(t *testing.T) {
|
||||
signer, err := keyutil.GenerateSigner("EC", "P-256", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
m, err := minica.New(minica.WithName("Step E2E | SCEP Decrypter w/ Upstream CAS"), minica.WithGetSignerFunc(func() (crypto.Signer, error) {
|
||||
return signer, nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
rootFilepath := filepath.Join(dir, "root.crt")
|
||||
_, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateCertFilepath := filepath.Join(dir, "intermediate.crt")
|
||||
_, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateKeyFilepath := filepath.Join(dir, "intermediate.key")
|
||||
_, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypterKey, err := keyutil.GenerateKey("RSA", "", 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypter, ok := decrypterKey.(crypto.Decrypter)
|
||||
require.True(t, ok)
|
||||
|
||||
decrypterCertifiate, err := m.Sign(&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "decrypter"},
|
||||
PublicKey: decrypter.Public(),
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(1 * time.Hour),
|
||||
DNSNames: []string{"decrypter"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err := pemutil.Serialize(decrypterCertifiate)
|
||||
require.NoError(t, err)
|
||||
decrypterCertificatePEMBytes := pem.EncodeToMemory(b)
|
||||
|
||||
b, err = pemutil.Serialize(decrypter, pemutil.WithPassword([]byte("1234")))
|
||||
require.NoError(t, err)
|
||||
decrypterKeyPEMBytes := pem.EncodeToMemory(b)
|
||||
|
||||
// get a random address to listen on and connect to; currently no nicer way to get one before starting the server
|
||||
// TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it?
|
||||
host, port := reservePort(t)
|
||||
insecureHost, insecurePort := reservePort(t)
|
||||
|
||||
prov := &provisioner.SCEP{
|
||||
ID: "scep",
|
||||
Name: "scep",
|
||||
Type: "SCEP",
|
||||
ForceCN: false,
|
||||
ChallengePassword: "",
|
||||
EncryptionAlgorithmIdentifier: 2,
|
||||
MinimumPublicKeyLength: 2048,
|
||||
Claims: &config.GlobalProvisionerClaims,
|
||||
DecrypterCertificate: decrypterCertificatePEMBytes,
|
||||
DecrypterKeyPEM: decrypterKeyPEMBytes,
|
||||
DecrypterKeyPassword: "1234",
|
||||
}
|
||||
|
||||
err = prov.Init(provisioner.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
apiv1.Register("test-scep-cas", func(_ context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) {
|
||||
return &testCAS{
|
||||
ca: m,
|
||||
}, nil
|
||||
})
|
||||
|
||||
cfg := &config.Config{
|
||||
Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved"
|
||||
InsecureAddress: net.JoinHostPort(insecureHost, insecurePort), // reuse the address that was just "reserved"
|
||||
DNSNames: []string{"127.0.0.1", "[::1]", "localhost"},
|
||||
AuthorityConfig: &config.AuthConfig{
|
||||
Options: &apiv1.Options{
|
||||
AuthorityID: "stepca-test-scep",
|
||||
Type: "test-scep-cas",
|
||||
CertificateAuthority: "test-cas",
|
||||
},
|
||||
AuthorityID: "stepca-test-scep",
|
||||
DeploymentType: "standalone-test",
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
Logger: json.RawMessage(`{"format": "text"}`),
|
||||
}
|
||||
c, err := ca.New(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// instantiate a client for the CA running at the random address
|
||||
caClient, err := ca.NewClient(
|
||||
fmt.Sprintf("https://localhost:%s", port),
|
||||
ca.WithRootFile(rootFilepath),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = c.Run()
|
||||
require.ErrorIs(t, err, http.ErrServerClosed)
|
||||
}()
|
||||
|
||||
// require OK health response as the baseline
|
||||
ctx := context.Background()
|
||||
healthResponse, err := caClient.HealthWithContext(ctx)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, healthResponse) {
|
||||
require.Equal(t, "ok", healthResponse.Status)
|
||||
}
|
||||
|
||||
scepClient, err := createSCEPClient(t, fmt.Sprintf("http://localhost:%s/scep/scep", insecurePort))
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"})
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
assert.Equal(t, "test.localhost", cert.Subject.CommonName)
|
||||
assert.Equal(t, "Step E2E | SCEP Decrypter w/ Upstream CAS Intermediate CA", cert.Issuer.CommonName)
|
||||
|
||||
// done testing; stop and wait for the server to quit
|
||||
err = c.Stop()
|
||||
require.NoError(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package sceptest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
)
|
||||
|
||||
func TestIssuesCertificateUsingSCEPWithDecrypter(t *testing.T) {
|
||||
signer, err := keyutil.GenerateSigner("EC", "P-256", 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
m, err := minica.New(minica.WithName("Step E2E | SCEP Decrypter"), minica.WithGetSignerFunc(func() (crypto.Signer, error) {
|
||||
return signer, nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
rootFilepath := filepath.Join(dir, "root.crt")
|
||||
_, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateCertFilepath := filepath.Join(dir, "intermediate.crt")
|
||||
_, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateKeyFilepath := filepath.Join(dir, "intermediate.key")
|
||||
_, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypterKey, err := keyutil.GenerateKey("RSA", "", 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
decrypter, ok := decrypterKey.(crypto.Decrypter)
|
||||
require.True(t, ok)
|
||||
|
||||
decrypterCertifiate, err := m.Sign(&x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "decrypter"},
|
||||
PublicKey: decrypter.Public(),
|
||||
SerialNumber: big.NewInt(1),
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(1 * time.Hour),
|
||||
DNSNames: []string{"decrypter"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err := pemutil.Serialize(decrypterCertifiate)
|
||||
require.NoError(t, err)
|
||||
decrypterCertificatePEMBytes := pem.EncodeToMemory(b)
|
||||
|
||||
b, err = pemutil.Serialize(decrypter, pemutil.WithPassword([]byte("1234")))
|
||||
require.NoError(t, err)
|
||||
decrypterKeyPEMBytes := pem.EncodeToMemory(b)
|
||||
|
||||
// get a random address to listen on and connect to; currently no nicer way to get one before starting the server
|
||||
// TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it?
|
||||
host, port := reservePort(t)
|
||||
insecureHost, insecurePort := reservePort(t)
|
||||
|
||||
prov := &provisioner.SCEP{
|
||||
ID: "scep",
|
||||
Name: "scep",
|
||||
Type: "SCEP",
|
||||
ForceCN: false,
|
||||
ChallengePassword: "",
|
||||
EncryptionAlgorithmIdentifier: 2,
|
||||
MinimumPublicKeyLength: 2048,
|
||||
Claims: &config.GlobalProvisionerClaims,
|
||||
DecrypterCertificate: decrypterCertificatePEMBytes,
|
||||
DecrypterKeyPEM: decrypterKeyPEMBytes,
|
||||
DecrypterKeyPassword: "1234",
|
||||
}
|
||||
|
||||
err = prov.Init(provisioner.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
Root: []string{rootFilepath},
|
||||
IntermediateCert: intermediateCertFilepath,
|
||||
IntermediateKey: intermediateKeyFilepath,
|
||||
Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved"
|
||||
InsecureAddress: net.JoinHostPort(insecureHost, insecurePort), // reuse the address that was just "reserved"
|
||||
DNSNames: []string{"127.0.0.1", "[::1]", "localhost"},
|
||||
AuthorityConfig: &config.AuthConfig{
|
||||
AuthorityID: "stepca-test-scep",
|
||||
DeploymentType: "standalone-test",
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
Logger: json.RawMessage(`{"format": "text"}`),
|
||||
}
|
||||
c, err := ca.New(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// instantiate a client for the CA running at the random address
|
||||
caClient, err := ca.NewClient(
|
||||
fmt.Sprintf("https://localhost:%s", port),
|
||||
ca.WithRootFile(rootFilepath),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = c.Run()
|
||||
require.ErrorIs(t, err, http.ErrServerClosed)
|
||||
}()
|
||||
|
||||
// require OK health response as the baseline
|
||||
ctx := context.Background()
|
||||
healthResponse, err := caClient.HealthWithContext(ctx)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, healthResponse) {
|
||||
require.Equal(t, "ok", healthResponse.Status)
|
||||
}
|
||||
|
||||
scepClient, err := createSCEPClient(t, fmt.Sprintf("http://localhost:%s/scep/scep", insecurePort))
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"})
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
assert.Equal(t, "test.localhost", cert.Subject.CommonName)
|
||||
assert.Equal(t, "Step E2E | SCEP Decrypter Intermediate CA", cert.Issuer.CommonName)
|
||||
|
||||
// done testing; stop and wait for the server to quit
|
||||
err = c.Stop()
|
||||
require.NoError(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package sceptest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
)
|
||||
|
||||
func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) {
|
||||
signer, err := keyutil.GenerateSigner("RSA", "", 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
m, err := minica.New(minica.WithName("Step E2E | SCEP Regular w/ Upstream CAS"), minica.WithGetSignerFunc(func() (crypto.Signer, error) {
|
||||
return signer, nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
rootFilepath := filepath.Join(dir, "root.crt")
|
||||
_, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateCertFilepath := filepath.Join(dir, "intermediate.crt")
|
||||
_, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateKeyFilepath := filepath.Join(dir, "intermediate.key")
|
||||
_, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
// get a random address to listen on and connect to; currently no nicer way to get one before starting the server
|
||||
// TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it?
|
||||
host, port := reservePort(t)
|
||||
insecureHost, insecurePort := reservePort(t)
|
||||
|
||||
prov := &provisioner.SCEP{
|
||||
ID: "scep",
|
||||
Name: "scep",
|
||||
Type: "SCEP",
|
||||
ForceCN: false,
|
||||
ChallengePassword: "",
|
||||
EncryptionAlgorithmIdentifier: 2,
|
||||
MinimumPublicKeyLength: 2048,
|
||||
Claims: &config.GlobalProvisionerClaims,
|
||||
}
|
||||
|
||||
err = prov.Init(provisioner.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
apiv1.Register("test-scep-cas", func(_ context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) {
|
||||
return &testCAS{
|
||||
ca: m,
|
||||
}, nil
|
||||
})
|
||||
|
||||
cfg := &config.Config{
|
||||
Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved"
|
||||
InsecureAddress: net.JoinHostPort(insecureHost, insecurePort), // reuse the address that was just "reserved"
|
||||
DNSNames: []string{"127.0.0.1", "[::1]", "localhost"},
|
||||
AuthorityConfig: &config.AuthConfig{
|
||||
Options: &apiv1.Options{
|
||||
AuthorityID: "stepca-test-scep",
|
||||
Type: "test-scep-cas",
|
||||
CertificateAuthority: "test-cas",
|
||||
},
|
||||
AuthorityID: "stepca-test-scep",
|
||||
DeploymentType: "standalone-test",
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
Logger: json.RawMessage(`{"format": "text"}`),
|
||||
}
|
||||
c, err := ca.New(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// instantiate a client for the CA running at the random address
|
||||
caClient, err := ca.NewClient(
|
||||
fmt.Sprintf("https://localhost:%s", port),
|
||||
ca.WithRootFile(rootFilepath),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = c.Run()
|
||||
require.ErrorIs(t, err, http.ErrServerClosed)
|
||||
}()
|
||||
|
||||
// require OK health response as the baseline
|
||||
ctx := context.Background()
|
||||
healthResponse, err := caClient.HealthWithContext(ctx)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, healthResponse) {
|
||||
require.Equal(t, "ok", healthResponse.Status)
|
||||
}
|
||||
|
||||
scepClient, err := createSCEPClient(t, fmt.Sprintf("http://localhost:%s/scep/scep", insecurePort))
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"})
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
assert.Equal(t, "test.localhost", cert.Subject.CommonName)
|
||||
assert.Equal(t, "Step E2E | SCEP Regular w/ Upstream CAS Intermediate CA", cert.Issuer.CommonName)
|
||||
|
||||
// done testing; stop and wait for the server to quit
|
||||
err = c.Stop()
|
||||
require.NoError(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package sceptest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/minica"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
)
|
||||
|
||||
func TestIssuesCertificateUsingRegularSCEPWithUpstreamCAS(t *testing.T) {
|
||||
signer, err := keyutil.GenerateSigner("RSA", "", 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
dir := t.TempDir()
|
||||
m, err := minica.New(minica.WithName("Step E2E | SCEP Regular w/ Upstream CAS"), minica.WithGetSignerFunc(func() (crypto.Signer, error) {
|
||||
return signer, nil
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
rootFilepath := filepath.Join(dir, "root.crt")
|
||||
_, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateCertFilepath := filepath.Join(dir, "intermediate.crt")
|
||||
_, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
intermediateKeyFilepath := filepath.Join(dir, "intermediate.key")
|
||||
_, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath))
|
||||
require.NoError(t, err)
|
||||
|
||||
// get a random address to listen on and connect to; currently no nicer way to get one before starting the server
|
||||
// TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it?
|
||||
host, port := reservePort(t)
|
||||
insecureHost, insecurePort := reservePort(t)
|
||||
|
||||
prov := &provisioner.SCEP{
|
||||
ID: "scep",
|
||||
Name: "scep",
|
||||
Type: "SCEP",
|
||||
ForceCN: false,
|
||||
ChallengePassword: "",
|
||||
EncryptionAlgorithmIdentifier: 2,
|
||||
MinimumPublicKeyLength: 2048,
|
||||
Claims: &config.GlobalProvisionerClaims,
|
||||
}
|
||||
|
||||
err = prov.Init(provisioner.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
Root: []string{rootFilepath},
|
||||
IntermediateCert: intermediateCertFilepath,
|
||||
IntermediateKey: intermediateKeyFilepath,
|
||||
Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved"
|
||||
InsecureAddress: net.JoinHostPort(insecureHost, insecurePort), // reuse the address that was just "reserved"
|
||||
DNSNames: []string{"127.0.0.1", "[::1]", "localhost"},
|
||||
AuthorityConfig: &config.AuthConfig{
|
||||
AuthorityID: "stepca-test-scep",
|
||||
DeploymentType: "standalone-test",
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
Logger: json.RawMessage(`{"format": "text"}`),
|
||||
}
|
||||
c, err := ca.New(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
// instantiate a client for the CA running at the random address
|
||||
caClient, err := ca.NewClient(
|
||||
fmt.Sprintf("https://localhost:%s", port),
|
||||
ca.WithRootFile(rootFilepath),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err = c.Run()
|
||||
require.ErrorIs(t, err, http.ErrServerClosed)
|
||||
}()
|
||||
|
||||
// require OK health response as the baseline
|
||||
ctx := context.Background()
|
||||
healthResponse, err := caClient.HealthWithContext(ctx)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, healthResponse) {
|
||||
require.Equal(t, "ok", healthResponse.Status)
|
||||
}
|
||||
|
||||
scepClient, err := createSCEPClient(t, fmt.Sprintf("http://localhost:%s/scep/scep", insecurePort))
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"})
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
|
||||
assert.Equal(t, "test.localhost", cert.Subject.CommonName)
|
||||
assert.Equal(t, "Step E2E | SCEP Regular Intermediate CA", cert.Issuer.CommonName)
|
||||
|
||||
// done testing; stop and wait for the server to quit
|
||||
err = c.Stop()
|
||||
require.NoError(t, err)
|
||||
|
||||
wg.Wait()
|
||||
}
|
Loading…
Reference in New Issue