170 lines
5.3 KiB
Go
170 lines
5.3 KiB
Go
package scep
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io/ioutil"
|
|
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
|
database "github.com/smallstep/certificates/db"
|
|
"go.step.sm/crypto/pemutil"
|
|
|
|
"github.com/smallstep/nosql"
|
|
)
|
|
|
|
// Interface is the SCEP authority interface.
|
|
type Interface interface {
|
|
// GetDirectory(ctx context.Context) (*Directory, error)
|
|
// NewNonce() (string, error)
|
|
// UseNonce(string) error
|
|
|
|
// DeactivateAccount(ctx context.Context, accID string) (*Account, error)
|
|
// GetAccount(ctx context.Context, accID string) (*Account, error)
|
|
// GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error)
|
|
// NewAccount(ctx context.Context, ao AccountOptions) (*Account, error)
|
|
// UpdateAccount(context.Context, string, []string) (*Account, error)
|
|
|
|
// GetAuthz(ctx context.Context, accID string, authzID string) (*Authz, error)
|
|
// ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error)
|
|
|
|
// FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error)
|
|
// GetOrder(ctx context.Context, accID string, orderID string) (*Order, error)
|
|
// GetOrdersByAccount(ctx context.Context, accID string) ([]string, error)
|
|
// NewOrder(ctx context.Context, oo OrderOptions) (*Order, error)
|
|
|
|
// GetCertificate(string, string) ([]byte, error)
|
|
|
|
LoadProvisionerByID(string) (provisioner.Interface, error)
|
|
// GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string
|
|
// GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
|
|
|
|
GetCACertificates() ([]*x509.Certificate, error)
|
|
GetSigningKey() (*rsa.PrivateKey, error)
|
|
}
|
|
|
|
// Authority is the layer that handles all SCEP interactions.
|
|
type Authority struct {
|
|
backdate provisioner.Duration
|
|
db nosql.DB
|
|
prefix string
|
|
dns string
|
|
|
|
// dir *directory
|
|
|
|
intermediateCertificate *x509.Certificate
|
|
intermediateKey *rsa.PrivateKey
|
|
|
|
//signer crypto.Signer
|
|
|
|
signAuth SignAuthority
|
|
}
|
|
|
|
// AuthorityOptions required to create a new SCEP Authority.
|
|
type AuthorityOptions struct {
|
|
IntermediateCertificatePath string
|
|
IntermediateKeyPath string
|
|
|
|
// Backdate
|
|
Backdate provisioner.Duration
|
|
// DB is the database used by nosql.
|
|
DB nosql.DB
|
|
// DNS the host used to generate accurate SCEP links. By default the authority
|
|
// will use the Host from the request, so this value will only be used if
|
|
// request.Host is empty.
|
|
DNS string
|
|
// Prefix is a URL path prefix under which the SCEP api is served. This
|
|
// prefix is required to generate accurate SCEP links.
|
|
Prefix string
|
|
}
|
|
|
|
// SignAuthority is the interface implemented by a CA authority.
|
|
type SignAuthority interface {
|
|
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
|
LoadProvisionerByID(string) (provisioner.Interface, error)
|
|
}
|
|
|
|
// New returns a new Authority that implements the SCEP interface.
|
|
func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
|
|
if _, ok := ops.DB.(*database.SimpleDB); !ok {
|
|
// TODO: see ACME implementation
|
|
}
|
|
|
|
// TODO: the below is a bit similar as what happens in the core Authority class, which
|
|
// creates the full x509 service. However, those aren't accessible directly, which is
|
|
// why I reimplemented this (for now). There might be an alternative that I haven't
|
|
// found yet.
|
|
certificateChain, err := pemutil.ReadCertificateBundle(ops.IntermediateCertificatePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Authority{
|
|
backdate: ops.Backdate,
|
|
db: ops.DB,
|
|
prefix: ops.Prefix,
|
|
dns: ops.DNS,
|
|
intermediateCertificate: certificateChain[0],
|
|
intermediateKey: intermediateKey,
|
|
signAuth: signAuth,
|
|
}, nil
|
|
}
|
|
|
|
func readPrivateKey(path string) (*rsa.PrivateKey, error) {
|
|
|
|
keyBytes, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(keyBytes))
|
|
if block == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// LoadProvisionerByID calls out to the SignAuthority interface to load a
|
|
// provisioner by ID.
|
|
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
|
return a.signAuth.LoadProvisionerByID(id)
|
|
}
|
|
|
|
// GetCACertificates returns the certificate (chain) for the CA
|
|
func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) {
|
|
|
|
if a.intermediateCertificate == nil {
|
|
return nil, errors.New("no intermediate certificate available in SCEP authority")
|
|
}
|
|
|
|
return []*x509.Certificate{a.intermediateCertificate}, nil
|
|
}
|
|
|
|
// GetSigningKey returns the RSA private key for the CA
|
|
// TODO: we likely should provide utility functions for decrypting and
|
|
// signing instead of providing the signing key directly
|
|
func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) {
|
|
|
|
if a.intermediateKey == nil {
|
|
return nil, errors.New("no intermediate key available in SCEP authority")
|
|
}
|
|
|
|
return a.intermediateKey, nil
|
|
}
|
|
|
|
// Interface guards
|
|
var (
|
|
_ Interface = (*Authority)(nil)
|
|
)
|