Compare commits

..

No commits in common. 'master' and 'v0.25.3-rc5' have entirely different histories.

@ -12,7 +12,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.1.0
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs

@ -98,7 +98,7 @@ signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}", "--yes"]
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"]
artifacts: all
snapshot:
@ -180,7 +180,7 @@ release:
Those were the changes on {{ .Tag }}!
Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peek at the freshest PKI memes.
Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peak at the freshest PKI memes.
# You can disable this pipe in order to not upload any artifacts.
# Defaults to false.
@ -268,7 +268,7 @@ winget:
# Release notes URL.
#
# Templates: allowed
release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{ .Tag }}"
release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{.Version}}"
# Create the PR - for testing
skip_upload: auto
@ -283,7 +283,7 @@ winget:
repository:
owner: smallstep
name: winget-pkgs
branch: "step-ca-{{.Version}}"
branch: step
# Optionally a token can be provided, if it differs from the token
# provided to GoReleaser

@ -25,47 +25,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
---
## [0.26.1] - 2024-04-22
### Added
- Allow configuration of a custom SCEP key manager (smallstep/certificates#1797)
### Fixed
- id-scep-failInfoText OID (smallstep/certificates#1794)
- CA startup with Vault RA configuration (smallstep/certificates#1803)
## [0.26.0] - 2024-03-28
### Added
- [TPM KMS](https://github.com/smallstep/crypto/tree/master/kms/tpmkms) support for CA keys (smallstep/certificates#1772)
- Propagation of HTTP request identifier using X-Request-Id header (smallstep/certificates#1743, smallstep/certificates#1542)
- Expires header in CRL response (smallstep/certificates#1708)
- Support for providing TLS configuration programmatically (smallstep/certificates#1685)
- Support for providing external CAS implementation (smallstep/certificates#1684)
- AWS `ca-west-1` identity document root certificate (smallstep/certificates#1715)
- [COSE RS1](https://www.rfc-editor.org/rfc/rfc8812.html#section-2) as a supported algorithm with ACME `device-attest-01` challenge (smallstep/certificates#1663)
### Changed
- In an RA setup, let the CA decide the RA certificate lifetime (smallstep/certificates#1764)
- Use Debian Bookworm in Docker containers (smallstep/certificates#1615)
- Error message for CSR validation (smallstep/certificates#1665)
- Updated dependencies
### Fixed
- Stop CA when any of the required servers fails to start (smallstep/certificates#1751). Before the fix, the CA would continue running and only log the server failure when stopped.
- Configuration loading errors when not using context were not returned. Fixed in [cli-utils/109](https://github.com/smallstep/cli-utils/pull/109).
- HTTP_PROXY and HTTPS_PROXY support for ACME validation client (smallstep/certificates#1658).
### Security
- Upgrade to using cosign v2 for signing artifacts
## [0.25.1] - 2023-11-28
### Added
@ -77,7 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Generation of first provisioner name on `step ca init` in (smallstep/certificates#1566)
- Processing of SCEP Get PKIOperation requests in (smallstep/certificates#1570)
- Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572)
- Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572)
- Dependency on `micromdm/scep` and `go.mozilla.org/pkcs7` to use Smallstep forks in (smallstep/certificates#1600)
- Make the Common Name validator for JWK provisioners accept values from SANs too in (smallstep/certificates#1609)

@ -1,62 +1,49 @@
# step-ca
# Step Certificates
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates)
`step-ca` is an online certificate authority for secure, automated certificate management. It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli).
`step-ca` is an online certificate authority for secure, automated certificate management for DevOps.
It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli) for working with certificates and keys.
Both projects are maintained by [Smallstep Labs](https://smallstep.com).
You can use `step-ca` to:
- Issue HTTPS server and client certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance)
- Issue TLS certificates for DevOps: VMs, containers, APIs, database connections, Kubernetes pods...
You can use it to:
- Issue X.509 certificates for your internal infrastructure:
- HTTPS certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance)
- TLS certificates for VMs, containers, APIs, mobile clients, database connections, printers, wifi networks, toaster ovens...
- Client certificates to [enable mutual TLS (mTLS)](https://smallstep.com/hello-mtls) in your infra. mTLS is an optional feature in TLS where both client and server authenticate each other. Why add the complexity of a VPN when you can safely use mTLS over the public internet?
- Issue SSH certificates:
- For people, in exchange for single sign-on identity tokens
- For people, in exchange for single sign-on ID tokens
- For hosts, in exchange for cloud instance identity documents
- Easily automate certificate management:
- It's an [ACME server](https://smallstep.com/docs/step-ca/acme-basics/) that supports all [popular ACME challenge types](https://smallstep.com/docs/step-ca/acme-basics/#acme-challenge-types)
- It's an ACME v2 server
- It has a JSON API
- It comes with a [Go wrapper](./examples#user-content-basic-client-usage)
- ... and there's a [command-line client](https://github.com/smallstep/cli) you can use in scripts!
---
### Comparison with Smallstep's commercial product
`step-ca` is optimized for a two-tier PKI serving common DevOps use cases.
As you design your PKI, if you need any of the following, [consider our commerical CA](http://smallstep.com):
- Multiple certificate authorities
- Active revocation (CRL, OSCP)
- Turnkey high-volume, high availability CA
- An API for seamless IaC management of your PKI
- Integrated support for SCEP & NDES, for migrating from legacy Active Directory Certificate Services deployments
- Device identity — cross-platform device inventory and attestation using Secure Enclave & TPM 2.0
- Highly automated PKI — managed certificate renewal, monitoring, TPM-based attested enrollment
- Seamless client deployments of EAP-TLS Wi-Fi, VPN, SSH, and browser certificates
- Jamf, Intune, or other MDM for root distribution and client enrollment
- Web Admin UI — history, issuance, and metrics
- ACME External Account Binding (EAB)
- Deep integration with an identity provider
- Fine-grained, role-based access control
- FIPS-compliant software
- HSM-bound private keys
Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [safe, sane defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults).
See our [full feature comparison](https://smallstep.com/step-ca-vs-smallstep-certificate-manager/) for more.
---
You can [start a free trial](https://smallstep.com/signup) or [set up a call with us](https://go.smallstep.com/request-demo) to learn more.
**Don't want to run your own CA?**
To get up and running quickly, or as an alternative to running your own `step-ca` server, consider creating a [free hosted smallstep Certificate Manager authority](https://info.smallstep.com/certificate-manager-early-access-mvp/).
---
**Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions) or [Join our Discord](https://u.step.sm/discord).**
[Website](https://smallstep.com/certificates) |
[Documentation](https://smallstep.com/docs/step-ca) |
[Documentation](https://smallstep.com/docs) |
[Installation](https://smallstep.com/docs/step-ca/installation) |
[Getting Started](https://smallstep.com/docs/step-ca/getting-started) |
[Contributor's Guide](./CONTRIBUTING.md)
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates)
[![GitHub stars](https://img.shields.io/github/stars/smallstep/certificates.svg?style=social)](https://github.com/smallstep/certificates/stargazers)
[![Twitter followers](https://img.shields.io/twitter/follow/smallsteplabs.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=smallsteplabs)
![star us](https://github.com/smallstep/certificates/raw/master/docs/images/star.gif)
## Features
### 🦾 A fast, stable, flexible private CA
@ -65,6 +52,7 @@ Setting up a *public key infrastructure* (PKI) is out of reach for many small te
- Choose key types (RSA, ECDSA, EdDSA) and lifetimes to suit your needs
- [Short-lived certificates](https://smallstep.com/blog/passive-revocation.html) with automated enrollment, renewal, and passive revocation
- Capable of high availability (HA) deployment using [root federation](https://smallstep.com/blog/step-v0.8.3-federation-root-rotation.html) and/or multiple intermediaries
- Can operate as [an online intermediate CA for an existing root CA](https://smallstep.com/docs/tutorials/intermediate-ca-new-ca)
- [Badger, BoltDB, Postgres, and MySQL database backends](https://smallstep.com/docs/step-ca/configuration#databases)
@ -139,5 +127,5 @@ and visiting http://localhost:8080.
## Feedback?
* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space. [Join our Discord](https://u.step.sm/discord) or [GitHub Discussions](https://github.com/smallstep/certificates/discussions)
* Tell us about a feature you'd like to see! [Request a Feature](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=)
* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space.
* Tell us about a feature you'd like to see! [Add a feature request Issue](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=), [ask on Discussions](https://github.com/smallstep/certificates/discussions), or hit us up on [Twitter](https://twitter.com/smallsteplabs).

@ -21,7 +21,6 @@ type Account struct {
OrdersURL string `json:"orders"`
ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"`
LocationPrefix string `json:"-"`
ProvisionerID string `json:"-"`
ProvisionerName string `json:"-"`
}

@ -82,23 +82,23 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
var nar NewAccountRequest
if err := json.Unmarshal(payload.value, &nar); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := nar.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -108,26 +108,26 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
var acmeErr *acme.Error
if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest {
// Something went wrong ...
render.Error(w, r, err)
render.Error(w, err)
return
}
// Account does not exist //
if nar.OnlyReturnExisting {
render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType,
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType,
"account does not exist"))
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
eak, err := validateExternalAccountBinding(ctx, &nar)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -136,21 +136,20 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
Contact: nar.Contact,
Status: acme.StatusValid,
LocationPrefix: getAccountLocationPath(ctx, linker, ""),
ProvisionerID: prov.ID,
ProvisionerName: prov.Name,
ProvisionerName: prov.GetName(),
}
if err := db.CreateAccount(ctx, acc); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error creating account"))
render.Error(w, acme.WrapErrorISE(err, "error creating account"))
return
}
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
if err := eak.BindTo(acc); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error updating external account binding key"))
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key"))
return
}
acc.ExternalAccountBinding = nar.ExternalAccountBinding
@ -163,7 +162,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc)
w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID))
render.JSONStatus(w, r, acc, httpStatus)
render.JSONStatus(w, acc, httpStatus)
}
// GetOrUpdateAccount is the api for updating an ACME account.
@ -174,12 +173,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -188,12 +187,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
if !payload.isPostAsGet {
var uar UpdateAccountRequest
if err := json.Unmarshal(payload.value, &uar); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-account request payload"))
return
}
if err := uar.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if len(uar.Status) > 0 || len(uar.Contact) > 0 {
@ -204,7 +203,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
}
if err := db.UpdateAccount(ctx, acc); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error updating account"))
render.Error(w, acme.WrapErrorISE(err, "error updating account"))
return
}
}
@ -213,7 +212,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) {
linker.LinkAccount(ctx, acc)
w.Header().Set("Location", linker.GetLink(ctx, acme.AccountLinkType, acc.ID))
render.JSON(w, r, acc)
render.JSON(w, acc)
}
func logOrdersByAccount(w http.ResponseWriter, oids []string) {
@ -233,23 +232,23 @@ func GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
accID := chi.URLParam(r, "accID")
if acc.ID != accID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID))
return
}
orders, err := db.GetOrdersByAccountID(ctx, acc.ID)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
linker.LinkOrdersByAccountID(ctx, orders)
render.JSON(w, r, orders)
render.JSON(w, orders)
logOrdersByAccount(w, orders)
}

@ -14,7 +14,6 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/pkg/errors"
"go.step.sm/crypto/jose"
@ -67,19 +66,6 @@ func newProv() acme.Provisioner {
return p
}
func newProvWithID() acme.Provisioner {
// Initialize provisioners
p := &provisioner.ACME{
ID: uuid.NewString(),
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
}
if err := p.Init(provisioner.Config{Claims: globalProvisionerClaims}); err != nil {
fmt.Printf("%v", err)
}
return p
}
func newProvWithOptions(options *provisioner.Options) acme.Provisioner {
// Initialize provisioners
p := &provisioner.ACME{

@ -223,13 +223,13 @@ func GetDirectory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
linker := acme.MustLinkerFromContext(ctx)
render.JSON(w, r, &Directory{
render.JSON(w, &Directory{
NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType),
NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType),
NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType),
@ -273,8 +273,8 @@ func shouldAddMetaObject(p *provisioner.ACME) bool {
// NotImplemented returns a 501 and is generally a placeholder for functionality which
// MAY be added at some point in the future but is not in any way a guarantee of such.
func NotImplemented(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
func NotImplemented(w http.ResponseWriter, _ *http.Request) {
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented"))
}
// GetAuthorization ACME api for retrieving an Authz.
@ -285,28 +285,28 @@ func GetAuthorization(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
az, err := db.GetAuthorization(ctx, chi.URLParam(r, "authzID"))
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving authorization"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving authorization"))
return
}
if acc.ID != az.AccountID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own authorization '%s'", acc.ID, az.ID))
return
}
if err = az.UpdateStatus(ctx, db); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error updating authorization status"))
render.Error(w, acme.WrapErrorISE(err, "error updating authorization status"))
return
}
linker.LinkAuthorization(ctx, az)
w.Header().Set("Location", linker.GetLink(ctx, acme.AuthzLinkType, az.ID))
render.JSON(w, r, az)
render.JSON(w, az)
}
// GetChallenge ACME api for retrieving a Challenge.
@ -317,13 +317,13 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -336,22 +336,22 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
azID := chi.URLParam(r, "authzID")
ch, err := db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving challenge"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving challenge"))
return
}
ch.AuthorizationID = azID
if acc.ID != ch.AccountID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own challenge '%s'", acc.ID, ch.ID))
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if err = ch.Validate(ctx, db, jwk, payload.value); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error validating challenge"))
render.Error(w, acme.WrapErrorISE(err, "error validating challenge"))
return
}
@ -359,7 +359,7 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Link", link(linker.GetLink(ctx, acme.AuthzLinkType, azID), "up"))
w.Header().Set("Location", linker.GetLink(ctx, acme.ChallengeLinkType, azID, ch.ID))
render.JSON(w, r, ch)
render.JSON(w, ch)
}
// GetCertificate ACME api for retrieving a Certificate.
@ -369,18 +369,18 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
certID := chi.URLParam(r, "certID")
cert, err := db.GetCertificate(ctx, certID)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate"))
return
}
if cert.AccountID != acc.ID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own certificate '%s'", acc.ID, certID))
return
}

@ -36,7 +36,7 @@ func addNonce(next nextHTTP) nextHTTP {
db := acme.MustDatabaseFromContext(r.Context())
nonce, err := db.CreateNonce(r.Context())
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
w.Header().Set("Replay-Nonce", string(nonce))
@ -64,7 +64,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
p, err := provisionerFromContext(r.Context())
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -88,7 +88,7 @@ func verifyContentType(next nextHTTP) nextHTTP {
return
}
}
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"expected content-type to be in %s, but got %s", expected, ct))
}
}
@ -98,12 +98,12 @@ func parseJWS(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "failed to read request body"))
render.Error(w, acme.WrapErrorISE(err, "failed to read request body"))
return
}
jws, err := jose.ParseJWS(string(body))
if err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body"))
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body"))
return
}
ctx := context.WithValue(r.Context(), jwsContextKey, jws)
@ -133,26 +133,26 @@ func validateJWS(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if len(jws.Signatures) == 0 {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature"))
return
}
if len(jws.Signatures) > 1 {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature"))
return
}
sig := jws.Signatures[0]
uh := sig.Unprotected
if uh.KeyID != "" ||
if len(uh.KeyID) > 0 ||
uh.JSONWebKey != nil ||
uh.Algorithm != "" ||
uh.Nonce != "" ||
len(uh.Algorithm) > 0 ||
len(uh.Nonce) > 0 ||
len(uh.ExtraHeaders) > 0 {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used"))
return
}
hdr := sig.Protected
@ -162,13 +162,13 @@ func validateJWS(next nextHTTP) nextHTTP {
switch k := hdr.JSONWebKey.Key.(type) {
case *rsa.PublicKey:
if k.Size() < keyutil.MinRSAKeyBytes {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"rsa keys must be at least %d bits (%d bytes) in size",
8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes))
return
}
default:
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"jws key type and algorithm do not match"))
return
}
@ -176,35 +176,35 @@ func validateJWS(next nextHTTP) nextHTTP {
case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
// we good
default:
render.Error(w, r, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm))
render.Error(w, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm))
return
}
// Check the validity/freshness of the Nonce.
if err := db.DeleteNonce(ctx, acme.Nonce(hdr.Nonce)); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
// Check that the JWS url matches the requested url.
jwsURL, ok := hdr.ExtraHeaders["url"].(string)
if !ok {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header"))
return
}
reqURL := &url.URL{Scheme: "https", Host: r.Host, Path: r.URL.Path}
if jwsURL != reqURL.String() {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"url header in JWS (%s) does not match request url (%s)", jwsURL, reqURL))
return
}
if hdr.JSONWebKey != nil && hdr.KeyID != "" {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive"))
if hdr.JSONWebKey != nil && len(hdr.KeyID) > 0 {
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive"))
return
}
if hdr.JSONWebKey == nil && hdr.KeyID == "" {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header"))
return
}
next(w, r)
@ -221,23 +221,23 @@ func extractJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
jwk := jws.Signatures[0].Protected.JSONWebKey
if jwk == nil {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header"))
return
}
if !jwk.Valid() {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header"))
return
}
// Overwrite KeyID with the JWK thumbprint.
jwk.KeyID, err = acme.KeyToID(jwk)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error getting KeyID from JWK"))
render.Error(w, acme.WrapErrorISE(err, "error getting KeyID from JWK"))
return
}
@ -247,15 +247,15 @@ func extractJWK(next nextHTTP) nextHTTP {
// Get Account OR continue to generate a new one OR continue Revoke with certificate private key
acc, err := db.GetAccountByKeyID(ctx, jwk.KeyID)
switch {
case acme.IsErrNotFound(err):
case errors.Is(err, acme.ErrNotFound):
// For NewAccount and Revoke requests ...
break
case err != nil:
render.Error(w, r, err)
render.Error(w, err)
return
default:
if !acc.IsValid() {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return
}
ctx = context.WithValue(ctx, accContextKey, acc)
@ -274,11 +274,11 @@ func checkPrerequisites(next nextHTTP) nextHTTP {
if ok {
ok, err := checkFunc(ctx)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites"))
render.Error(w, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites"))
return
}
if !ok {
render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites"))
render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites"))
return
}
}
@ -296,13 +296,13 @@ func lookupJWK(next nextHTTP) nextHTTP {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
kid := jws.Signatures[0].Protected.KeyID
if kid == "" {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'"))
return
}
@ -310,14 +310,14 @@ func lookupJWK(next nextHTTP) nextHTTP {
acc, err := db.GetAccount(ctx, accID)
switch {
case acme.IsErrNotFound(err):
render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID))
return
case err != nil:
render.Error(w, r, err)
render.Error(w, err)
return
default:
if !acc.IsValid() {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active"))
return
}
@ -325,7 +325,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
if kid != storedLocation {
// ACME accounts should have a stored location equivalent to the
// kid in the ACME request.
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"kid does not match stored account location; expected %s, but got %s",
storedLocation, kid))
return
@ -334,16 +334,14 @@ func lookupJWK(next nextHTTP) nextHTTP {
// Verify that the provisioner with which the account was created
// matches the provisioner in the request URL.
reqProv := acme.MustProvisionerFromContext(ctx)
switch {
case acc.ProvisionerID == "" && acc.ProvisionerName != reqProv.GetName():
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerName, reqProv.GetName()))
return
case acc.ProvisionerID != "" && acc.ProvisionerID != reqProv.GetID():
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
reqProvName := reqProv.GetName()
accProvName := acc.ProvisionerName
if reqProvName != accProvName {
// Provisioner in the URL must match the provisioner with
// which the account was created.
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerID, reqProv.GetID()))
accProvName, reqProvName))
return
}
} else {
@ -355,7 +353,7 @@ func lookupJWK(next nextHTTP) nextHTTP {
linker := acme.MustLinkerFromContext(ctx)
kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "")
if !strings.HasPrefix(kid, kidPrefix) {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType,
render.Error(w, acme.NewError(acme.ErrorMalformedType,
"kid does not have required prefix; expected %s, but got %s",
kidPrefix, kid))
return
@ -376,7 +374,7 @@ func extractOrLookupJWK(next nextHTTP) nextHTTP {
ctx := r.Context()
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -412,16 +410,16 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
ctx := r.Context()
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
jwk, err := jwkFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if jwk.Algorithm != "" && jwk.Algorithm != jws.Signatures[0].Protected.Algorithm {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match"))
return
}
@ -430,11 +428,11 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP {
case errors.Is(err, jose.ErrCryptoFailure):
payload, err = retryVerificationWithPatchedSignatures(jws, jwk)
if err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)"))
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)"))
return
}
case err != nil:
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws"))
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws"))
return
}
@ -551,11 +549,11 @@ func isPostAsGet(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) {
payload, err := payloadFromContext(r.Context())
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if !payload.isPostAsGet {
render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET"))
render.Error(w, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET"))
return
}
next(w, r)

@ -15,7 +15,6 @@ import (
"strings"
"testing"
"github.com/google/uuid"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/acme"
tassert "github.com/stretchr/testify/assert"
@ -832,37 +831,8 @@ func TestHandler_lookupJWK(t *testing.T) {
},
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
"other", prov.GetName()),
}
},
"fail/account-with-location-prefix/bad-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: uuid.NewString()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: http.StatusUnauthorized,
err: acme.NewError(acme.ErrorUnauthorizedType,
"account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s",
acc.ProvisionerID, p.GetID()),
"account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s",
prov.GetName(), "other"),
}
},
"ok/account-with-location-prefix": func(t *testing.T) test {
@ -915,32 +885,6 @@ func TestHandler_lookupJWK(t *testing.T) {
statusCode: 200,
}
},
"ok/account-with-provisioner-id": func(t *testing.T) test {
p := newProvWithID()
acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: p.GetID()}
ctx := acme.NewProvisionerContext(context.Background(), p)
ctx = context.WithValue(ctx, jwsContextKey, parsedJWS)
return test{
linker: acme.NewLinker("test.ca.smallstep.com", "acme"),
db: &acme.MockDB{
MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) {
assert.Equals(t, id, accID)
return acc, nil
},
},
ctx: ctx,
next: func(w http.ResponseWriter, r *http.Request) {
_acc, err := accountFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _acc, acc)
_jwk, err := jwkFromContext(r.Context())
assert.FatalError(t, err)
assert.Equals(t, _jwk, jwk)
w.Write(testBody)
},
statusCode: 200,
}
},
}
for name, run := range tests {
tc := run(t)

@ -99,29 +99,29 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
var nor NewOrderRequest
if err := json.Unmarshal(payload.value, &nor); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal new-order request payload"))
return
}
if err := nor.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -130,39 +130,39 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
acmeProv, err := acmeProvisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
var eak *acme.ExternalAccountKey
if acmeProv.RequireEAB {
if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving external account binding key"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving external account binding key"))
return
}
}
acmePolicy, err := newACMEPolicyEngine(eak)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error creating ACME policy engine"))
render.Error(w, acme.WrapErrorISE(err, "error creating ACME policy engine"))
return
}
for _, identifier := range nor.Identifiers {
// evaluate the ACME account level policy
if err = isIdentifierAllowed(acmePolicy, identifier); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
// evaluate the provisioner level policy
orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value}
if err = prov.AuthorizeOrderIdentifier(ctx, orderIdentifier); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
// evaluate the authority level policy
if err = ca.AreSANsAllowed(ctx, []string{identifier.Value}); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized"))
return
}
}
@ -188,7 +188,7 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
Status: acme.StatusPending,
}
if err := newAuthorization(ctx, az); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
o.AuthorizationIDs[i] = az.ID
@ -207,14 +207,14 @@ func NewOrder(w http.ResponseWriter, r *http.Request) {
}
if err := db.CreateOrder(ctx, o); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error creating order"))
render.Error(w, acme.WrapErrorISE(err, "error creating order"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSONStatus(w, r, o, http.StatusCreated)
render.JSONStatus(w, o, http.StatusCreated)
}
func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifier) error {
@ -288,39 +288,39 @@ func GetOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving order"))
return
}
if acc.ID != o.AccountID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID))
return
}
if prov.GetID() != o.ProvisionerID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return
}
if err = o.UpdateStatus(ctx, db); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error updating order status"))
render.Error(w, acme.WrapErrorISE(err, "error updating order status"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, r, o)
render.JSON(w, o)
}
// FinalizeOrder attempts to finalize an order and create a certificate.
@ -331,56 +331,56 @@ func FinalizeOrder(w http.ResponseWriter, r *http.Request) {
acc, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
var fr FinalizeRequest
if err := json.Unmarshal(payload.value, &fr); err != nil {
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err,
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err,
"failed to unmarshal finalize-order request payload"))
return
}
if err := fr.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID"))
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving order"))
return
}
if acc.ID != o.AccountID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"account '%s' does not own order '%s'", acc.ID, o.ID))
return
}
if prov.GetID() != o.ProvisionerID {
render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType,
render.Error(w, acme.NewError(acme.ErrorUnauthorizedType,
"provisioner '%s' does not own order '%s'", prov.GetID(), o.ID))
return
}
ca := mustAuthority(ctx)
if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error finalizing order"))
render.Error(w, acme.WrapErrorISE(err, "error finalizing order"))
return
}
linker.LinkOrder(ctx, o)
w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID))
render.JSON(w, r, o)
render.JSON(w, o)
}
// challengeTypes determines the types of challenges that should be used

@ -33,65 +33,65 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
jws, err := jwsFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov, err := provisionerFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
payload, err := payloadFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
var p revokePayload
err = json.Unmarshal(payload.value, &p)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error unmarshaling payload"))
render.Error(w, acme.WrapErrorISE(err, "error unmarshaling payload"))
return
}
certBytes, err := base64.RawURLEncoding.DecodeString(p.Certificate)
if err != nil {
// in this case the most likely cause is a client that didn't properly encode the certificate
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property"))
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property"))
return
}
certToBeRevoked, err := x509.ParseCertificate(certBytes)
if err != nil {
// in this case a client may have encoded something different than a certificate
render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate"))
render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate"))
return
}
serial := certToBeRevoked.SerialNumber.String()
dbCert, err := db.GetCertificateBySerial(ctx, serial)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate by serial"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate by serial"))
return
}
if !bytes.Equal(dbCert.Leaf.Raw, certToBeRevoked.Raw) {
// this should never happen
render.Error(w, r, acme.NewErrorISE("certificate raw bytes are not equal"))
render.Error(w, acme.NewErrorISE("certificate raw bytes are not equal"))
return
}
if shouldCheckAccountFrom(jws) {
account, err := accountFromContext(ctx)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
acmeErr := isAccountAuthorized(ctx, dbCert, certToBeRevoked, account)
if acmeErr != nil {
render.Error(w, r, acmeErr)
render.Error(w, acmeErr)
return
}
} else {
@ -100,7 +100,7 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
_, err := jws.Verify(certToBeRevoked.PublicKey)
if err != nil {
// TODO(hs): possible to determine an error vs. unauthorized and thus provide an ISE vs. Unauthorized?
render.Error(w, r, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err))
render.Error(w, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err))
return
}
}
@ -108,19 +108,19 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ca := mustAuthority(ctx)
hasBeenRevokedBefore, err := ca.IsRevoked(serial)
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error retrieving revocation status of certificate"))
render.Error(w, acme.WrapErrorISE(err, "error retrieving revocation status of certificate"))
return
}
if hasBeenRevokedBefore {
render.Error(w, r, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked"))
render.Error(w, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked"))
return
}
reasonCode := p.ReasonCode
acmeErr := validateReasonCode(reasonCode)
if acmeErr != nil {
render.Error(w, r, acmeErr)
render.Error(w, acmeErr)
return
}
@ -128,14 +128,14 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.RevokeMethod)
err = prov.AuthorizeRevoke(ctx, "")
if err != nil {
render.Error(w, r, acme.WrapErrorISE(err, "error authorizing revocation on provisioner"))
render.Error(w, acme.WrapErrorISE(err, "error authorizing revocation on provisioner"))
return
}
options := revokeOptions(serial, certToBeRevoked, reasonCode)
err = ca.Revoke(ctx, options)
if err != nil {
render.Error(w, r, wrapRevokeErr(err))
render.Error(w, wrapRevokeErr(err))
return
}

@ -281,7 +281,7 @@ type mockCA struct {
MockAreSANsallowed func(ctx context.Context, sans []string) error
}
func (m *mockCA) SignWithContext(context.Context, *x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) {
func (m *mockCA) Sign(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) {
return nil, nil
}

@ -726,7 +726,7 @@ var (
oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3}
)
// validateAKCertificate validates the X.509 AK certificate to be
// validateAKCertifiate validates the X.509 AK certificate to be
// in accordance with the required properties. The requirements come from:
// https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements.
//
@ -735,7 +735,7 @@ var (
// - The Subject Alternative Name extension MUST be set as defined
// in [TPMv2-EK-Profile] section 3.2.9.
// - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
// ("joint-iso-itu-t(2) international-organizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
// ("joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
// - The Basic Constraints extension MUST have the CA component set to false.
// - An Authority Information Access (AIA) extension with entry id-ad-ocsp
// and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as

@ -21,7 +21,7 @@ var clock Clock
// CertificateAuthority is the interface implemented by a CA authority.
type CertificateAuthority interface {
SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
AreSANsAllowed(ctx context.Context, sans []string) error
IsRevoked(sn string) (bool, error)
Revoke(context.Context, *authority.RevokeOptions) error
@ -130,7 +130,7 @@ func (m *MockProvisioner) GetName() string {
return m.Mret1.(string)
}
// AuthorizeOrderIdentifier mock
// AuthorizeOrderIdentifiers mock
func (m *MockProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error {
if m.MauthorizeOrderIdentifier != nil {
return m.MauthorizeOrderIdentifier(ctx, identifier)

@ -2,7 +2,6 @@ package acme
import (
"context"
"database/sql"
"github.com/pkg/errors"
)
@ -16,7 +15,7 @@ var ErrNotFound = errors.New("not found")
// IsErrNotFound returns true if the error is a "not found" error. Returns false
// otherwise.
func IsErrNotFound(err error) bool {
return errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows)
return errors.Is(err, ErrNotFound)
}
// DB is the DB interface expected by the step-ca ACME API.

@ -18,7 +18,6 @@ type dbAccount struct {
Contact []string `json:"contact,omitempty"`
Status acme.Status `json:"status"`
LocationPrefix string `json:"locationPrefix"`
ProvisionerID string `json:"provisionerID,omitempty"`
ProvisionerName string `json:"provisionerName"`
CreatedAt time.Time `json:"createdAt"`
DeactivatedAt time.Time `json:"deactivatedAt"`
@ -70,7 +69,6 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error)
Key: dbacc.Key,
ID: dbacc.ID,
LocationPrefix: dbacc.LocationPrefix,
ProvisionerID: dbacc.ProvisionerID,
ProvisionerName: dbacc.ProvisionerName,
}, nil
}
@ -99,7 +97,6 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error {
Status: acc.Status,
CreatedAt: clock.Now(),
LocationPrefix: acc.LocationPrefix,
ProvisionerID: acc.ProvisionerID,
ProvisionerName: acc.ProvisionerName,
}

@ -68,14 +68,12 @@ func TestDB_getDBAccount(t *testing.T) {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
dbacc := &dbAccount{
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
ProvisionerID: "73d2c0f1-9753-448b-9b48-bf00fe434681",
ProvisionerName: "acme",
ID: accID,
Status: acme.StatusDeactivated,
CreatedAt: now,
DeactivatedAt: now,
Contact: []string{"foo", "bar"},
Key: jwk,
}
b, err := json.Marshal(dbacc)
assert.FatalError(t, err)

@ -1,32 +0,0 @@
package acme
import (
"database/sql"
"errors"
"fmt"
"testing"
)
func TestIsErrNotFound(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
want bool
}{
{"true ErrNotFound", args{ErrNotFound}, true},
{"true sql.ErrNoRows", args{sql.ErrNoRows}, true},
{"true wrapped ErrNotFound", args{fmt.Errorf("something failed: %w", ErrNotFound)}, true},
{"true wrapped sql.ErrNoRows", args{fmt.Errorf("something failed: %w", sql.ErrNoRows)}, true},
{"false other", args{errors.New("not found")}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsErrNotFound(tt.args.err); got != tt.want {
t.Errorf("IsErrNotFound() = %v, want %v", got, tt.want)
}
})
}
}

@ -424,7 +424,7 @@ func (e *Error) ToLog() (interface{}, error) {
}
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
func (e *Error) Render(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/problem+json")
render.JSONStatus(w, r, e, e.StatusCode())
render.JSONStatus(w, e, e.StatusCode())
}

@ -186,19 +186,19 @@ func (l *linker) Middleware(next http.Handler) http.Handler {
nameEscaped := chi.URLParam(r, "provisionerID")
name, err := url.PathUnescape(nameEscaped)
if err != nil {
render.Error(w, r, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
render.Error(w, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
return
}
p, err := authority.MustFromContext(ctx).LoadProvisionerByName(name)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
acmeProv, ok := p.(*provisioner.ACME)
if !ok {
render.Error(w, r, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME"))
render.Error(w, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME"))
return
}

@ -127,7 +127,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil
}
// getAuthorizationFingerprint returns a fingerprint from the list of authorizations. This
// getKeyFingerprint returns a fingerprint from the list of authorizations. This
// fingerprint is used on the device-attest-01 flow to verify the attestation
// certificate public key with the CSR public key.
//
@ -263,7 +263,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
signOps = append(signOps, extraOptions...)
// Sign a new certificate.
certChain, err := auth.SignWithContext(ctx, csr, provisioner.SignOptions{
certChain, err := auth.Sign(csr, provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(o.NotBefore),
NotAfter: provisioner.NewTimeDuration(o.NotAfter),
}, signOps...)

@ -271,16 +271,16 @@ func TestOrder_UpdateStatus(t *testing.T) {
}
type mockSignAuth struct {
signWithContext func(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
areSANsAllowed func(ctx context.Context, sans []string) error
loadProvisionerByName func(string) (provisioner.Interface, error)
ret1, ret2 interface{}
err error
}
func (m *mockSignAuth) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.signWithContext != nil {
return m.signWithContext(ctx, csr, signOpts, extraOpts...)
func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.sign != nil {
return m.sign(csr, signOpts, extraOpts...)
} else if m.err != nil {
return nil, m.err
}
@ -578,7 +578,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return nil, errors.New("force")
},
@ -628,7 +628,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil
},
@ -685,7 +685,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil
},
@ -770,7 +770,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil
},
@ -863,7 +863,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil
},
@ -973,7 +973,7 @@ func TestOrder_Finalize(t *testing.T) {
// using the mocking functions as a wrapper for actual test helpers generated per test case or per
// function that's tested.
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil
},
@ -1044,7 +1044,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil
},
@ -1108,7 +1108,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil
},
@ -1175,7 +1175,7 @@ func TestOrder_Finalize(t *testing.T) {
},
},
ca: &mockSignAuth{
signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{foo, bar, baz}, nil
},

@ -42,7 +42,7 @@ type Authority interface {
AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error)
GetTLSOptions() *config.TLSOptions
Root(shasum string) (*x509.Certificate, error)
SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
@ -54,7 +54,7 @@ type Authority interface {
GetRoots() ([]*x509.Certificate, error)
GetFederation() ([]*x509.Certificate, error)
Version() authority.Version
GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error)
GetCertificateRevocationList() ([]byte, error)
}
// mustAuthority will be replaced on unit tests.
@ -353,15 +353,15 @@ func Route(r Router) {
// Version is an HTTP handler that returns the version of the server.
func Version(w http.ResponseWriter, r *http.Request) {
v := mustAuthority(r.Context()).Version()
render.JSON(w, r, VersionResponse{
render.JSON(w, VersionResponse{
Version: v.Version,
RequireClientAuthentication: v.RequireClientAuthentication,
})
}
// Health is an HTTP handler that returns the status of the server.
func Health(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, HealthResponse{Status: "ok"})
func Health(w http.ResponseWriter, _ *http.Request) {
render.JSON(w, HealthResponse{Status: "ok"})
}
// Root is an HTTP handler that using the SHA256 from the URL, returns the root
@ -372,11 +372,11 @@ func Root(w http.ResponseWriter, r *http.Request) {
// Load root certificate with the
cert, err := mustAuthority(r.Context()).Root(sum)
if err != nil {
render.Error(w, r, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI))
render.Error(w, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI))
return
}
render.JSON(w, r, &RootResponse{RootPEM: Certificate{cert}})
render.JSON(w, &RootResponse{RootPEM: Certificate{cert}})
}
func certChainToPEM(certChain []*x509.Certificate) []Certificate {
@ -391,17 +391,17 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate {
func Provisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := ParseCursor(r)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
render.JSON(w, r, &ProvisionersResponse{
render.JSON(w, &ProvisionersResponse{
Provisioners: p,
NextCursor: next,
})
@ -412,18 +412,18 @@ func ProvisionerKey(w http.ResponseWriter, r *http.Request) {
kid := chi.URLParam(r, "kid")
key, err := mustAuthority(r.Context()).GetEncryptedKey(kid)
if err != nil {
render.Error(w, r, errs.NotFoundErr(err))
render.Error(w, errs.NotFoundErr(err))
return
}
render.JSON(w, r, &ProvisionerKeyResponse{key})
render.JSON(w, &ProvisionerKeyResponse{key})
}
// Roots returns all the root certificates for the CA.
func Roots(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error getting roots"))
render.Error(w, errs.ForbiddenErr(err, "error getting roots"))
return
}
@ -432,7 +432,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{roots[i]}
}
render.JSONStatus(w, r, &RootsResponse{
render.JSONStatus(w, &RootsResponse{
Certificates: certs,
}, http.StatusCreated)
}
@ -441,7 +441,7 @@ func Roots(w http.ResponseWriter, r *http.Request) {
func RootsPEM(w http.ResponseWriter, r *http.Request) {
roots, err := mustAuthority(r.Context()).GetRoots()
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
@ -454,7 +454,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
})
if _, err := w.Write(block); err != nil {
log.Error(w, r, err)
log.Error(w, err)
return
}
}
@ -464,7 +464,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) {
func Federation(w http.ResponseWriter, r *http.Request) {
federated, err := mustAuthority(r.Context()).GetFederation()
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error getting federated roots"))
render.Error(w, errs.ForbiddenErr(err, "error getting federated roots"))
return
}
@ -473,7 +473,7 @@ func Federation(w http.ResponseWriter, r *http.Request) {
certs[i] = Certificate{federated[i]}
}
render.JSONStatus(w, r, &FederationResponse{
render.JSONStatus(w, &FederationResponse{
Certificates: certs,
}, http.StatusCreated)
}
@ -565,7 +565,7 @@ func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) {
func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
q := r.URL.Query()
cursor = q.Get("cursor")
if v := q.Get("limit"); v != "" {
if v := q.Get("limit"); len(v) > 0 {
limit, err = strconv.Atoi(v)
if err != nil {
return "", 0, errs.BadRequestErr(err, "limit '%s' is not an integer", v)

@ -189,7 +189,7 @@ type mockAuthority struct {
authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error)
getTLSOptions func() *authority.TLSOptions
root func(shasum string) (*x509.Certificate, error)
signWithContext func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
renewContext func(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
@ -200,7 +200,7 @@ type mockAuthority struct {
getEncryptedKey func(kid string) (string, error)
getRoots func() ([]*x509.Certificate, error)
getFederation func() ([]*x509.Certificate, error)
getCRL func() (*authority.CertificateRevocationListInfo, error)
getCRL func() ([]byte, error)
signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
@ -214,12 +214,12 @@ type mockAuthority struct {
version func() authority.Version
}
func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) {
func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) {
if m.getCRL != nil {
return m.getCRL()
}
return m.ret1.(*authority.CertificateRevocationListInfo), m.err
return m.ret1.([]byte), m.err
}
// TODO: remove once Authorize is deprecated.
@ -251,9 +251,9 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
return m.ret1.(*x509.Certificate), m.err
}
func (m *mockAuthority) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.signWithContext != nil {
return m.signWithContext(ctx, cr, opts, signOpts...)
func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.sign != nil {
return m.sign(cr, opts, signOpts...)
}
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
}
@ -789,6 +789,45 @@ func (m *mockProvisioner) AuthorizeSSHRekey(ctx context.Context, token string) (
return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err
}
func Test_CRLGeneration(t *testing.T) {
tests := []struct {
name string
err error
statusCode int
expected []byte
}{
{"empty", nil, http.StatusOK, nil},
}
chiCtx := chi.NewRouteContext()
req := httptest.NewRequest("GET", "http://example.com/crl", http.NoBody)
req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMustAuthority(t, &mockAuthority{ret1: tt.expected, err: tt.err})
w := httptest.NewRecorder()
CRL(w, req)
res := w.Result()
if res.StatusCode != tt.statusCode {
t.Errorf("caHandler.CRL StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("caHandler.Root unexpected error = %v", err)
}
if tt.statusCode == 200 {
if !bytes.Equal(bytes.TrimSpace(body), tt.expected) {
t.Errorf("caHandler.Root CRL = %s, wants %s", body, tt.expected)
}
}
})
}
}
func Test_caHandler_Route(t *testing.T) {
type fields struct {
Authority Authority
@ -884,12 +923,16 @@ func Test_Sign(t *testing.T) {
CsrPEM: CertificateRequest{csr},
OTT: "foobarzar",
})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
invalid, err := json.Marshal(SignRequest{
CsrPEM: CertificateRequest{csr},
OTT: "",
})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
expected1 := []byte(`{"crt":"` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`)
expected2 := []byte(`{"crt":"` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`)

@ -3,32 +3,18 @@ package api
import (
"encoding/pem"
"net/http"
"time"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/errs"
)
// CRL is an HTTP handler that returns the current CRL in DER or PEM format
func CRL(w http.ResponseWriter, r *http.Request) {
crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList()
crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList()
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if crlInfo == nil {
render.Error(w, r, errs.New(http.StatusNotFound, "no CRL available"))
return
}
expires := crlInfo.ExpiresAt
if expires.IsZero() {
expires = time.Now()
}
w.Header().Add("Expires", expires.Format(time.RFC1123))
_, formatAsPEM := r.URL.Query()["pem"]
if formatAsPEM {
w.Header().Add("Content-Type", "application/x-pem-file")
@ -36,11 +22,11 @@ func CRL(w http.ResponseWriter, r *http.Request) {
_ = pem.Encode(w, &pem.Block{
Type: "X509 CRL",
Bytes: crlInfo.Data,
Bytes: crlBytes,
})
} else {
w.Header().Add("Content-Type", "application/pkix-crl")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"")
w.Write(crlInfo.Data)
w.Write(crlBytes)
}
}

@ -1,93 +0,0 @@
package api
import (
"bytes"
"context"
"encoding/pem"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/errs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_CRL(t *testing.T) {
data := []byte{1, 2, 3, 4}
pemData := pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL",
Bytes: data,
})
pemData = bytes.TrimSpace(pemData)
emptyPEMData := pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL",
Bytes: nil,
})
emptyPEMData = bytes.TrimSpace(emptyPEMData)
tests := []struct {
name string
url string
err error
statusCode int
crlInfo *authority.CertificateRevocationListInfo
expectedBody []byte
expectedHeaders http.Header
expectedErrorJSON string
}{
{"ok", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, data, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""},
{"ok/pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, pemData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""},
{"ok/empty", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, nil, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""},
{"ok/empty-pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, emptyPEMData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""},
{"fail/internal", "http://example.com/crl", errs.Wrap(http.StatusInternalServerError, errors.New("failure"), "authority.GetCertificateRevocationList"), http.StatusInternalServerError, nil, nil, http.Header{}, `{"status":500,"message":"The certificate authority encountered an Internal Server Error. Please see the certificate authority logs for more info."}`},
{"fail/nil", "http://example.com/crl", nil, http.StatusNotFound, nil, nil, http.Header{}, `{"status":404,"message":"no CRL available"}`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMustAuthority(t, &mockAuthority{ret1: tt.crlInfo, err: tt.err})
chiCtx := chi.NewRouteContext()
req := httptest.NewRequest("GET", tt.url, http.NoBody)
req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx))
w := httptest.NewRecorder()
CRL(w, req)
res := w.Result()
assert.Equal(t, tt.statusCode, res.StatusCode)
body, err := io.ReadAll(res.Body)
res.Body.Close()
require.NoError(t, err)
if tt.statusCode >= 300 {
assert.JSONEq(t, tt.expectedErrorJSON, string(bytes.TrimSpace(body)))
return
}
// check expected header values
for _, h := range []string{"content-type", "content-disposition"} {
v := tt.expectedHeaders.Get(h)
require.NotEmpty(t, v)
actual := res.Header.Get(h)
assert.Equal(t, v, actual)
}
// check expires header value
assert.NotEmpty(t, res.Header.Get("expires"))
t1, err := time.Parse(time.RFC1123, res.Header.Get("expires"))
if assert.NoError(t, err) {
assert.False(t, t1.IsZero())
}
// check body contents
assert.Equal(t, tt.expectedBody, bytes.TrimSpace(body))
})
}
}

@ -2,7 +2,6 @@
package log
import (
"context"
"fmt"
"net/http"
"os"
@ -10,29 +9,6 @@ import (
"github.com/pkg/errors"
)
type errorLoggerKey struct{}
// ErrorLogger is the function type used to log errors.
type ErrorLogger func(http.ResponseWriter, *http.Request, error)
func (fn ErrorLogger) call(w http.ResponseWriter, r *http.Request, err error) {
if fn == nil {
return
}
fn(w, r, err)
}
// WithErrorLogger returns a new context with the given error logger.
func WithErrorLogger(ctx context.Context, fn ErrorLogger) context.Context {
return context.WithValue(ctx, errorLoggerKey{}, fn)
}
// ErrorLoggerFromContext returns an error logger from the context.
func ErrorLoggerFromContext(ctx context.Context) (fn ErrorLogger) {
fn, _ = ctx.Value(errorLoggerKey{}).(ErrorLogger)
return
}
// StackTracedError is the set of errors implementing the StackTrace function.
//
// Errors implementing this interface have their stack traces logged when passed
@ -51,10 +27,8 @@ type fieldCarrier interface {
// Error adds to the response writer the given error if it implements
// logging.ResponseLogger. If it does not implement it, then writes the error
// using the log package.
func Error(w http.ResponseWriter, r *http.Request, err error) {
ErrorLoggerFromContext(r.Context()).call(w, r, err)
fc, ok := w.(fieldCarrier)
func Error(rw http.ResponseWriter, err error) {
fc, ok := rw.(fieldCarrier)
if !ok {
return
}
@ -77,7 +51,7 @@ func Error(w http.ResponseWriter, r *http.Request, err error) {
// EnabledResponse log the response object if it implements the EnableLogger
// interface.
func EnabledResponse(rw http.ResponseWriter, r *http.Request, v any) {
func EnabledResponse(rw http.ResponseWriter, v any) {
type enableLogger interface {
ToLog() (any, error)
}
@ -85,7 +59,7 @@ func EnabledResponse(rw http.ResponseWriter, r *http.Request, v any) {
if el, ok := v.(enableLogger); ok {
out, err := el.ToLog()
if err != nil {
Error(rw, r, err)
Error(rw, err)
return
}

@ -1,9 +1,6 @@
package log
import (
"bytes"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
@ -30,34 +27,21 @@ func (stackTracedError) StackTrace() pkgerrors.StackTrace {
}
func TestError(t *testing.T) {
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{}))
req := httptest.NewRequest("GET", "/test", http.NoBody)
reqWithLogger := req.WithContext(WithErrorLogger(req.Context(), func(w http.ResponseWriter, r *http.Request, err error) {
if err != nil {
logger.ErrorContext(r.Context(), "request failed", slog.Any("error", err))
}
}))
tests := []struct {
name string
error
rw http.ResponseWriter
r *http.Request
isFieldCarrier bool
isSlogLogger bool
stepDebug bool
expectStackTrace bool
}{
{"noLogger", nil, nil, req, false, false, false, false},
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true},
{"slogWithNoError", nil, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
{"slogWithError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false},
{"noLogger", nil, nil, false, false, false},
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
}
for _, tt := range tests {
@ -68,41 +52,27 @@ func TestError(t *testing.T) {
t.Setenv("STEPDEBUG", "0")
}
Error(tt.rw, tt.r, tt.error)
Error(tt.rw, tt.error)
// return early if test case doesn't use logger
if !tt.isFieldCarrier && !tt.isSlogLogger {
if !tt.isFieldCarrier {
return
}
if tt.isFieldCarrier {
fields := tt.rw.(logging.ResponseLogger).Fields()
// expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil {
assert.Nil(t, fields["error"])
} else {
assert.Same(t, tt.error, fields["error"])
}
fields := tt.rw.(logging.ResponseLogger).Fields()
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
}
// expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil {
assert.Nil(t, fields["error"])
} else {
assert.Same(t, tt.error, fields["error"])
}
if tt.isSlogLogger {
b := buf.Bytes()
if tt.error == nil {
assert.Empty(t, b)
} else if assert.NotEmpty(t, b) {
var m map[string]any
assert.NoError(t, json.Unmarshal(b, &m))
assert.Equal(t, tt.error.Error(), m["error"])
}
buf.Reset()
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
}
})
}

@ -97,7 +97,7 @@ func (s *SCEP) AuthorizeSSHSign(context.Context, string) ([]provisioner.SignOpti
return nil, errDummyImplementation
}
// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates.
func (s *SCEP) AuthorizeSSHRevoke(context.Context, string) error {
return errDummyImplementation

@ -51,7 +51,7 @@ func (e badProtoJSONError) Error() string {
}
// Render implements render.RenderableError for badProtoJSONError
func (e badProtoJSONError) Render(w http.ResponseWriter, r *http.Request) {
func (e badProtoJSONError) Render(w http.ResponseWriter) {
v := struct {
Type string `json:"type"`
Detail string `json:"detail"`
@ -62,5 +62,5 @@ func (e badProtoJSONError) Render(w http.ResponseWriter, r *http.Request) {
// trim the proto prefix for the message
Message: strings.TrimSpace(strings.TrimPrefix(e.Error(), "proto:")),
}
render.JSONStatus(w, r, v, http.StatusBadRequest)
render.JSONStatus(w, v, http.StatusBadRequest)
}

@ -142,8 +142,7 @@ func Test_badProtoJSONError_Render(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/test", http.NoBody)
tt.e.Render(w, r)
tt.e.Render(w)
res := w.Result()
defer res.Body.Close()

@ -29,25 +29,25 @@ func (s *RekeyRequest) Validate() error {
// Rekey is similar to renew except that the certificate will be renewed with new key from csr.
func Rekey(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, r, errs.BadRequest("missing client certificate"))
render.Error(w, errs.BadRequest("missing client certificate"))
return
}
var body RekeyRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
a := mustAuthority(r.Context())
certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey)
if err != nil {
render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -57,7 +57,7 @@ func Rekey(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, r, &SignResponse{
render.JSONStatus(w, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -13,8 +13,8 @@ import (
)
// JSON is shorthand for JSONStatus(w, v, http.StatusOK).
func JSON(w http.ResponseWriter, r *http.Request, v interface{}) {
JSONStatus(w, r, v, http.StatusOK)
func JSON(w http.ResponseWriter, v interface{}) {
JSONStatus(w, v, http.StatusOK)
}
// JSONStatus marshals v into w. It additionally sets the status code of
@ -22,7 +22,7 @@ func JSON(w http.ResponseWriter, r *http.Request, v interface{}) {
//
// JSONStatus sets the Content-Type of w to application/json unless one is
// specified.
func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status int) {
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
setContentTypeUnlessPresent(w, "application/json")
w.WriteHeader(status)
@ -43,7 +43,7 @@ func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status in
}
}
log.EnabledResponse(w, r, v)
log.EnabledResponse(w, v)
}
// ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK).
@ -80,22 +80,22 @@ func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) {
type RenderableError interface {
error
Render(http.ResponseWriter, *http.Request)
Render(http.ResponseWriter)
}
// Error marshals the JSON representation of err to w. In case err implements
// RenderableError its own Render method will be called instead.
func Error(rw http.ResponseWriter, r *http.Request, err error) {
log.Error(rw, r, err)
func Error(w http.ResponseWriter, err error) {
log.Error(w, err)
var re RenderableError
if errors.As(err, &re) {
re.Render(rw, r)
var r RenderableError
if errors.As(err, &r) {
r.Render(w)
return
}
JSONStatus(rw, r, err, statusCodeFromError(err))
JSONStatus(w, err, statusCodeFromError(err))
}
// StatusCodedError is the set of errors that implement the basic StatusCode

@ -18,8 +18,8 @@ import (
func TestJSON(t *testing.T) {
rec := httptest.NewRecorder()
rw := logging.NewResponseLogger(rec)
r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(rw, r, map[string]interface{}{"foo": "bar"})
JSON(rw, map[string]interface{}{"foo": "bar"})
assert.Equal(t, http.StatusOK, rec.Result().StatusCode)
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
@ -64,8 +64,7 @@ func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | js
assert.ErrorAs(t, err, &e)
}()
r := httptest.NewRequest("POST", "/test", http.NoBody)
JSON(httptest.NewRecorder(), r, v)
JSON(httptest.NewRecorder(), v)
}
type renderableError struct {
@ -77,9 +76,10 @@ func (err renderableError) Error() string {
return err.Message
}
func (err renderableError) Render(w http.ResponseWriter, r *http.Request) {
func (err renderableError) Render(w http.ResponseWriter) {
w.Header().Set("Content-Type", "something/custom")
JSONStatus(w, r, err, err.Code)
JSONStatus(w, err, err.Code)
}
type statusedError struct {
@ -116,8 +116,8 @@ func TestError(t *testing.T) {
t.Run(strconv.Itoa(caseIndex), func(t *testing.T) {
rec := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/test", http.NoBody)
Error(rec, r, kase.err)
Error(rec, kase.err)
assert.Equal(t, kase.code, rec.Result().StatusCode)
assert.Equal(t, kase.body, rec.Body.String())

@ -23,20 +23,19 @@ func Renew(w http.ResponseWriter, r *http.Request) {
// Get the leaf certificate from the peer or the token.
cert, token, err := getPeerCertificate(r)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
// The token can be used by RAs to renew a certificate.
if token != "" {
ctx = authority.NewTokenContext(ctx, token)
logOtt(w, token)
}
a := mustAuthority(ctx)
certChain, err := a.RenewContext(ctx, cert, nil)
if err != nil {
render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew"))
render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -46,7 +45,7 @@ func Renew(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, r, &SignResponse{
render.JSONStatus(w, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -57,12 +57,12 @@ func (r *RevokeRequest) Validate() (err error) {
func Revoke(w http.ResponseWriter, r *http.Request) {
var body RevokeRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -78,10 +78,10 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// A token indicates that we are using the api via a provisioner token,
// otherwise it is assumed that the certificate is revoking itself over mTLS.
if body.OTT != "" {
if len(body.OTT) > 0 {
logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
opts.OTT = body.OTT
@ -90,12 +90,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
// the client certificate Serial Number must match the serial number
// being revoked.
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
render.Error(w, r, errs.BadRequest("missing ott or client certificate"))
render.Error(w, errs.BadRequest("missing ott or client certificate"))
return
}
opts.Crt = r.TLS.PeerCertificates[0]
if opts.Crt.SerialNumber.String() != opts.Serial {
render.Error(w, r, errs.BadRequest("serial number in client certificate different than body"))
render.Error(w, errs.BadRequest("serial number in client certificate different than body"))
return
}
// TODO: should probably be checking if the certificate was revoked here.
@ -106,12 +106,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) {
}
if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error revoking certificate"))
render.Error(w, errs.ForbiddenErr(err, "error revoking certificate"))
return
}
logRevoke(w, opts)
render.JSON(w, r, &RevokeResponse{Status: "ok"})
render.JSON(w, &RevokeResponse{Status: "ok"})
}
func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -52,13 +52,13 @@ type SignResponse struct {
func Sign(w http.ResponseWriter, r *http.Request) {
var body SignRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -74,13 +74,13 @@ func Sign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
certChain, err := a.SignWithContext(ctx, body.CsrPEM.CertificateRequest, opts, signOpts...)
certChain, err := a.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error signing certificate"))
render.Error(w, errs.ForbiddenErr(err, "error signing certificate"))
return
}
certChainPEM := certChainToPEM(certChain)
@ -90,7 +90,7 @@ func Sign(w http.ResponseWriter, r *http.Request) {
}
LogCertificate(w, certChain[0])
render.JSONStatus(w, r, &SignResponse{
render.JSONStatus(w, &SignResponse{
ServerPEM: certChainPEM[0],
CaPEM: caPEM,
CertChainPEM: certChainPEM,

@ -253,19 +253,19 @@ type SSHBastionResponse struct {
func SSHSign(w http.ResponseWriter, r *http.Request) {
var body SSHSignRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey"))
return
}
@ -273,7 +273,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if body.AddUserPublicKey != nil {
addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey)
if err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error parsing addUserPublicKey"))
render.Error(w, errs.BadRequestErr(err, "error parsing addUserPublicKey"))
return
}
}
@ -293,13 +293,13 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
cert, err := a.SignSSH(ctx, publicKey, opts, signOpts...)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate"))
return
}
@ -307,7 +307,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil {
addUserCert, err := a.SignSSHAddUser(ctx, addUserPublicKey, cert)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate"))
render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate"))
return
}
addUserCertificate = &SSHCertificate{addUserCert}
@ -320,7 +320,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignIdentityMethod)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
@ -330,16 +330,16 @@ func SSHSign(w http.ResponseWriter, r *http.Request) {
NotAfter: time.Unix(int64(cert.ValidBefore), 0),
})
certChain, err := a.SignWithContext(ctx, cr, provisioner.SignOptions{}, signOpts...)
certChain, err := a.Sign(cr, provisioner.SignOptions{}, signOpts...)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error signing identity certificate"))
render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate"))
return
}
identityCertificate = certChainToPEM(certChain)
}
LogSSHCertificate(w, cert)
render.JSONStatus(w, r, &SSHSignResponse{
render.JSONStatus(w, &SSHSignResponse{
Certificate: SSHCertificate{cert},
AddUserCertificate: addUserCertificate,
IdentityCertificate: identityCertificate,
@ -352,12 +352,12 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHRoots(ctx)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, r, errs.NotFound("no keys found"))
render.Error(w, errs.NotFound("no keys found"))
return
}
@ -369,7 +369,7 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
}
render.JSON(w, r, resp)
render.JSON(w, resp)
}
// SSHFederation is an HTTP handler that returns the federated SSH public keys
@ -378,12 +378,12 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
keys, err := mustAuthority(ctx).GetSSHFederation(ctx)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 {
render.Error(w, r, errs.NotFound("no keys found"))
render.Error(w, errs.NotFound("no keys found"))
return
}
@ -395,7 +395,7 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k})
}
render.JSON(w, r, resp)
render.JSON(w, resp)
}
// SSHConfig is an HTTP handler that returns rendered templates for ssh clients
@ -403,18 +403,18 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) {
func SSHConfig(w http.ResponseWriter, r *http.Request) {
var body SSHConfigRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
ctx := r.Context()
ts, err := mustAuthority(ctx).GetSSHConfig(ctx, body.Type, body.Data)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
@ -425,32 +425,32 @@ func SSHConfig(w http.ResponseWriter, r *http.Request) {
case provisioner.SSHHostCert:
cfg.HostTemplates = ts
default:
render.Error(w, r, errs.InternalServer("it should hot get here"))
render.Error(w, errs.InternalServer("it should hot get here"))
return
}
render.JSON(w, r, cfg)
render.JSON(w, cfg)
}
// SSHCheckHost is the HTTP handler that returns if a hosts certificate exists or not.
func SSHCheckHost(w http.ResponseWriter, r *http.Request) {
var body SSHCheckPrincipalRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
ctx := r.Context()
exists, err := mustAuthority(ctx).CheckSSHHost(ctx, body.Principal, body.Token)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
render.JSON(w, r, &SSHCheckPrincipalResponse{
render.JSON(w, &SSHCheckPrincipalResponse{
Exists: exists,
})
}
@ -465,10 +465,10 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hosts, err := mustAuthority(ctx).GetSSHHosts(ctx, cert)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
render.JSON(w, r, &SSHGetHostsResponse{
render.JSON(w, &SSHGetHostsResponse{
Hosts: hosts,
})
}
@ -477,22 +477,22 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) {
func SSHBastion(w http.ResponseWriter, r *http.Request) {
var body SSHBastionRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
ctx := r.Context()
bastion, err := mustAuthority(ctx).GetSSHBastion(ctx, body.User, body.Hostname)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
render.JSON(w, r, &SSHBastionResponse{
render.JSON(w, &SSHBastionResponse{
Hostname: body.Hostname,
Bastion: bastion,
})

@ -42,19 +42,19 @@ type SSHRekeyResponse struct {
func SSHRekey(w http.ResponseWriter, r *http.Request) {
var body SSHRekeyRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
publicKey, err := ssh.ParsePublicKey(body.PublicKey)
if err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey"))
render.Error(w, errs.BadRequestErr(err, "error parsing publicKey"))
return
}
@ -64,18 +64,18 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
signOpts, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
newCert, err := a.RekeySSH(ctx, oldCert, publicKey, signOpts...)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error rekeying ssh certificate"))
render.Error(w, errs.ForbiddenErr(err, "error rekeying ssh certificate"))
return
}
@ -85,12 +85,12 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, r, &SSHRekeyResponse{
render.JSONStatus(w, &SSHRekeyResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,
}, http.StatusCreated)

@ -40,13 +40,13 @@ type SSHRenewResponse struct {
func SSHRenew(w http.ResponseWriter, r *http.Request) {
var body SSHRenewRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
logOtt(w, body.OTT)
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -56,18 +56,18 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
a := mustAuthority(ctx)
_, err := a.Authorize(ctx, body.OTT)
if err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
newCert, err := a.RenewSSH(ctx, oldCert)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error renewing ssh certificate"))
render.Error(w, errs.ForbiddenErr(err, "error renewing ssh certificate"))
return
}
@ -77,12 +77,12 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
if err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate"))
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
return
}
LogSSHCertificate(w, newCert)
render.JSONStatus(w, r, &SSHSignResponse{
render.JSONStatus(w, &SSHSignResponse{
Certificate: SSHCertificate{newCert},
IdentityCertificate: identity,
}, http.StatusCreated)

@ -51,12 +51,12 @@ func (r *SSHRevokeRequest) Validate() (err error) {
func SSHRevoke(w http.ResponseWriter, r *http.Request) {
var body SSHRevokeRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, errs.BadRequestErr(err, "error reading request body"))
render.Error(w, errs.BadRequestErr(err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -75,18 +75,18 @@ func SSHRevoke(w http.ResponseWriter, r *http.Request) {
logOtt(w, body.OTT)
if _, err := a.Authorize(ctx, body.OTT); err != nil {
render.Error(w, r, errs.UnauthorizedErr(err))
render.Error(w, errs.UnauthorizedErr(err))
return
}
opts.OTT = body.OTT
if err := a.Revoke(ctx, opts); err != nil {
render.Error(w, r, errs.ForbiddenErr(err, "error revoking ssh certificate"))
render.Error(w, errs.ForbiddenErr(err, "error revoking ssh certificate"))
return
}
logSSHRevoke(w, opts)
render.JSON(w, r, &SSHRevokeResponse{Status: "ok"})
render.JSON(w, &SSHRevokeResponse{Status: "ok"})
}
func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) {

@ -325,7 +325,7 @@ func Test_SSHSign(t *testing.T) {
signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) {
return tt.addUserCert, tt.addUserErr
},
signWithContext: func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
sign: func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
return tt.tlsSignCerts, tt.tlsSignErr
},
})

@ -40,12 +40,12 @@ func requireEABEnabled(next http.HandlerFunc) http.HandlerFunc {
acmeProvisioner := prov.GetDetails().GetACME()
if acmeProvisioner == nil {
render.Error(w, r, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName()))
render.Error(w, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName()))
return
}
if !acmeProvisioner.RequireEab {
render.Error(w, r, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName()))
render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName()))
return
}
@ -69,18 +69,18 @@ func NewACMEAdminResponder() ACMEAdminResponder {
}
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
// CreateExternalAccountKey writes the response for the EAB key POST endpoint
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, _ *http.Request) {
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
}
func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey {

@ -90,7 +90,7 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
adm, ok := mustAuthority(r.Context()).LoadAdminByID(id)
if !ok {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType,
render.Error(w, admin.NewError(admin.ErrorNotFoundType,
"admin %s not found", id))
return
}
@ -101,17 +101,17 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) {
func GetAdmins(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r)
if err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params"))
return
}
admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit)
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving paginated admins"))
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
return
}
render.JSON(w, r, &GetAdminsResponse{
render.JSON(w, &GetAdminsResponse{
Admins: admins,
NextCursor: nextCursor,
})
@ -121,19 +121,19 @@ func GetAdmins(w http.ResponseWriter, r *http.Request) {
func CreateAdmin(w http.ResponseWriter, r *http.Request) {
var body CreateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
auth := mustAuthority(r.Context())
p, err := auth.LoadProvisionerByName(body.Provisioner)
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
return
}
adm := &linkedca.Admin{
@ -143,7 +143,7 @@ func CreateAdmin(w http.ResponseWriter, r *http.Request) {
}
// Store to authority collection.
if err := auth.StoreAdmin(r.Context(), adm, p); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error storing admin"))
render.Error(w, admin.WrapErrorISE(err, "error storing admin"))
return
}
@ -155,23 +155,23 @@ func DeleteAdmin(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error deleting admin %s", id))
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
return
}
render.JSON(w, r, &DeleteResponse{Status: "ok"})
render.JSON(w, &DeleteResponse{Status: "ok"})
}
// UpdateAdmin updates an existing admin.
func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
var body UpdateAdminRequest
if err := read.JSON(r.Body, &body); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
return
}
if err := body.Validate(); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -179,7 +179,7 @@ func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(r.Context())
adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error updating admin %s", id))
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id))
return
}

@ -1,6 +1,7 @@
package api
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
@ -19,7 +20,7 @@ import (
func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !mustAuthority(r.Context()).IsAdminAPIEnabled() {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled"))
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled"))
return
}
next(w, r)
@ -31,7 +32,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tok := r.Header.Get("Authorization")
if tok == "" {
render.Error(w, r, admin.NewError(admin.ErrorUnauthorizedType,
render.Error(w, admin.NewError(admin.ErrorUnauthorizedType,
"missing authorization header token"))
return
}
@ -39,7 +40,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc {
ctx := r.Context()
adm, err := mustAuthority(ctx).AuthorizeAdminToken(r, tok)
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -64,13 +65,13 @@ func loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc {
// TODO(hs): distinguish 404 vs. 500
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
prov, err := adminDB.GetProvisioner(ctx, p.GetID())
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving provisioner %s", name))
render.Error(w, admin.WrapErrorISE(err, "error retrieving provisioner %s", name))
return
}
@ -91,7 +92,7 @@ func checkAction(next http.HandlerFunc, supportedInStandalone bool) http.Handler
// when an action is not supported in standalone mode and when
// using a nosql.DB backend, actions are not supported
if _, ok := admin.MustFromContext(r.Context()).(*nosql.DB); ok {
render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType,
render.Error(w, admin.NewError(admin.ErrorNotImplementedType,
"operation not supported in standalone mode"))
return
}
@ -124,16 +125,16 @@ func loadExternalAccountKey(next http.HandlerFunc) http.HandlerFunc {
}
if err != nil {
if acme.IsErrNotFound(err) {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
if errors.Is(err, acme.ErrNotFound) {
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving ACME External Account Key"))
render.Error(w, admin.WrapErrorISE(err, "error retrieving ACME External Account Key"))
return
}
if eak == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found"))
return
}

@ -35,7 +35,7 @@ type PolicyAdminResponder interface {
// policyAdminResponder implements PolicyAdminResponder.
type policyAdminResponder struct{}
// NewPolicyAdminResponder returns a new PolicyAdminResponder.
// NewACMEAdminResponder returns a new PolicyAdminResponder.
func NewPolicyAdminResponder() PolicyAdminResponder {
return &policyAdminResponder{}
}
@ -44,7 +44,7 @@ func NewPolicyAdminResponder() PolicyAdminResponder {
func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -52,12 +52,12 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
authorityPolicy, err := auth.GetAuthorityPolicy(r.Context())
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
@ -68,7 +68,7 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht
func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -77,26 +77,26 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy"))
return
}
if authorityPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "authority already has a policy")
render.Error(w, r, adminErr)
render.Error(w, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
@ -105,11 +105,11 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
var createdPolicy *linkedca.Policy
if createdPolicy, err = auth.CreateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error storing authority policy"))
render.Error(w, admin.WrapErrorISE(err, "error storing authority policy"))
return
}
@ -120,7 +120,7 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -129,25 +129,25 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy"))
render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy"))
return
}
@ -156,11 +156,11 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
var updatedPolicy *linkedca.Policy
if updatedPolicy, err = auth.UpdateAuthorityPolicy(ctx, adm, newPolicy); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error updating authority policy"))
render.Error(w, admin.WrapErrorISE(err, "error updating authority policy"))
return
}
@ -171,7 +171,7 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r
func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -180,35 +180,35 @@ func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r
var ae *admin.Error
if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) {
render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy"))
render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy"))
return
}
if authorityPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist"))
return
}
if err := auth.RemoveAuthorityPolicy(ctx); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error deleting authority policy"))
render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy"))
return
}
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
}
// GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request
func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
@ -219,7 +219,7 @@ func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -227,20 +227,20 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "provisioner %s already has a policy", prov.Name)
render.Error(w, r, adminErr)
render.Error(w, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
@ -248,11 +248,11 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner policy"))
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner policy"))
return
}
@ -263,27 +263,27 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
provisionerPolicy := prov.GetPolicy()
if provisionerPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy"))
return
}
@ -291,11 +291,11 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner policy"))
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner policy"))
return
}
@ -306,13 +306,13 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
prov := linkedca.MustProvisionerFromContext(ctx)
if prov.Policy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist"))
return
}
@ -321,24 +321,24 @@ func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter,
auth := mustAuthority(ctx)
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner policy"))
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy"))
return
}
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
}
func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
@ -348,7 +348,7 @@ func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *
func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -357,20 +357,20 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
eakPolicy := eak.GetPolicy()
if eakPolicy != nil {
adminErr := admin.NewError(admin.ErrorConflictType, "ACME EAK %s already has a policy", eak.Id)
render.Error(w, r, adminErr)
render.Error(w, adminErr)
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
@ -379,7 +379,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
render.Error(w, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
return
}
@ -389,7 +389,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -397,20 +397,20 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
var newPolicy = new(linkedca.Policy)
if err := read.ProtoJSON(r.Body, newPolicy); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
newPolicy.Deduplicate()
if err := validatePolicy(newPolicy); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy"))
return
}
@ -418,7 +418,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
render.Error(w, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
return
}
@ -428,7 +428,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter,
func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := blockLinkedCA(ctx); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -436,7 +436,7 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
eak := linkedca.MustExternalAccountKeyFromContext(ctx)
eakPolicy := eak.GetPolicy()
if eakPolicy == nil {
render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
return
}
@ -446,11 +446,11 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter,
acmeEAK := linkedEAKToCertificates(eak)
acmeDB := acme.MustDatabaseFromContext(ctx)
if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
render.Error(w, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
return
}
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
}
// blockLinkedCA blocks all API operations on linked deployments

@ -38,21 +38,21 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
auth := mustAuthority(ctx)
db := admin.MustFromContext(ctx)
if id != "" {
if len(id) > 0 {
if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return
}
} else {
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
}
prov, err := db.GetProvisioner(ctx, p.GetID())
if err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
render.ProtoJSON(w, prov)
@ -62,17 +62,17 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) {
func GetProvisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := api.ParseCursor(r)
if err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err,
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
"error parsing cursor and limit from query params"))
return
}
p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit)
if err != nil {
render.Error(w, r, errs.InternalServerErr(err))
render.Error(w, errs.InternalServerErr(err))
return
}
render.JSON(w, r, &GetProvisionersResponse{
render.JSON(w, &GetProvisionersResponse{
Provisioners: p,
NextCursor: next,
})
@ -82,24 +82,24 @@ func GetProvisioners(w http.ResponseWriter, r *http.Request) {
func CreateProvisioner(w http.ResponseWriter, r *http.Request) {
var prov = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, prov); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
// TODO: Validate inputs
if err := authority.ValidateClaims(prov.Claims); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
// validate the templates and template data
if err := validateTemplates(prov.X509Template, prov.SshTemplate); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return
}
if err := mustAuthority(r.Context()).StoreProvisioner(r.Context(), prov); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
render.Error(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
return
}
render.ProtoJSONStatus(w, prov, http.StatusCreated)
@ -116,31 +116,31 @@ func DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
auth := mustAuthority(r.Context())
if id != "" {
if len(id) > 0 {
if p, err = auth.LoadProvisionerByID(id); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
return
}
} else {
if p, err = auth.LoadProvisionerByName(name); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
return
}
}
if err := auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
render.Error(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
return
}
render.JSON(w, r, &DeleteResponse{Status: "ok"})
render.JSON(w, &DeleteResponse{Status: "ok"})
}
// UpdateProvisioner updates an existing prov.
func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
var nu = new(linkedca.Provisioner)
if err := read.ProtoJSON(r.Body, nu); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -151,51 +151,51 @@ func UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
p, err := auth.LoadProvisionerByName(name)
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
return
}
old, err := db.GetProvisioner(r.Context(), p.GetID())
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID()))
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID()))
return
}
if nu.Id != old.Id {
render.Error(w, r, admin.NewErrorISE("cannot change provisioner ID"))
render.Error(w, admin.NewErrorISE("cannot change provisioner ID"))
return
}
if nu.Type != old.Type {
render.Error(w, r, admin.NewErrorISE("cannot change provisioner type"))
render.Error(w, admin.NewErrorISE("cannot change provisioner type"))
return
}
if nu.AuthorityId != old.AuthorityId {
render.Error(w, r, admin.NewErrorISE("cannot change provisioner authorityID"))
render.Error(w, admin.NewErrorISE("cannot change provisioner authorityID"))
return
}
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
render.Error(w, r, admin.NewErrorISE("cannot change provisioner createdAt"))
render.Error(w, admin.NewErrorISE("cannot change provisioner createdAt"))
return
}
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
render.Error(w, r, admin.NewErrorISE("cannot change provisioner deletedAt"))
render.Error(w, admin.NewErrorISE("cannot change provisioner deletedAt"))
return
}
// TODO: Validate inputs
if err := authority.ValidateClaims(nu.Claims); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
// validate the templates and template data
if err := validateTemplates(nu.X509Template, nu.SshTemplate); err != nil {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template"))
return
}
if err := auth.UpdateProvisioner(r.Context(), nu); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
render.ProtoJSON(w, nu)

@ -71,28 +71,28 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if err := validateWebhook(newWebhook); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if newWebhook.Secret != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set")
render.Error(w, r, err)
render.Error(w, err)
return
}
if newWebhook.Id != "" {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set")
render.Error(w, r, err)
render.Error(w, err)
return
}
id, err := randutil.UUIDv4()
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook id"))
render.Error(w, admin.WrapErrorISE(err, "error generating webhook id"))
return
}
newWebhook.Id = id
@ -101,14 +101,14 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
for _, wh := range prov.Webhooks {
if wh.Name == newWebhook.Name {
err := admin.NewError(admin.ErrorConflictType, "provisioner %q already has a webhook with the name %q", prov.Name, newWebhook.Name)
render.Error(w, r, err)
render.Error(w, err)
return
}
}
secret, err := randutil.Bytes(64)
if err != nil {
render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook secret"))
render.Error(w, admin.WrapErrorISE(err, "error generating webhook secret"))
return
}
newWebhook.Secret = base64.StdEncoding.EncodeToString(secret)
@ -117,11 +117,11 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner webhook"))
render.Error(w, admin.WrapErrorISE(err, "error creating provisioner webhook"))
return
}
@ -145,21 +145,21 @@ func (war *webhookAdminResponder) DeleteProvisionerWebhook(w http.ResponseWriter
}
}
if !found {
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
return
}
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner webhook"))
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner webhook"))
return
}
render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK)
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
}
func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) {
@ -170,12 +170,12 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
var newWebhook = new(linkedca.Webhook)
if err := read.ProtoJSON(r.Body, newWebhook); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
if err := validateWebhook(newWebhook); err != nil {
render.Error(w, r, err)
render.Error(w, err)
return
}
@ -186,13 +186,13 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
}
if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret {
err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated")
render.Error(w, r, err)
render.Error(w, err)
return
}
newWebhook.Secret = wh.Secret
if newWebhook.Id != "" && newWebhook.Id != wh.Id {
err := admin.NewError(admin.ErrorBadRequestType, "webhook ID cannot be updated")
render.Error(w, r, err)
render.Error(w, err)
return
}
newWebhook.Id = wh.Id
@ -203,17 +203,17 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter
if !found {
msg := fmt.Sprintf("provisioner %q has no webhook with the name %q", prov.Name, newWebhook.Name)
err := admin.NewError(admin.ErrorNotFoundType, msg)
render.Error(w, r, err)
render.Error(w, err)
return
}
if err := auth.UpdateProvisioner(ctx, prov); err != nil {
if isBadRequest(err) {
render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook"))
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook"))
return
}
render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner webhook"))
render.Error(w, admin.WrapErrorISE(err, "error updating provisioner webhook"))
return
}

@ -857,7 +857,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject)
@ -890,7 +890,7 @@ func TestDB_CreateAdmin(t *testing.T) {
var _dba = new(dbAdmin)
assert.FatalError(t, json.Unmarshal(nu, _dba))
assert.True(t, _dba.ID != "" && _dba.ID == string(key))
assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key))
assert.Equals(t, _dba.AuthorityID, adm.AuthorityId)
assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId)
assert.Equals(t, _dba.Subject, adm.Subject)

@ -906,7 +906,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -944,7 +944,7 @@ func TestDB_CreateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -1093,7 +1093,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)
@ -1188,7 +1188,7 @@ func TestDB_UpdateProvisioner(t *testing.T) {
var _dbp = new(dbProvisioner)
assert.FatalError(t, json.Unmarshal(nu, _dbp))
assert.True(t, _dbp.ID != "" && _dbp.ID == string(key))
assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key))
assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId)
assert.Equals(t, _dbp.Type, prov.Type)
assert.Equals(t, _dbp.Name, prov.Name)

@ -205,8 +205,8 @@ func (e *Error) ToLog() (interface{}, error) {
}
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
func (e *Error) Render(w http.ResponseWriter) {
e.Message = e.Err.Error()
render.JSONStatus(w, r, e, e.StatusCode())
render.JSONStatus(w, e, e.StatusCode())
}

@ -62,10 +62,9 @@ type Authority struct {
x509Enforcers []provisioner.CertificateEnforcer
// SCEP CA
scepOptions *scep.Options
validateSCEP bool
scepAuthority *scep.Authority
scepKeyManager provisioner.SCEPKeyManager
scepOptions *scep.Options
validateSCEP bool
scepAuthority *scep.Authority
// SSH CA
sshHostPassword []byte
@ -140,7 +139,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) {
}
}
if a.keyManager != nil {
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}
if !a.skipInit {
@ -169,7 +168,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
}
}
if a.keyManager != nil {
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}
// Validate required options
@ -350,7 +349,7 @@ func (a *Authority) init() error {
return err
}
a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter)
a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter}
}
// Initialize linkedca client if necessary. On a linked RA, the issuer
@ -447,7 +446,6 @@ func (a *Authority) init() error {
return err
}
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
a.intermediateX509Certs = append(a.intermediateX509Certs, resp.IntermediateCertificates...)
}
}
@ -696,42 +694,32 @@ func (a *Authority) init() error {
options := &scep.Options{
Roots: a.rootX509Certs,
Intermediates: a.intermediateX509Certs,
SignerCert: a.intermediateX509Certs[0],
}
// intermediate certificates can be empty in RA mode
if len(a.intermediateX509Certs) > 0 {
options.SignerCert = a.intermediateX509Certs[0]
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
}); err != nil {
return err
}
// attempt to create the (default) SCEP signer if the intermediate
// key is configured.
if a.config.IntermediateKey != "" {
if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: a.password,
}); err != nil {
return err
}
// TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP authority,
// and only instantiate it when required there. Is that possible?
// Also with entering passwords?
// TODO(hs): if moving the logic, try improving the logic for the
// decrypter password too? Right now it needs to be entered multiple
// times; I've observed it to be three times maximum, every time
// the intermediate key is read.
_, isRSAKey := options.Signer.Public().(*rsa.PublicKey)
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSAKey {
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
// only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter
options.DecrypterCert = options.Intermediates[0]
}
// TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP authority,
// and only instantiate it when required there. Is that possible?
// Also with entering passwords?
// TODO(hs): if moving the logic, try improving the logic for the
// decrypter password too? Right now it needs to be entered multiple
// times; I've observed it to be three times maximum, every time
// the intermediate key is read.
_, isRSA := options.Signer.Public().(*rsa.PublicKey)
if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA {
if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: a.password,
}); err == nil {
// only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter
options.DecrypterCert = options.Intermediates[0]
}
}

@ -1,7 +1,6 @@
package authority
import (
"context"
"crypto"
"crypto/rand"
"crypto/sha256"
@ -113,7 +112,7 @@ func TestAuthorityNew(t *testing.T) {
c.Root = []string{"foo"}
return &newTest{
config: c,
err: errors.New(`error reading "foo": no such file or directory`),
err: errors.New("error reading foo: no such file or directory"),
}
},
"fail bad password": func(t *testing.T) *newTest {
@ -131,7 +130,7 @@ func TestAuthorityNew(t *testing.T) {
c.IntermediateCert = "wrong"
return &newTest{
config: c,
err: errors.New(`error reading "wrong": no such file or directory`),
err: errors.New("error reading wrong: no such file or directory"),
}
},
}
@ -415,7 +414,7 @@ func TestNewEmbedded_Sign(t *testing.T) {
csr, err := x509.ParseCertificateRequest(cr)
assert.FatalError(t, err)
cert, err := a.SignWithContext(context.Background(), csr, provisioner.SignOptions{})
cert, err := a.Sign(csr, provisioner.SignOptions{})
assert.FatalError(t, err)
assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames)
assert.Equals(t, crt, cert[1])

@ -1375,7 +1375,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) {
}
generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) {
chain, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...)
chain, err := a.Sign(csr, provisioner.SignOptions{}, opts...)
if err != nil {
t.Fatal(err)
}

@ -203,7 +203,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
// domainToReverseLabels converts a textual domain name like foo.example.com to
// the list of labels in reverse order, e.g. ["com", "example", "foo"].
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
for domain != "" {
for len(domain) > 0 {
if i := strings.LastIndexByte(domain, '.'); i == -1 {
reverseLabels = append(reverseLabels, domain)
domain = ""
@ -316,7 +316,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
} else {
// Atom ("." Atom)*
NextChar:
for in != "" {
for len(in) > 0 {
// atext from RFC 2822, Section 3.2.4
c := in[0]

@ -110,7 +110,7 @@ func newLinkedCAClient(token string) (*linkedCaClient, error) {
tlsConfig.GetClientCertificate = renewer.GetClientCertificate
// Start mTLS client
conn, err := grpc.NewClient(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
conn, err := grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
return nil, errors.Wrapf(err, "error connecting %s", u.Host)
}
@ -478,7 +478,10 @@ func getAuthority(sans []string) (string, error) {
// getRootCertificate creates an insecure majordomo client and returns the
// verified root certificate.
func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) {
conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
//nolint:gosec // used in bootstrap protocol
InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check]
})))
@ -486,7 +489,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
return nil, errors.Wrapf(err, "error connecting %s", endpoint)
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)
@ -528,7 +531,11 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error)
// login creates a new majordomo client with just the root ca pool and returns
// the signed certificate and tls configuration.
func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) {
conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
// Connect to majordomo
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
RootCAs: rootCAs,
})))
@ -537,7 +544,7 @@ func login(authority, token string, csr *x509.CertificateRequest, signer crypto.
}
// Login to get the signed certificate
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)

@ -66,22 +66,6 @@ type instrumentedKeyManager struct {
meter Meter
}
type instrumentedKeyAndDecrypterManager struct {
kms.KeyManager
decrypter kmsapi.Decrypter
meter Meter
}
func newInstrumentedKeyManager(k kms.KeyManager, m Meter) kms.KeyManager {
decrypter, isDecrypter := k.(kmsapi.Decrypter)
switch {
case isDecrypter:
return &instrumentedKeyAndDecrypterManager{&instrumentedKeyManager{k, m}, decrypter, m}
default:
return &instrumentedKeyManager{k, m}
}
}
func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) {
if s, err = i.KeyManager.CreateSigner(req); err == nil {
s = &instrumentedKMSSigner{s, i.meter}
@ -90,10 +74,6 @@ func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (
return
}
func (i *instrumentedKeyAndDecrypterManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (s crypto.Decrypter, err error) {
return i.decrypter.CreateDecrypter(req)
}
type instrumentedKMSSigner struct {
crypto.Signer
meter Meter
@ -105,7 +85,3 @@ func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.
return
}
var _ kms.KeyManager = (*instrumentedKeyManager)(nil)
var _ kms.KeyManager = (*instrumentedKeyAndDecrypterManager)(nil)
var _ kmsapi.Decrypter = (*instrumentedKeyAndDecrypterManager)(nil)

@ -167,15 +167,6 @@ func WithKeyManager(k kms.KeyManager) Option {
}
}
// WithX509CAService allows the consumer to provide an externally implemented
// API implementation of apiv1.CertificateAuthorityService
func WithX509CAService(svc casapi.CertificateAuthorityService) Option {
return func(a *Authority) error {
a.x509CAService = svc
return nil
}
}
// WithX509Signer defines the signer used to sign X509 certificates.
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
return WithX509SignerChain([]*x509.Certificate{crt}, s)
@ -226,16 +217,6 @@ func WithFullSCEPOptions(options *scep.Options) Option {
}
}
// WithSCEPKeyManager defines the key manager used on SCEP provisioners.
//
// This feature is EXPERIMENTAL and might change at any time.
func WithSCEPKeyManager(skm provisioner.SCEPKeyManager) Option {
return func(a *Authority) error {
a.scepKeyManager = skm
return nil
}
}
// WithSSHUserSigner defines the signer used to sign SSH user certificates.
func WithSSHUserSigner(s crypto.Signer) Option {
return func(a *Authority) error {

@ -1,5 +1,4 @@
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/regions-certs.html use RSA format
# default certificate for "other regions"
-----BEGIN CERTIFICATE-----
@ -245,20 +244,4 @@ Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz
M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2
xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle
Rwi/S3BFXISixNx9cILu
-----END CERTIFICATE-----
# certificate for ca-west-1
-----BEGIN CERTIFICATE-----
MIICMzCCAZygAwIBAgIGAYPou9weMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT
AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl
MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMjEwMTgwMTM2
MDlaGA8yMjAxMTAxODAxMzYwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh
c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv
biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK
1kIcG5Q6adBXQM75GldfTSiXl7tn54p10TnspI0ErDdb2B6q2Ji/v4XBVH13ZCMg
qlRHMqV8AWI5iO6gFn2A9sN3AZXTMqwtZeiDdebq3k6Wt7ieYvpXTg0qvgsjQIov
RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB
BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx
EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8
4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2
-----END CERTIFICATE-----

@ -896,5 +896,5 @@ func TestAWS_HardcodedCertificates(t *testing.T) {
assert.True(t, cert.NotAfter.After(time.Now()))
certs = append(certs, cert)
}
assert.Len(t, 15, certs, "expected 15 certificates in aws_certificates.pem")
assert.Len(t, 14, certs, "expected 14 certificates in aws_certificates.pem")
}

@ -234,24 +234,24 @@ func (c *Claimer) IsSSHCAEnabled() bool {
// Validate validates and modifies the Claims with default values.
func (c *Claimer) Validate() error {
var (
minDur = c.MinTLSCertDuration()
maxDur = c.MaxTLSCertDuration()
defDur = c.DefaultTLSCertDuration()
min = c.MinTLSCertDuration()
max = c.MaxTLSCertDuration()
def = c.DefaultTLSCertDuration()
)
switch {
case minDur <= 0:
case min <= 0:
return errors.Errorf("claims: MinTLSCertDuration must be greater than 0")
case maxDur <= 0:
case max <= 0:
return errors.Errorf("claims: MaxTLSCertDuration must be greater than 0")
case defDur <= 0:
case def <= 0:
return errors.Errorf("claims: DefaultTLSCertDuration must be greater than 0")
case maxDur < minDur:
case max < min:
return errors.Errorf("claims: MaxCertDuration cannot be less "+
"than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", maxDur, minDur)
case defDur < minDur:
return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", defDur, minDur)
case maxDur < defDur:
return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", maxDur, defDur)
"than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", max, min)
case def < min:
return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", def, min)
case max < def:
return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", max, def)
default:
return nil
}

@ -125,7 +125,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
}
// Try with azp (OIDC)
if payload.AuthorizedParty != "" {
if len(payload.AuthorizedParty) > 0 {
if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok
}

@ -87,7 +87,7 @@ func (p *JWK) GetType() Type {
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
func (p *JWK) GetEncryptedKey() (string, string, bool) {
return p.Key.KeyID, p.EncryptedKey, p.EncryptedKey != ""
return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0
}
// Init initializes and validates the fields of a JWK type.

@ -105,7 +105,7 @@ func getKeysFromJWKsURI(uri string) (jose.JSONWebKeySet, time.Duration, error) {
func getCacheAge(cacheControl string) time.Duration {
age := defaultCacheAge
if cacheControl != "" {
if len(cacheControl) > 0 {
match := maxAgeRegex.FindAllStringSubmatch(cacheControl, -1)
if len(match) > 0 {
if len(match[0]) == 2 {

@ -10,7 +10,6 @@ import (
"strings"
"github.com/pkg/errors"
kmsapi "go.step.sm/crypto/kms/apiv1"
"golang.org/x/crypto/ssh"
"github.com/smallstep/certificates/errs"
@ -207,13 +206,6 @@ type SSHKeys struct {
HostKeys []ssh.PublicKey
}
// SCEPKeyManager is a KMS interface that combines a KeyManager with a
// Decrypter.
type SCEPKeyManager interface {
kmsapi.KeyManager
kmsapi.Decrypter
}
// Config defines the default parameters used in the initialization of
// provisioners.
type Config struct {
@ -234,8 +226,6 @@ type Config struct {
AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc
// WebhookClient is an http client to use in webhook request
WebhookClient *http.Client
// SCEPKeyManager, if defined, is the interface used by SCEP provisioners.
SCEPKeyManager SCEPKeyManager
}
type provisioner struct {
@ -330,7 +320,7 @@ func (b *base) AuthorizeSSHSign(context.Context, string) ([]SignOption, error) {
return nil, errs.Unauthorized("provisioner.AuthorizeSSHSign not implemented")
}
// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite
// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite
// this method if they will support authorizing tokens for revoking SSH Certificates.
func (b *base) AuthorizeSSHRevoke(context.Context, string) error {
return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented")

@ -15,6 +15,7 @@ import (
"go.step.sm/crypto/kms"
kmsapi "go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/uri"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
@ -58,7 +59,7 @@ type SCEP struct {
encryptionAlgorithm int
challengeValidationController *challengeValidationController
notificationController *notificationController
keyManager SCEPKeyManager
keyManager kmsapi.KeyManager
decrypter crypto.Decrypter
decrypterCertificate *x509.Certificate
signer crypto.Signer
@ -268,38 +269,34 @@ func (s *SCEP) Init(config Config) (err error) {
)
// parse the decrypter key PEM contents if available
if len(s.DecrypterKeyPEM) > 0 {
if decryptionKeyPEM := s.DecrypterKeyPEM; len(decryptionKeyPEM) > 0 {
// try reading the PEM for validation
block, rest := pem.Decode(s.DecrypterKeyPEM)
block, rest := pem.Decode(decryptionKeyPEM)
if len(rest) > 0 {
return errors.New("failed parsing decrypter key: trailing data")
}
if block == nil {
return errors.New("failed parsing decrypter key: no PEM block found")
}
opts := kms.Options{
Type: kmsapi.SoftKMS,
}
km, err := kms.New(context.Background(), opts)
if err != nil {
if s.keyManager, err = kms.New(context.Background(), opts); err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
scepKeyManager, ok := km.(SCEPKeyManager)
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
s.keyManager = scepKeyManager
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKeyPEM: s.DecrypterKeyPEM,
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKeyPEM: decryptionKeyPEM,
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err)
}
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKeyPEM: s.DecrypterKeyPEM, // TODO(hs): support distinct signer key in the future?
SigningKeyPEM: decryptionKeyPEM, // TODO(hs): support distinct signer key in the future?
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
@ -307,44 +304,41 @@ func (s *SCEP) Init(config Config) (err error) {
}
}
if s.DecrypterKeyURI != "" {
kmsType, err := kmsapi.TypeOf(s.DecrypterKeyURI)
if decryptionKeyURI := s.DecrypterKeyURI; len(decryptionKeyURI) > 0 {
u, err := uri.Parse(s.DecrypterKeyURI)
if err != nil {
return fmt.Errorf("failed parsing decrypter key: %w", err)
}
if config.SCEPKeyManager != nil {
s.keyManager = config.SCEPKeyManager
} else {
if kmsType == kmsapi.DefaultKMS {
kmsType = kmsapi.SoftKMS
}
opts := kms.Options{
Type: kmsType,
URI: s.DecrypterKeyURI,
}
km, err := kms.New(context.Background(), opts)
if err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
scepKeyManager, ok := km.(SCEPKeyManager)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
s.keyManager = scepKeyManager
}
// Create decrypter and signer with the same key:
// TODO(hs): support distinct signer key in the future?
if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: s.DecrypterKeyURI,
var kmsType kmsapi.Type
switch {
case u.Scheme != "":
kmsType = kms.Type(u.Scheme)
default:
kmsType = kmsapi.SoftKMS
}
opts := kms.Options{
Type: kmsType,
URI: s.DecrypterKeyURI,
}
if s.keyManager, err = kms.New(context.Background(), opts); err != nil {
return fmt.Errorf("failed initializing kms: %w", err)
}
kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter)
if !ok {
return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type)
}
if kmsType != "softkms" { // TODO(hs): this should likely become more transparent?
decryptionKeyURI = u.Opaque
}
if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: decryptionKeyURI,
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {
return fmt.Errorf("failed creating decrypter: %w", err)
}
if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: s.DecrypterKeyURI,
SigningKey: decryptionKeyURI, // TODO(hs): support distinct signer key in the future?
Password: []byte(s.DecrypterKeyPassword),
PasswordPrompter: kmsapi.NonInteractivePasswordPrompter,
}); err != nil {

@ -2,27 +2,19 @@ package provisioner
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/smallstep/certificates/webhook"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/kms/softkms"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/webhook"
)
func Test_challengeValidationController_Validate(t *testing.T) {
@ -374,270 +366,3 @@ func TestSCEP_ValidateChallenge(t *testing.T) {
})
}
}
func TestSCEP_Init(t *testing.T) {
serialize := func(key crypto.PrivateKey, password string) []byte {
var opts []pemutil.Options
if password == "" {
opts = append(opts, pemutil.WithPasswordPrompt("no password", func(s string) ([]byte, error) {
return nil, nil
}))
} else {
opts = append(opts, pemutil.WithPassword([]byte("password")))
}
block, err := pemutil.Serialize(key, opts...)
require.NoError(t, err)
return pem.EncodeToMemory(block)
}
ca, err := minica.New()
require.NoError(t, err)
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
badKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "SCEP decryptor"},
PublicKey: key.Public(),
})
require.NoError(t, err)
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
})
certPEMWithIntermediate := append(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
}), pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: ca.Intermediate.Raw,
})...)
keyPEM := serialize(key, "password")
keyPEMNoPassword := serialize(key, "")
badKeyPEM := serialize(badKey, "password")
tmp := t.TempDir()
path := filepath.Join(tmp, "rsa.priv")
pathNoPassword := filepath.Join(tmp, "rsa.key")
require.NoError(t, os.WriteFile(path, keyPEM, 0600))
require.NoError(t, os.WriteFile(pathNoPassword, keyPEMNoPassword, 0600))
type args struct {
config Config
}
tests := []struct {
name string
s *SCEP
args args
wantErr bool
}{
{"ok", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEMNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 1,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 1024,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 2,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with uri no password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 3,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"ok with SCEPKeyManager", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2048,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + pathNoPassword,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 4,
}, args{Config{Claims: globalProvisionerClaims, SCEPKeyManager: &softkms.SoftKMS{}}}, false},
{"ok intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: nil,
DecrypterKeyPassword: "",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, false},
{"fail type", &SCEP{
Type: "",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail name", &SCEP{
Type: "SCEP",
Name: "",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail minimumPublicKeyLength", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 2001,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 5,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail negative encryptionAlgorithmIdentifier", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: -1,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: []byte("not a pem"),
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate decode", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: []byte("not a pem"),
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail certificate with intermediate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEMWithIntermediate,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail decrypter password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=missing.key",
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri password", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "softkms:path=" + path,
DecrypterKeyPassword: "badpassword",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail uri type", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyURI: "foo:path=" + path,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail missing certificate", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: nil,
DecrypterKeyPEM: keyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
{"fail key match", &SCEP{
Type: "SCEP",
Name: "scep",
ChallengePassword: "password123",
MinimumPublicKeyLength: 0,
DecrypterCertificate: certPEM,
DecrypterKeyPEM: badKeyPEM,
DecrypterKeyPassword: "password",
EncryptionAlgorithmIdentifier: 0,
}, args{Config{Claims: globalProvisionerClaims}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.s.Init(tt.args.config); (err != nil) != tt.wantErr {
t.Errorf("SCEP.Init() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -369,8 +369,8 @@ type validityValidator struct {
}
// newValidityValidator return a new validity validator.
func newValidityValidator(minDur, maxDur time.Duration) *validityValidator {
return &validityValidator{min: minDur, max: maxDur}
func newValidityValidator(min, max time.Duration) *validityValidator {
return &validityValidator{min: min, max: max}
}
// Valid validates the certificate validity settings (notBefore/notAfter) and

@ -276,14 +276,14 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
return errs.BadRequest("ssh certificate validBefore cannot be before validAfter")
}
var minDur, maxDur time.Duration
var min, max time.Duration
switch cert.CertType {
case ssh.UserCert:
minDur = v.MinUserSSHCertDuration()
maxDur = v.MaxUserSSHCertDuration()
min = v.MinUserSSHCertDuration()
max = v.MaxUserSSHCertDuration()
case ssh.HostCert:
minDur = v.MinHostSSHCertDuration()
maxDur = v.MaxHostSSHCertDuration()
min = v.MinHostSSHCertDuration()
max = v.MaxHostSSHCertDuration()
case 0:
return errs.BadRequest("ssh certificate type has not been set")
default:
@ -295,10 +295,10 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second
switch {
case dur < minDur:
return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, minDur)
case dur > maxDur+opts.Backdate:
return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, maxDur+opts.Backdate)
case dur < min:
return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, min)
case dur > max+opts.Backdate:
return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, max+opts.Backdate)
default:
return nil
}

@ -15,7 +15,6 @@ import (
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/certificates/webhook"
"go.step.sm/linkedca"
@ -37,7 +36,7 @@ type WebhookController struct {
// Enrich fetches data from remote servers and adds returned data to the
// templateData
func (wc *WebhookController) Enrich(ctx context.Context, req *webhook.RequestBody) error {
func (wc *WebhookController) Enrich(req *webhook.RequestBody) error {
if wc == nil {
return nil
}
@ -56,11 +55,7 @@ func (wc *WebhookController) Enrich(ctx context.Context, req *webhook.RequestBod
if !wc.isCertTypeOK(wh) {
continue
}
whCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel() //nolint:gocritic // every request canceled with its own timeout
resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData)
resp, err := wh.Do(wc.client, req, wc.TemplateData)
if err != nil {
return err
}
@ -73,7 +68,7 @@ func (wc *WebhookController) Enrich(ctx context.Context, req *webhook.RequestBod
}
// Authorize checks that all remote servers allow the request
func (wc *WebhookController) Authorize(ctx context.Context, req *webhook.RequestBody) error {
func (wc *WebhookController) Authorize(req *webhook.RequestBody) error {
if wc == nil {
return nil
}
@ -92,11 +87,7 @@ func (wc *WebhookController) Authorize(ctx context.Context, req *webhook.Request
if !wc.isCertTypeOK(wh) {
continue
}
whCtx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel() //nolint:gocritic // every request canceled with its own timeout
resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData)
resp, err := wh.Do(wc.client, req, wc.TemplateData)
if err != nil {
return err
}
@ -132,6 +123,13 @@ type Webhook struct {
} `json:"-"`
}
func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return w.DoWithContext(ctx, client, reqBody, data)
}
func (w *Webhook) DoWithContext(ctx context.Context, client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) {
tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL)
if err != nil {
@ -171,10 +169,6 @@ retry:
return nil, err
}
if requestID, ok := requestid.FromContext(ctx); ok {
req.Header.Set("X-Request-Id", requestID)
}
secret, err := base64.StdEncoding.DecodeString(w.Secret)
if err != nil {
return nil, err

@ -1,7 +1,6 @@
package provisioner
import (
"context"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
@ -9,23 +8,18 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/webhook"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/webhook"
)
func TestWebhookController_isCertTypeOK(t *testing.T) {
@ -98,25 +92,19 @@ func TestWebhookController_isCertTypeOK(t *testing.T) {
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
assert.Equal(t, test.want, test.wc.isCertTypeOK(test.wh))
assert.Equals(t, test.want, test.wc.isCertTypeOK(test.wh))
})
}
}
// withRequestID is a helper that calls into [requestid.NewContext] and returns
// a new context with the requestID added.
func withRequestID(t *testing.T, ctx context.Context, requestID string) context.Context {
t.Helper()
return requestid.NewContext(ctx, requestID)
}
func TestWebhookController_Enrich(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
type test struct {
ctl *WebhookController
ctx context.Context
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
@ -141,7 +129,6 @@ func TestWebhookController_Enrich(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false,
@ -156,7 +143,6 @@ func TestWebhookController_Enrich(t *testing.T) {
},
TemplateData: x509util.TemplateData{},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{
{Allow: true, Data: map[string]any{"role": "bar"}},
@ -180,7 +166,6 @@ func TestWebhookController_Enrich(t *testing.T) {
TemplateData: x509util.TemplateData{},
certType: linkedca.Webhook_X509,
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{
{Allow: true, Data: map[string]any{"role": "bar"}},
@ -200,15 +185,14 @@ func TestWebhookController_Enrich(t *testing.T) {
TemplateData: x509util.TemplateData{},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}},
expectErr: false,
expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}},
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
require.NoError(t, err)
assert.Equal(t, &webhook.X5CCertificate{
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
@ -223,7 +207,6 @@ func TestWebhookController_Enrich(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}},
TemplateData: x509util.TemplateData{},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
@ -238,7 +221,6 @@ func TestWebhookController_Enrich(t *testing.T) {
PublicKey: []byte("bad"),
})},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
@ -250,21 +232,19 @@ func TestWebhookController_Enrich(t *testing.T) {
for i, wh := range test.ctl.webhooks {
var j = i
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "reqID", r.Header.Get("X-Request-ID"))
err := json.NewEncoder(w).Encode(test.responses[j])
require.NoError(t, err)
assert.FatalError(t, err)
}))
// nolint: gocritic // defer in loop isn't a memory leak
defer ts.Close()
wh.URL = ts.URL
}
err := test.ctl.Enrich(test.ctx, test.req)
err := test.ctl.Enrich(test.req)
if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr)
}
assert.Equal(t, test.expectTemplateData, test.ctl.TemplateData)
assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData)
if test.assertRequest != nil {
test.assertRequest(t, test.req)
}
@ -274,11 +254,12 @@ func TestWebhookController_Enrich(t *testing.T) {
func TestWebhookController_Authorize(t *testing.T) {
cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock())
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
type test struct {
ctl *WebhookController
ctx context.Context
req *webhook.RequestBody
responses []*webhook.ResponseBody
expectErr bool
@ -299,7 +280,6 @@ func TestWebhookController_Authorize(t *testing.T) {
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false,
@ -310,7 +290,6 @@ func TestWebhookController_Authorize(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING", CertType: linkedca.Webhook_X509.String()}},
certType: linkedca.Webhook_SSH,
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: false,
@ -321,14 +300,13 @@ func TestWebhookController_Authorize(t *testing.T) {
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: true}},
expectErr: false,
assertRequest: func(t *testing.T, req *webhook.RequestBody) {
key, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
require.NoError(t, err)
assert.Equal(t, &webhook.X5CCertificate{
assert.FatalError(t, err)
assert.Equals(t, &webhook.X5CCertificate{
Raw: cert.Raw,
PublicKey: key,
PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(),
@ -342,7 +320,6 @@ func TestWebhookController_Authorize(t *testing.T) {
client: http.DefaultClient,
webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
@ -355,7 +332,6 @@ func TestWebhookController_Authorize(t *testing.T) {
PublicKey: []byte("bad"),
})},
},
ctx: withRequestID(t, context.Background(), "reqID"),
req: &webhook.RequestBody{},
responses: []*webhook.ResponseBody{{Allow: false}},
expectErr: true,
@ -366,17 +342,15 @@ func TestWebhookController_Authorize(t *testing.T) {
for i, wh := range test.ctl.webhooks {
var j = i
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "reqID", r.Header.Get("X-Request-ID"))
err := json.NewEncoder(w).Encode(test.responses[j])
require.NoError(t, err)
assert.FatalError(t, err)
}))
// nolint: gocritic // defer in loop isn't a memory leak
defer ts.Close()
wh.URL = ts.URL
}
err := test.ctl.Authorize(test.ctx, test.req)
err := test.ctl.Authorize(test.req)
if (err != nil) != test.expectErr {
t.Fatalf("Got err %v, want %v", err, test.expectErr)
}
@ -392,7 +366,6 @@ func TestWebhook_Do(t *testing.T) {
type test struct {
webhook Webhook
dataArg any
requestID string
webhookResponse webhook.ResponseBody
expectPath string
errStatusCode int
@ -402,16 +375,6 @@ func TestWebhook_Do(t *testing.T) {
}
tests := map[string]test{
"ok": {
webhook: Webhook{
ID: "abc123",
Secret: "c2VjcmV0Cg==",
},
requestID: "reqID",
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
},
"ok/no-request-id": {
webhook: Webhook{
ID: "abc123",
Secret: "c2VjcmV0Cg==",
@ -426,7 +389,6 @@ func TestWebhook_Do(t *testing.T) {
Secret: "c2VjcmV0Cg==",
BearerToken: "mytoken",
},
requestID: "reqID",
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
@ -443,7 +405,6 @@ func TestWebhook_Do(t *testing.T) {
Password: "mypass",
},
},
requestID: "reqID",
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
@ -455,8 +416,7 @@ func TestWebhook_Do(t *testing.T) {
URL: "/users/{{ .username }}?region={{ .region }}",
Secret: "c2VjcmV0Cg==",
},
requestID: "reqID",
dataArg: map[string]interface{}{"username": "areed", "region": "central"},
dataArg: map[string]interface{}{"username": "areed", "region": "central"},
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
@ -491,7 +451,6 @@ func TestWebhook_Do(t *testing.T) {
ID: "abc123",
Secret: "c2VjcmV0Cg==",
},
requestID: "reqID",
webhookResponse: webhook.ResponseBody{
Allow: true,
},
@ -504,7 +463,6 @@ func TestWebhook_Do(t *testing.T) {
webhookResponse: webhook.ResponseBody{
Data: map[string]interface{}{"role": "dba"},
},
requestID: "reqID",
errStatusCode: 404,
serverErrMsg: "item not found",
expectErr: errors.New("Webhook server responded with 404"),
@ -513,20 +471,17 @@ func TestWebhook_Do(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tc.requestID != "" {
assert.Equal(t, tc.requestID, r.Header.Get("X-Request-ID"))
}
assert.Equal(t, tc.webhook.ID, r.Header.Get("X-Smallstep-Webhook-ID"))
id := r.Header.Get("X-Smallstep-Webhook-ID")
assert.Equals(t, tc.webhook.ID, id)
sig, err := hex.DecodeString(r.Header.Get("X-Smallstep-Signature"))
assert.NoError(t, err)
assert.FatalError(t, err)
body, err := io.ReadAll(r.Body)
assert.NoError(t, err)
assert.FatalError(t, err)
secret, err := base64.StdEncoding.DecodeString(tc.webhook.Secret)
assert.NoError(t, err)
assert.FatalError(t, err)
h := hmac.New(sha256.New, secret)
h.Write(body)
mac := h.Sum(nil)
@ -535,19 +490,19 @@ func TestWebhook_Do(t *testing.T) {
switch {
case tc.webhook.BearerToken != "":
ah := fmt.Sprintf("Bearer %s", tc.webhook.BearerToken)
assert.Equal(t, ah, r.Header.Get("Authorization"))
assert.Equals(t, ah, r.Header.Get("Authorization"))
case tc.webhook.BasicAuth.Username != "" || tc.webhook.BasicAuth.Password != "":
whReq, err := http.NewRequest("", "", http.NoBody)
require.NoError(t, err)
assert.FatalError(t, err)
whReq.SetBasicAuth(tc.webhook.BasicAuth.Username, tc.webhook.BasicAuth.Password)
ah := whReq.Header.Get("Authorization")
assert.Equal(t, ah, whReq.Header.Get("Authorization"))
assert.Equals(t, ah, whReq.Header.Get("Authorization"))
default:
assert.Equal(t, "", r.Header.Get("Authorization"))
assert.Equals(t, "", r.Header.Get("Authorization"))
}
if tc.expectPath != "" {
assert.Equal(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery)
assert.Equals(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery)
}
if tc.errStatusCode != 0 {
@ -557,33 +512,26 @@ func TestWebhook_Do(t *testing.T) {
reqBody := new(webhook.RequestBody)
err = json.Unmarshal(body, reqBody)
require.NoError(t, err)
assert.FatalError(t, err)
// assert.Equals(t, tc.expectToken, reqBody.Token)
err = json.NewEncoder(w).Encode(tc.webhookResponse)
require.NoError(t, err)
assert.FatalError(t, err)
}))
defer ts.Close()
tc.webhook.URL = ts.URL + tc.webhook.URL
reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr))
require.NoError(t, err)
ctx := context.Background()
if tc.requestID != "" {
ctx = withRequestID(t, ctx, tc.requestID)
}
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
got, err := tc.webhook.DoWithContext(ctx, http.DefaultClient, reqBody, tc.dataArg)
assert.FatalError(t, err)
got, err := tc.webhook.Do(http.DefaultClient, reqBody, tc.dataArg)
if tc.expectErr != nil {
assert.Equal(t, tc.expectErr.Error(), err.Error())
assert.Equals(t, tc.expectErr.Error(), err.Error())
return
}
assert.NoError(t, err)
assert.FatalError(t, err)
assert.Equal(t, &tc.webhookResponse, got)
assert.Equals(t, got, &tc.webhookResponse)
})
}
@ -596,7 +544,7 @@ func TestWebhook_Do(t *testing.T) {
URL: ts.URL,
}
cert, err := tls.LoadX509KeyPair("testdata/certs/foo.crt", "testdata/secrets/foo.key")
require.NoError(t, err)
assert.FatalError(t, err)
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
@ -606,19 +554,12 @@ func TestWebhook_Do(t *testing.T) {
Transport: transport,
}
reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr))
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err = wh.DoWithContext(ctx, client, reqBody, nil)
require.NoError(t, err)
ctx, cancel = context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
assert.FatalError(t, err)
_, err = wh.Do(client, reqBody, nil)
assert.FatalError(t, err)
wh.DisableTLSClientAuth = true
_, err = wh.DoWithContext(ctx, client, reqBody, nil)
require.Error(t, err)
_, err = wh.Do(client, reqBody, nil)
assert.Error(t, err)
})
}

@ -813,7 +813,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
}
tot++
}
if tc.claims.Step.SSH.CertType != "" {
if len(tc.claims.Step.SSH.CertType) > 0 {
assert.Equals(t, tot, 12)
} else {
assert.Equals(t, tot, 10)

@ -201,7 +201,6 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
AuthorizeRenewFunc: a.authorizeRenewFunc,
AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc,
WebhookClient: a.webhookClient,
SCEPKeyManager: a.scepKeyManager,
}, nil
}
@ -426,25 +425,25 @@ func ValidateClaims(c *linkedca.Claims) error {
// ValidateDurations validates the Durations type.
func ValidateDurations(d *linkedca.Durations) error {
var (
err error
minDur, maxDur, def *provisioner.Duration
err error
min, max, def *provisioner.Duration
)
if d.Min != "" {
minDur, err = provisioner.NewDuration(d.Min)
min, err = provisioner.NewDuration(d.Min)
if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min)
}
if minDur.Value() < 0 {
if min.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min)
}
}
if d.Max != "" {
maxDur, err = provisioner.NewDuration(d.Max)
max, err = provisioner.NewDuration(d.Max)
if err != nil {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max)
}
if maxDur.Value() < 0 {
if max.Value() < 0 {
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max)
}
}
@ -457,15 +456,15 @@ func ValidateDurations(d *linkedca.Durations) error {
return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default)
}
}
if d.Min != "" && d.Max != "" && minDur.Value() > maxDur.Value() {
if d.Min != "" && d.Max != "" && min.Value() > max.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max)
}
if d.Min != "" && d.Default != "" && minDur.Value() > def.Value() {
if d.Min != "" && d.Default != "" && min.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default)
}
if d.Default != "" && d.Max != "" && minDur.Value() > def.Value() {
if d.Default != "" && d.Max != "" && min.Value() > def.Value() {
return admin.NewError(admin.ErrorBadRequestType,
"default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max)
}
@ -608,20 +607,20 @@ func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook {
return lwh
}
func durationsToCertificates(d *linkedca.Durations) (minDur, maxDur, def *provisioner.Duration, err error) {
if d.Min != "" {
minDur, err = provisioner.NewDuration(d.Min)
func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) {
if len(d.Min) > 0 {
min, err = provisioner.NewDuration(d.Min)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min)
}
}
if d.Max != "" {
maxDur, err = provisioner.NewDuration(d.Max)
if len(d.Max) > 0 {
max, err = provisioner.NewDuration(d.Max)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max)
}
}
if d.Default != "" {
if len(d.Default) > 0 {
def, err = provisioner.NewDuration(d.Default)
if err != nil {
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default)

@ -149,7 +149,7 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) {
opts, err := a.Authorize(ctx, token)
require.NoError(t, err)
opts = append(opts, extraOpts...)
certs, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...)
certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...)
require.NoError(t, err)
return certs[0]
}

@ -45,7 +45,7 @@ func (a *Authority) GetRoots() ([]*x509.Certificate, error) {
// GetFederation returns all the root certificates in the federation.
// This method implements the Authority interface.
func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) {
a.certificates.Range(func(_, v interface{}) bool {
a.certificates.Range(func(k, v interface{}) bool {
crt, ok := v.(*x509.Certificate)
if !ok {
federation = nil
@ -57,26 +57,3 @@ func (a *Authority) GetFederation() (federation []*x509.Certificate, err error)
})
return
}
// GetIntermediateCertificate return the intermediate certificate that issues
// the leaf certificates in the CA.
//
// This method can return nil if the CA is configured with a Certificate
// Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificate() *x509.Certificate {
if len(a.intermediateX509Certs) > 0 {
return a.intermediateX509Certs[0]
}
return nil
}
// GetIntermediateCertificates returns a list of all intermediate certificates
// configured. The first certificate in the list will be the issuer certificate.
//
// This method can return an empty list or nil if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthorityGetter interface.
func (a *Authority) GetIntermediateCertificates() []*x509.Certificate {
return a.intermediateX509Certs
}

@ -2,18 +2,15 @@ package authority
import (
"crypto/x509"
"crypto/x509/pkix"
"errors"
"net/http"
"reflect"
"testing"
"go.step.sm/crypto/pemutil"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
)
func TestRoot(t *testing.T) {
@ -155,63 +152,3 @@ func TestAuthority_GetFederation(t *testing.T) {
})
}
}
func TestAuthority_GetIntermediateCertificate(t *testing.T) {
ca, err := minica.New(minica.WithRootTemplate(`{
"subject": {{ toJson .Subject }},
"issuer": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": -1
}
}`), minica.WithIntermediateTemplate(`{
"subject": {{ toJson .Subject }},
"keyUsage": ["certSign", "crlSign"],
"basicConstraints": {
"isCA": true,
"maxPathLen": 1
}
}`))
require.NoError(t, err)
signer, err := keyutil.GenerateDefaultSigner()
require.NoError(t, err)
cert, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "MiniCA Intermediate CA 0"},
PublicKey: signer.Public(),
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 0,
})
require.NoError(t, err)
type fields struct {
intermediateX509Certs []*x509.Certificate
}
tests := []struct {
name string
fields fields
want *x509.Certificate
wantSlice []*x509.Certificate
}{
{"ok one", fields{[]*x509.Certificate{ca.Intermediate}}, ca.Intermediate, []*x509.Certificate{ca.Intermediate}},
{"ok multiple", fields{[]*x509.Certificate{cert, ca.Intermediate}}, cert, []*x509.Certificate{cert, ca.Intermediate}},
{"ok empty", fields{[]*x509.Certificate{}}, nil, []*x509.Certificate{}},
{"ok nil", fields{nil}, nil, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Authority{
intermediateX509Certs: tt.fields.intermediateX509Certs,
}
if got := a.GetIntermediateCertificate(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Authority.GetIntermediateCertificate() = %v, want %v", got, tt.want)
}
if got := a.GetIntermediateCertificates(); !reflect.DeepEqual(got, tt.wantSlice) {
t.Errorf("Authority.GetIntermediateCertificates() = %v, want %v", got, tt.wantSlice)
}
})
}
}

@ -152,7 +152,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
return cert, err
}
func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
func (a *Authority) signSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) {
var (
certOptions []sshutil.Option
mods []provisioner.SSHCertModifier
@ -211,7 +211,7 @@ func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisi
}
// Call enriching webhooks
if err := a.callEnrichingWebhooksSSH(ctx, prov, webhookCtl, cr); err != nil {
if err := a.callEnrichingWebhooksSSH(prov, webhookCtl, cr); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("signOptions", signOpts),
@ -284,7 +284,7 @@ func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisi
}
// Send certificate to webhooks for authorization
if err := a.callAuthorizingWebhooksSSH(ctx, prov, webhookCtl, certificate, certTpl); err != nil {
if err := a.callAuthorizingWebhooksSSH(prov, webhookCtl, certificate, certTpl); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"),
)
@ -671,33 +671,35 @@ func (a *Authority) getAddUserCommand(principal string) string {
return strings.ReplaceAll(cmd, "<principal>", principal)
}
func (a *Authority) callEnrichingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) {
func (a *Authority) callEnrichingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) {
if webhookCtl == nil {
return
}
defer func() { a.meter.SSHWebhookEnriched(prov, err) }()
var whEnrichReq *webhook.RequestBody
if whEnrichReq, err = webhook.NewRequestBody(
webhook.WithSSHCertificateRequest(cr),
); err == nil {
err = webhookCtl.Enrich(ctx, whEnrichReq)
err = webhookCtl.Enrich(whEnrichReq)
a.meter.SSHWebhookEnriched(prov, err)
}
return
}
func (a *Authority) callAuthorizingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) {
func (a *Authority) callAuthorizingWebhooksSSH(prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) {
if webhookCtl == nil {
return
}
defer func() { a.meter.SSHWebhookAuthorized(prov, err) }()
var whAuthBody *webhook.RequestBody
if whAuthBody, err = webhook.NewRequestBody(
webhook.WithSSHCertificate(cert, certTpl),
); err == nil {
err = webhookCtl.Authorize(ctx, whAuthBody)
err = webhookCtl.Authorize(whAuthBody)
a.meter.SSHWebhookAuthorized(prov, err)
}
return

@ -59,7 +59,7 @@ var (
)
func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
if def == nil {
return errors.New("default ASN1DN template cannot be nil")
}
@ -91,38 +91,14 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
}
}
// GetX509Signer returns a [crypto.Signer] implementation using the intermediate
// key.
//
// This method can return a [NotImplementedError] if the CA is configured with a
// Certificate Authority Service (CAS) that does not implement the
// CertificateAuthoritySigner interface.
//
// [NotImplementedError]: https://pkg.go.dev/github.com/smallstep/certificates/cas/apiv1#NotImplementedError
func (a *Authority) GetX509Signer() (crypto.Signer, error) {
if s, ok := a.x509CAService.(casapi.CertificateAuthoritySigner); ok {
return s.GetSigner()
}
return nil, casapi.NotImplementedError{}
}
// Sign creates a signed certificate from a certificate signing request. It
// creates a new context.Context, and calls into SignWithContext.
//
// Deprecated: Use authority.SignWithContext with an actual context.Context.
// Sign creates a signed certificate from a certificate signing request.
func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
return a.SignWithContext(context.Background(), csr, signOpts, extraOpts...)
}
// SignWithContext creates a signed certificate from a certificate signing
// request, taking the provided context.Context.
func (a *Authority) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
chain, prov, err := a.signX509(ctx, csr, signOpts, extraOpts...)
chain, prov, err := a.signX509(csr, signOpts, extraOpts...)
a.meter.X509Signed(prov, err)
return chain, err
}
func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) {
func (a *Authority) signX509(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) {
var (
certOptions []x509util.Option
certValidators []provisioner.CertificateValidator
@ -195,7 +171,7 @@ func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest,
}
}
if err := a.callEnrichingWebhooksX509(ctx, prov, webhookCtl, attData, csr); err != nil {
if err := a.callEnrichingWebhooksX509(prov, webhookCtl, attData, csr); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, err.Error()),
errs.WithKeyVal("csr", csr),
@ -289,7 +265,7 @@ func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest,
}
// Send certificate to webhooks for authorization
if err := a.callAuthorizingWebhooksX509(ctx, prov, webhookCtl, crt, leaf, attData); err != nil {
if err := a.callAuthorizingWebhooksX509(prov, webhookCtl, crt, leaf, attData); err != nil {
return nil, prov, errs.ApplyOptions(
errs.ForbiddenErr(err, "error creating certificate"),
opts...,
@ -720,17 +696,9 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn
return a.db.RevokeSSH(rci)
}
// CertificateRevocationListInfo contains a CRL in DER format and associated metadata.
type CertificateRevocationListInfo struct {
Number int64
ExpiresAt time.Time
Duration time.Duration
Data []byte
}
// GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented
// error if the underlying AuthDB does not support CRLs
func (a *Authority) GetCertificateRevocationList() (*CertificateRevocationListInfo, error) {
func (a *Authority) GetCertificateRevocationList() ([]byte, error) {
if !a.config.CRL.IsEnabled() {
return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList")
}
@ -745,12 +713,7 @@ func (a *Authority) GetCertificateRevocationList() (*CertificateRevocationListIn
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList")
}
return &CertificateRevocationListInfo{
Number: crlInfo.Number,
ExpiresAt: crlInfo.ExpiresAt,
Duration: crlInfo.Duration,
Data: crlInfo.DER,
}, nil
return crlInfo.DER, nil
}
// GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the
@ -928,19 +891,10 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
return fatal(err)
}
// Set the cert lifetime as follows:
// i) If the CA is not a StepCAS RA use 24h, else
// ii) if the CA is a StepCAS RA, leave the lifetime empty and
// let the provisioner of the CA decide the lifetime of the RA cert.
var lifetime time.Duration
if casapi.TypeOf(a.x509CAService) != casapi.StepCAS {
lifetime = 24 * time.Hour
}
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: certTpl,
CSR: cr,
Lifetime: lifetime,
Lifetime: 24 * time.Hour,
Backdate: 1 * time.Minute,
IsCAServerCert: true,
})
@ -1019,11 +973,10 @@ func templatingError(err error) error {
return errors.Wrap(cause, "error applying certificate template")
}
func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) {
func (a *Authority) callEnrichingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) {
if webhookCtl == nil {
return
}
defer func() { a.meter.X509WebhookEnriched(prov, err) }()
var attested *webhook.AttestationData
if attData != nil {
@ -1037,17 +990,18 @@ func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisio
webhook.WithX509CertificateRequest(csr),
webhook.WithAttestationData(attested),
); err == nil {
err = webhookCtl.Enrich(ctx, whEnrichReq)
err = webhookCtl.Enrich(whEnrichReq)
a.meter.X509WebhookEnriched(prov, err)
}
return
}
func (a *Authority) callAuthorizingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) {
func (a *Authority) callAuthorizingWebhooksX509(prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) {
if webhookCtl == nil {
return
}
defer func() { a.meter.X509WebhookAuthorized(prov, err) }()
var attested *webhook.AttestationData
if attData != nil {
@ -1061,7 +1015,9 @@ func (a *Authority) callAuthorizingWebhooksX509(ctx context.Context, prov provis
webhook.WithX509Certificate(cert, leaf),
webhook.WithAttestationData(attested),
); err == nil {
err = webhookCtl.Authorize(ctx, whAuthBody)
err = webhookCtl.Authorize(whAuthBody)
a.meter.X509WebhookAuthorized(prov, err)
}
return

@ -15,7 +15,6 @@ import (
"fmt"
"net/http"
"reflect"
"strings"
"testing"
"time"
@ -25,17 +24,15 @@ import (
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/cas/softcas"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/nosql/database"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@ -83,25 +80,25 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts ..
t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
assert.FatalError(t, err)
cr, err := x509util.CreateCertificateRequest(commonName, sans, priv)
require.NoError(t, err)
assert.FatalError(t, err)
template, err := x509util.NewCertificate(cr)
require.NoError(t, err)
assert.FatalError(t, err)
cert := template.GetCertificate()
for _, m := range opts {
switch m := m.(type) {
case provisioner.CertificateModifierFunc:
err = m.Modify(cert, provisioner.SignOptions{})
require.NoError(t, err)
assert.FatalError(t, err)
case signerFunc:
cert, err = m(cert, priv.Public())
require.NoError(t, err)
assert.FatalError(t, err)
default:
require.Fail(t, "", "unknown type %T", m)
t.Fatalf("unknown type %T", m)
}
}
@ -111,36 +108,36 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts ..
func generateRootCertificate(t *testing.T) (*x509.Certificate, crypto.Signer) {
t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
assert.FatalError(t, err)
cr, err := x509util.CreateCertificateRequest("TestRootCA", nil, priv)
require.NoError(t, err)
assert.FatalError(t, err)
data := x509util.CreateTemplateData("TestRootCA", nil)
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
require.NoError(t, err)
assert.FatalError(t, err)
cert := template.GetCertificate()
cert, err = x509util.CreateCertificate(cert, cert, priv.Public(), priv)
require.NoError(t, err)
assert.FatalError(t, err)
return cert, priv
}
func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) {
t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
assert.FatalError(t, err)
cr, err := x509util.CreateCertificateRequest("TestIntermediateCA", nil, priv)
require.NoError(t, err)
assert.FatalError(t, err)
data := x509util.CreateTemplateData("TestIntermediateCA", nil)
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
require.NoError(t, err)
assert.FatalError(t, err)
cert := template.GetCertificate()
cert, err = x509util.CreateCertificate(cert, issuer, priv.Public(), signer)
require.NoError(t, err)
assert.FatalError(t, err)
return cert, priv
}
@ -195,9 +192,9 @@ func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateReques
opt(_csr)
}
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, _csr, priv)
require.NoError(t, err)
assert.FatalError(t, err)
csr, err := x509.ParseCertificateRequest(csrBytes)
require.NoError(t, err)
assert.FatalError(t, err)
return csr
}
@ -224,15 +221,6 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
return hash[:], nil
}
func assertHasPrefix(t *testing.T, s, p string) bool {
if strings.HasPrefix(s, p) {
return true
}
t.Helper()
t.Errorf("%q is not a prefix of %q", p, s)
return false
}
type basicConstraints struct {
IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional,default:-1"`
@ -249,12 +237,12 @@ func (e *testEnforcer) Enforce(cert *x509.Certificate) error {
return nil
}
func TestAuthority_SignWithContext(t *testing.T) {
func TestAuthority_Sign(t *testing.T) {
pub, priv, err := keyutil.GenerateDefaultKeyPair()
require.NoError(t, err)
assert.FatalError(t, err)
a := testAuthority(t)
require.NoError(t, err)
assert.FatalError(t, err)
a.config.AuthorityConfig.Template = &ASN1DN{
Country: "Tazmania",
Organization: "Acme Co",
@ -274,12 +262,12 @@ func TestAuthority_SignWithContext(t *testing.T) {
// Create a token to get test extra opts.
p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK)
key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
require.NoError(t, err)
assert.FatalError(t, err)
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
require.NoError(t, err)
assert.FatalError(t, err)
ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod)
extraOpts, err := a.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
type signTest struct {
auth *Authority
@ -384,9 +372,9 @@ W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5/3i16d5zhuxUoaPTYr
ZYtQ9Ot36qc=
-----END CERTIFICATE REQUEST-----`
block, _ := pem.Decode([]byte(shortRSAKeyPEM))
require.NoError(t, err)
assert.FatalError(t, err)
csr, err := x509.ParseCertificateRequest(block.Bytes)
require.NoError(t, err)
assert.FatalError(t, err)
return &signTest{
auth: a,
@ -425,10 +413,10 @@ ZYtQ9Ot36qc=
X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`},
}
testExtraOpts, err := testAuthority.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -454,10 +442,10 @@ ZYtQ9Ot36qc=
},
}
testExtraOpts, err := testAuthority.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -483,10 +471,10 @@ ZYtQ9Ot36qc=
},
}
testExtraOpts, err := testAuthority.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -504,7 +492,7 @@ ZYtQ9Ot36qc=
aa := testAuthority(t)
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -529,7 +517,7 @@ ZYtQ9Ot36qc=
}))
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -549,7 +537,7 @@ ZYtQ9Ot36qc=
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
fmt.Println(crt.Subject)
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -561,7 +549,7 @@ ZYtQ9Ot36qc=
},
}
engine, err := policy.New(options)
require.NoError(t, err)
assert.FatalError(t, err)
aa.policyEngine = engine
return &signTest{
auth: aa,
@ -610,7 +598,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t)
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -629,7 +617,7 @@ ZYtQ9Ot36qc=
bcExt.Id = asn1.ObjectIdentifier{2, 5, 29, 19}
bcExt.Critical = false
bcExt.Value, err = asn1.Marshal(basicConstraints{IsCA: true, MaxPathLen: 4})
require.NoError(t, err)
assert.FatalError(t, err)
csr := getCSR(t, priv, setExtraExtsCSR([]pkix.Extension{
bcExt,
@ -644,7 +632,7 @@ ZYtQ9Ot36qc=
_a := testAuthority(t)
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -675,10 +663,10 @@ ZYtQ9Ot36qc=
}`},
}
testExtraOpts, err := testAuthority.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -709,10 +697,10 @@ ZYtQ9Ot36qc=
}`},
}
testExtraOpts, err := testAuthority.Authorize(ctx, token)
require.NoError(t, err)
assert.FatalError(t, err)
testAuthority.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -749,7 +737,7 @@ ZYtQ9Ot36qc=
_a.config.AuthorityConfig.Template = &ASN1DN{}
_a.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, pkix.Name{}, crt.Subject)
assert.Equals(t, crt.Subject, pkix.Name{})
return nil
},
}
@ -774,8 +762,8 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, "smallstep test", crt.Subject.CommonName)
assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints)
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
assert.Equals(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"})
return nil
},
}
@ -795,7 +783,7 @@ ZYtQ9Ot36qc=
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
aa.db = &db.MockAuthDB{
MStoreCertificate: func(crt *x509.Certificate) error {
assert.Equal(t, crt.Subject.CommonName, "smallstep test")
assert.Equals(t, crt.Subject.CommonName, "smallstep test")
return nil
},
}
@ -808,7 +796,7 @@ ZYtQ9Ot36qc=
},
}
engine, err := policy.New(options)
require.NoError(t, err)
assert.FatalError(t, err)
aa.policyEngine = engine
return &signTest{
auth: aa,
@ -828,13 +816,13 @@ ZYtQ9Ot36qc=
MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error {
p, ok := prov.(attProvisioner)
if assert.True(t, ok) {
assert.Equal(t, &provisioner.AttestationData{
assert.Equals(t, &provisioner.AttestationData{
PermanentIdentifier: "1234567890",
}, p.AttestationData())
}
if assert.Len(t, certs, 2) {
assert.Equal(t, "smallstep test", certs[0].Subject.CommonName)
assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName)
if assert.Len(t, 2, certs) {
assert.Equals(t, certs[0].Subject.CommonName, "smallstep test")
assert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA")
}
return nil
},
@ -858,50 +846,51 @@ ZYtQ9Ot36qc=
t.Run(name, func(t *testing.T) {
tc := genTestCase(t)
certChain, err := tc.auth.SignWithContext(context.Background(), tc.csr, tc.signOpts, tc.extraOpts...)
certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...)
if err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.csr, ctxErr.Details["csr"])
assert.Equal(t, tc.signOpts, ctxErr.Details["signOptions"])
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["csr"], tc.csr)
assert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts)
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
assert.Equal(t, tc.notBefore, leaf.NotBefore)
assert.Equal(t, tc.notAfter, leaf.NotAfter)
assert.Equals(t, leaf.NotBefore, tc.notBefore)
assert.Equals(t, leaf.NotAfter, tc.notAfter)
tmplt := a.config.AuthorityConfig.Template
if tc.csr.Subject.CommonName == "" {
assert.Equal(t, pkix.Name{}, leaf.Subject)
assert.Equals(t, leaf.Subject, pkix.Name{})
} else {
assert.Equal(t, pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: "smallstep test",
}.String(), leaf.Subject.String())
assert.Equal(t, []string{"test.smallstep.com"}, leaf.DNSNames)
assert.Equals(t, leaf.Subject.String(),
pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: "smallstep test",
}.String())
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"})
}
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equals(t, leaf.Issuer, intermediate.Subject)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
assert.Equals(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
issuer := getDefaultIssuer(a)
subjectKeyID, err := generateSubjectKeyID(pub)
require.NoError(t, err)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
// Verify Provisioner OID
found := 0
@ -911,18 +900,18 @@ ZYtQ9Ot36qc=
found++
val := stepProvisionerASN1{}
_, err := asn1.Unmarshal(ext.Value, &val)
require.NoError(t, err)
assert.Equal(t, provisionerTypeJWK, val.Type)
assert.Equal(t, []byte(p.Name), val.Name)
assert.Equal(t, []byte(p.Key.KeyID), val.CredentialID)
assert.FatalError(t, err)
assert.Equals(t, val.Type, provisionerTypeJWK)
assert.Equals(t, val.Name, []byte(p.Name))
assert.Equals(t, val.CredentialID, []byte(p.Key.KeyID))
// Basic Constraints
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})):
val := basicConstraints{}
_, err := asn1.Unmarshal(ext.Value, &val)
require.NoError(t, err)
assert.FatalError(t, err)
assert.False(t, val.IsCA, false)
assert.Equal(t, val.MaxPathLen, 0)
assert.Equals(t, val.MaxPathLen, 0)
// SAN extension
case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})):
@ -933,11 +922,11 @@ ZYtQ9Ot36qc=
}
}
}
assert.Equal(t, found, 1)
assert.Equals(t, found, 1)
realIntermediate, err := x509.ParseCertificate(issuer.Raw)
require.NoError(t, err)
assert.Equal(t, realIntermediate, intermediate)
assert.Len(t, leaf.Extensions, tc.extensionsCount)
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
assert.Len(t, tc.extensionsCount, leaf.Extensions)
}
}
})
@ -1067,7 +1056,7 @@ func TestAuthority_Renew(t *testing.T) {
for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) {
tc, err := genTestCase()
require.NoError(t, err)
assert.FatalError(t, err)
var certChain []*x509.Certificate
if tc.auth != nil {
@ -1079,19 +1068,19 @@ func TestAuthority_Renew(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String())
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1101,29 +1090,30 @@ func TestAuthority_Renew(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template
assert.Equal(t, tc.cert.RawSubject, leaf.RawSubject)
assert.Equal(t, []string{tmplt.Country}, leaf.Subject.Country)
assert.Equal(t, []string{tmplt.Organization}, leaf.Subject.Organization)
assert.Equal(t, []string{tmplt.Locality}, leaf.Subject.Locality)
assert.Equal(t, []string{tmplt.StreetAddress}, leaf.Subject.StreetAddress)
assert.Equal(t, []string{tmplt.Province}, leaf.Subject.Province)
assert.Equal(t, tmplt.CommonName, leaf.Subject.CommonName)
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
assert.Equals(t, leaf.RawSubject, tc.cert.RawSubject)
assert.Equals(t, leaf.Subject.Country, []string{tmplt.Country})
assert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization})
assert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality})
assert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress})
assert.Equals(t, leaf.Subject.Province, []string{tmplt.Province})
assert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName)
assert.Equals(t, leaf.Issuer, intermediate.Subject)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
assert.Equals(t, leaf.ExtKeyUsage,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey)
require.NoError(t, err)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
// We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber {
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1143,7 +1133,7 @@ func TestAuthority_Renew(t *testing.T) {
}
} else {
// We did change the intermediate before renewing.
assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1171,8 +1161,8 @@ func TestAuthority_Renew(t *testing.T) {
}
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
require.NoError(t, err)
assert.Equal(t, realIntermediate, intermediate)
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
}
}
})
@ -1181,7 +1171,7 @@ func TestAuthority_Renew(t *testing.T) {
func TestAuthority_Rekey(t *testing.T) {
pub, _, err := keyutil.GenerateDefaultKeyPair()
require.NoError(t, err)
assert.FatalError(t, err)
a := testAuthority(t)
a.config.AuthorityConfig.Template = &ASN1DN{
@ -1271,7 +1261,7 @@ func TestAuthority_Rekey(t *testing.T) {
for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) {
tc, err := genTestCase()
require.NoError(t, err)
assert.FatalError(t, err)
var certChain []*x509.Certificate
if tc.auth != nil {
@ -1283,19 +1273,19 @@ func TestAuthority_Rekey(t *testing.T) {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
assert.Nil(t, certChain)
var sc render.StatusCodedError
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String())
}
} else {
leaf := certChain[0]
intermediate := certChain[1]
if assert.Nil(t, tc.err) {
assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore))
assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore))
assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute)))
assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute)))
@ -1305,39 +1295,41 @@ func TestAuthority_Rekey(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template
assert.Equal(t, pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: tmplt.CommonName,
}.String(), leaf.Subject.String())
assert.Equal(t, intermediate.Subject, leaf.Issuer)
assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm)
assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm)
assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage)
assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames)
assert.Equals(t, leaf.Subject.String(),
pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: tmplt.CommonName,
}.String())
assert.Equals(t, leaf.Issuer, intermediate.Subject)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA)
assert.Equals(t, leaf.ExtKeyUsage,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
// Test Public Key and SubjectKeyId
expectedPK := tc.pk
if tc.pk == nil {
expectedPK = cert.PublicKey
}
assert.Equal(t, expectedPK, leaf.PublicKey)
assert.Equals(t, leaf.PublicKey, expectedPK)
subjectKeyID, err := generateSubjectKeyID(expectedPK)
require.NoError(t, err)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
if tc.pk == nil {
assert.Equal(t, cert.SubjectKeyId, leaf.SubjectKeyId)
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
}
// We did not change the intermediate before renewing.
authIssuer := getDefaultIssuer(tc.auth)
if issuer.SerialNumber == authIssuer.SerialNumber {
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1357,7 +1349,7 @@ func TestAuthority_Rekey(t *testing.T) {
}
} else {
// We did change the intermediate before renewing.
assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1385,8 +1377,8 @@ func TestAuthority_Rekey(t *testing.T) {
}
realIntermediate, err := x509.ParseCertificate(authIssuer.Raw)
require.NoError(t, err)
assert.Equal(t, realIntermediate, intermediate)
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
}
}
})
@ -1421,10 +1413,10 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
for name, genTestCase := range tests {
t.Run(name, func(t *testing.T) {
tc, err := genTestCase()
require.NoError(t, err)
assert.FatalError(t, err)
opts := tc.auth.GetTLSOptions()
assert.Equal(t, tc.opts, opts)
assert.Equals(t, opts, tc.opts)
})
}
}
@ -1437,11 +1429,11 @@ func TestAuthority_Revoke(t *testing.T) {
now := time.Now().UTC()
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
require.NoError(t, err)
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
require.NoError(t, err)
assert.FatalError(t, err)
a := testAuthority(t)
@ -1480,7 +1472,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44",
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: a,
@ -1494,9 +1486,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke; no persistence layer configured"),
code: http.StatusNotImplemented,
checkErrDetails: func(err *errs.Error) {
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
assert.Equals(t, err.Details["token"], raw)
assert.Equals(t, err.Details["tokenID"], "44")
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
},
}
},
@ -1520,7 +1512,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44",
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: _a,
@ -1534,9 +1526,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("authority.Revoke: force"),
code: http.StatusInternalServerError,
checkErrDetails: func(err *errs.Error) {
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
assert.Equals(t, err.Details["token"], raw)
assert.Equals(t, err.Details["tokenID"], "44")
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
},
}
},
@ -1560,7 +1552,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44",
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: _a,
@ -1574,9 +1566,9 @@ func TestAuthority_Revoke(t *testing.T) {
err: errors.New("certificate with serial number 'sn' is already revoked"),
code: http.StatusBadRequest,
checkErrDetails: func(err *errs.Error) {
assert.Equal(t, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
assert.Equals(t, err.Details["token"], raw)
assert.Equals(t, err.Details["tokenID"], "44")
assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc")
},
}
},
@ -1599,7 +1591,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44",
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: _a,
ctx: tlsRevokeCtx,
@ -1615,7 +1607,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: _a,
@ -1633,7 +1625,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
require.NoError(t, err)
assert.FatalError(t, err)
// Filter out provisioner extension.
for i, ext := range crt.Extensions {
if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}) {
@ -1658,7 +1650,7 @@ func TestAuthority_Revoke(t *testing.T) {
_a := testAuthority(t, WithDatabase(&db.MockAuthDB{}))
crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt")
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: _a,
@ -1691,7 +1683,7 @@ func TestAuthority_Revoke(t *testing.T) {
ID: "44",
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
return test{
auth: a,
ctx: provisioner.NewContextWithMethod(context.Background(), provisioner.SSHRevokeMethod),
@ -1710,17 +1702,17 @@ func TestAuthority_Revoke(t *testing.T) {
if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil {
if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) {
var sc render.StatusCodedError
require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equal(t, tc.code, sc.StatusCode())
assertHasPrefix(t, err.Error(), tc.err.Error())
assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
var ctxErr *errs.Error
require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equal(t, tc.opts.Serial, ctxErr.Details["serialNumber"])
assert.Equal(t, tc.opts.ReasonCode, ctxErr.Details["reasonCode"])
assert.Equal(t, tc.opts.Reason, ctxErr.Details["reason"])
assert.Equal(t, tc.opts.MTLS, ctxErr.Details["MTLS"])
assert.Equal(t, provisioner.RevokeMethod.String(), ctxErr.Details["context"])
assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error")
assert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial)
assert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode)
assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason)
assert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS)
assert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String())
if tc.checkErrDetails != nil {
tc.checkErrDetails(ctxErr)
@ -1803,9 +1795,9 @@ func TestAuthority_constraints(t *testing.T) {
t.Fatal(err)
}
_, err = auth.SignWithContext(context.Background(), csr, provisioner.SignOptions{}, templateOption)
_, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption)
if (err != nil) != tt.wantErr {
t.Errorf("Authority.SignWithContext() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr)
}
_, err = auth.Renew(cert)
@ -1822,11 +1814,13 @@ func TestAuthority_CRL(t *testing.T) {
validIssuer := "step-cli"
validAudience := testAudiences.Revoke
now := time.Now().UTC()
//
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
require.NoError(t, err)
assert.FatalError(t, err)
//
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
require.NoError(t, err)
assert.FatalError(t, err)
crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod)
@ -1871,7 +1865,7 @@ func TestAuthority_CRL(t *testing.T) {
auth: a,
ctx: crlCtx,
expected: nil,
err: errors.New("authority.GetCertificateRevocationList: not found"),
err: database.ErrNotFound,
}
},
"ok/crl-full": func() test {
@ -1916,7 +1910,7 @@ func TestAuthority_CRL(t *testing.T) {
ID: sn,
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
assert.FatalError(t, err)
err = a.Revoke(crlCtx, &RevokeOptions{
Serial: sn,
ReasonCode: reasonCode,
@ -1924,7 +1918,7 @@ func TestAuthority_CRL(t *testing.T) {
OTT: raw,
})
require.NoError(t, err)
assert.FatalError(t, err)
ex = append(ex, sn)
}
@ -1939,58 +1933,22 @@ func TestAuthority_CRL(t *testing.T) {
for name, f := range tests {
tc := f()
t.Run(name, func(t *testing.T) {
crlInfo, err := tc.auth.GetCertificateRevocationList()
if tc.err != nil {
assert.EqualError(t, err, tc.err.Error())
assert.Nil(t, crlInfo)
return
}
if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil {
crl, parseErr := x509.ParseRevocationList(crlBytes)
if parseErr != nil {
t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil)
return
}
crl, parseErr := x509.ParseRevocationList(crlInfo.Data)
require.NoError(t, parseErr)
var cmpList []string
for _, c := range crl.RevokedCertificates {
cmpList = append(cmpList, c.SerialNumber.String())
}
var cmpList []string
for _, c := range crl.RevokedCertificateEntries {
cmpList = append(cmpList, c.SerialNumber.String())
assert.Equals(t, cmpList, tc.expected)
} else {
assert.NotNil(t, tc.err, err.Error())
}
assert.Equal(t, tc.expected, cmpList)
})
}
}
type notImplementedCAS struct{}
func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
return nil, apiv1.NotImplementedError{}
}
func TestAuthority_GetX509Signer(t *testing.T) {
auth := testAuthority(t)
require.IsType(t, &softcas.SoftCAS{}, auth.x509CAService)
signer := auth.x509CAService.(*softcas.SoftCAS).Signer
require.NotNil(t, signer)
tests := []struct {
name string
authority *Authority
want crypto.Signer
assertion assert.ErrorAssertionFunc
}{
{"ok", auth, signer, assert.NoError},
{"fail", testAuthority(t, WithX509CAService(notImplementedCAS{})), nil, assert.Error},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.authority.GetX509Signer()
tt.assertion(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -1,12 +1,8 @@
package authority
import (
"context"
"github.com/smallstep/certificates/webhook"
)
import "github.com/smallstep/certificates/webhook"
type webhookController interface {
Enrich(context.Context, *webhook.RequestBody) error
Authorize(context.Context, *webhook.RequestBody) error
Enrich(*webhook.RequestBody) error
Authorize(*webhook.RequestBody) error
}

@ -1,8 +1,6 @@
package authority
import (
"context"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/webhook"
)
@ -16,7 +14,7 @@ type mockWebhookController struct {
var _ webhookController = &mockWebhookController{}
func (wc *mockWebhookController) Enrich(context.Context, *webhook.RequestBody) error {
func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error {
for key, data := range wc.respData {
wc.templateData.SetWebhook(key, data)
}
@ -24,6 +22,6 @@ func (wc *mockWebhookController) Enrich(context.Context, *webhook.RequestBody) e
return wc.enrichErr
}
func (wc *mockWebhookController) Authorize(context.Context, *webhook.RequestBody) error {
func (wc *mockWebhookController) Authorize(*webhook.RequestBody) error {
return wc.authorizeErr
}

@ -48,7 +48,6 @@ func NewACMEClient(endpoint string, contact []string, opts ...ClientOption) (*AC
return nil, errors.Wrapf(err, "creating GET request %s failed", endpoint)
}
req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := ac.client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", endpoint)
@ -110,7 +109,6 @@ func (c *ACMEClient) GetNonce() (string, error) {
return "", errors.Wrapf(err, "creating GET request %s failed", c.dir.NewNonce)
}
req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := c.client.Do(req)
if err != nil {
return "", errors.Wrapf(err, "client GET %s failed", c.dir.NewNonce)
@ -190,7 +188,6 @@ func (c *ACMEClient) post(payload []byte, url string, headerOps ...withHeaderOpt
}
req.Header.Set("Content-Type", "application/jose+json")
req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", c.dir.NewOrder)

@ -108,19 +108,19 @@ func TestNewACMEClient(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
switch {
case i == 0:
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
case i == 1:
w.Header().Set("Replay-Nonce", "abc123")
render.JSONStatus(w, r, []byte{}, 200)
render.JSONStatus(w, []byte{}, 200)
i++
default:
w.Header().Set("Location", accLocation)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
}
})
@ -203,10 +203,10 @@ func TestACMEClient_GetNonce(t *testing.T) {
t.Run(name, func(t *testing.T) {
tc := run(t)
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
})
if nonce, err := ac.GetNonce(); err != nil {
@ -310,18 +310,18 @@ func TestACMEClient_post(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -338,7 +338,7 @@ func TestACMEClient_post(t *testing.T) {
assert.Equals(t, hdr.KeyID, ac.kid)
}
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if resp, err := tc.client.post(tc.payload, url, tc.ops...); err != nil {
@ -450,18 +450,18 @@ func TestACMEClient_NewOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -477,7 +477,7 @@ func TestACMEClient_NewOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, norb)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if res, err := ac.NewOrder(norb); err != nil {
@ -572,18 +572,18 @@ func TestACMEClient_GetOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -599,7 +599,7 @@ func TestACMEClient_GetOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if res, err := ac.GetOrder(url); err != nil {
@ -694,18 +694,18 @@ func TestACMEClient_GetAuthz(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -721,7 +721,7 @@ func TestACMEClient_GetAuthz(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if res, err := ac.GetAuthz(url); err != nil {
@ -816,18 +816,18 @@ func TestACMEClient_GetChallenge(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -844,7 +844,7 @@ func TestACMEClient_GetChallenge(t *testing.T) {
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if res, err := ac.GetChallenge(url); err != nil {
@ -939,18 +939,18 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -967,7 +967,7 @@ func TestACMEClient_ValidateChallenge(t *testing.T) {
assert.Equals(t, payload, []byte("{}"))
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if err := ac.ValidateChallenge(url); err != nil {
@ -983,22 +983,22 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
t.Log(r.RequestURI)
t.Log(req.RequestURI)
w.Header().Set("Replay-Nonce", "nonce")
switch r.RequestURI {
switch req.RequestURI {
case "/nonce":
render.JSONStatus(w, r, []byte{}, 200)
render.JSONStatus(w, []byte{}, 200)
return
case "/fail-nonce":
render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
@ -1015,15 +1015,15 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, []byte("the-payload"))
switch r.RequestURI {
switch req.RequestURI {
case "/ok":
render.JSONStatus(w, r, acme.Challenge{
render.JSONStatus(w, acme.Challenge{
Type: "device-attestation-01",
Status: "valid",
Token: "foo",
}, 200)
case "/fail":
render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400)
}
}))
defer srv.Close()
@ -1160,18 +1160,18 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1187,7 +1187,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, payload, frb)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if err := ac.FinalizeOrder(url, csr); err != nil {
@ -1289,18 +1289,18 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1316,7 +1316,7 @@ func TestACMEClient_GetAccountOrders(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, len(payload), 0)
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
})
if res, err := tc.client.GetAccountOrders(); err != nil {
@ -1420,18 +1420,18 @@ func TestACMEClient_GetCertificate(t *testing.T) {
tc := run(t)
i := 0
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header
w.Header().Set("Replay-Nonce", expectedNonce)
if i == 0 {
render.JSONStatus(w, r, tc.r1, tc.rc1)
render.JSONStatus(w, tc.r1, tc.rc1)
i++
return
}
// validate jws request protected headers and body
body, err := io.ReadAll(r.Body)
body, err := io.ReadAll(req.Body)
assert.FatalError(t, err)
jws, err := jose.ParseJWS(string(body))
assert.FatalError(t, err)
@ -1450,7 +1450,7 @@ func TestACMEClient_GetCertificate(t *testing.T) {
if tc.certBytes != nil {
w.Write(tc.certBytes)
} else {
render.JSONStatus(w, r, tc.r2, tc.rc2)
render.JSONStatus(w, tc.r2, tc.rc2)
}
})

@ -204,7 +204,7 @@ func (o *adminOptions) apply(opts []AdminOption) (err error) {
func (o *adminOptions) rawQuery() string {
v := url.Values{}
if o.cursor != "" {
if len(o.cursor) > 0 {
v.Set("cursor", o.cursor)
}
if o.limit > 0 {

@ -87,7 +87,7 @@ func startCAServer(configFile string) (*CA, string, error) {
func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/version" {
render.JSON(w, r, api.VersionResponse{
render.JSON(w, api.VersionResponse{
Version: "test",
RequireClientAuthentication: true,
})
@ -102,7 +102,7 @@ func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Han
}
isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0
if !isMTLS {
render.Error(w, r, errs.Unauthorized("missing peer certificate"))
render.Error(w, errs.Unauthorized("missing peer certificate"))
} else {
next.ServeHTTP(w, r)
}
@ -412,7 +412,7 @@ func TestBootstrapClientServerRotation(t *testing.T) {
//nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("ok"))
}),
}, RequireAndVerifyClientCert())
@ -531,7 +531,7 @@ func TestBootstrapClientServerFederation(t *testing.T) {
//nolint:gosec // insecure test server
server, err := BootstrapServer(context.Background(), token, &http.Server{
Addr: ":0",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("ok"))
}),
}, RequireAndVerifyClientCert(), AddFederationToClientCAs())

@ -26,11 +26,9 @@ import (
"github.com/smallstep/certificates/authority/admin"
adminAPI "github.com/smallstep/certificates/authority/admin/api"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/internal/metrix"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/middleware/requestid"
"github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/scep"
scepAPI "github.com/smallstep/certificates/scep/api"
@ -49,8 +47,6 @@ type options struct {
sshHostPassword []byte
sshUserPassword []byte
database db.AuthDB
x509CAService apiv1.CertificateAuthorityService
tlsConfig *tls.Config
}
func (o *options) apply(opts []Option) {
@ -70,13 +66,6 @@ func WithConfigFile(name string) Option {
}
}
// WithX509CAService provides the x509CAService to be used for signing x509 requests
func WithX509CAService(svc apiv1.CertificateAuthorityService) Option {
return func(o *options) {
o.x509CAService = svc
}
}
// WithPassword sets the given password as the configured password in the CA
// options.
func WithPassword(password []byte) Option {
@ -116,14 +105,6 @@ func WithDatabase(d db.AuthDB) Option {
}
}
// WithTLSConfig sets the TLS configuration to be used by the HTTP(s) server
// spun by step-ca.
func WithTLSConfig(t *tls.Config) Option {
return func(o *options) {
o.tlsConfig = t
}
}
// WithLinkedCAToken sets the token used to authenticate with the linkedca.
func WithLinkedCAToken(token string) Option {
return func(o *options) {
@ -184,13 +165,10 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
opts = append(opts, authority.WithQuietInit())
}
if ca.opts.x509CAService != nil {
opts = append(opts, authority.WithX509CAService(ca.opts.x509CAService))
}
var meter *metrix.Meter
if ca.config.MetricsAddress != "" {
meter = metrix.New()
opts = append(opts, authority.WithMeter(meter))
}
@ -203,20 +181,9 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
}
ca.auth = auth
var tlsConfig *tls.Config
var clientTLSConfig *tls.Config
if ca.opts.tlsConfig != nil {
// try using the tls Configuration supplied by the caller
log.Print("Using tls configuration supplied by the application")
tlsConfig = ca.opts.tlsConfig
clientTLSConfig = ca.opts.tlsConfig
} else {
// default to using the step-ca x509 Signer Interface
log.Print("Building new tls configuration using step-ca x509 Signer Interface")
tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth)
if err != nil {
return nil, err
}
tlsConfig, clientTLSConfig, err := ca.getTLSConfig(auth)
if err != nil {
return nil, err
}
webhookTransport.TLSClientConfig = clientTLSConfig
@ -330,21 +297,15 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
}
// Add logger if configured
var legacyTraceHeader string
if len(cfg.Logger) > 0 {
logger, err := logging.New("ca", cfg.Logger)
if err != nil {
return nil, err
}
legacyTraceHeader = logger.GetTraceHeader()
handler = logger.Middleware(handler)
insecureHandler = logger.Middleware(insecureHandler)
}
// always use request ID middleware; traceHeader is provided for backwards compatibility (for now)
handler = requestid.New(legacyTraceHeader).Middleware(handler)
insecureHandler = requestid.New(legacyTraceHeader).Middleware(insecureHandler)
// Create context with all the necessary values.
baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker)
@ -476,20 +437,6 @@ func (ca *CA) Run() error {
// wait till error occurs; ensures the servers keep listening
err := <-errs
// if the error is not the usual HTTP server closed error, it is
// highly likely that an error occurred when starting one of the
// CA servers, possibly because of a port already being in use or
// some part of the configuration not being correct. This case is
// handled by stopping the CA in its entirety.
if !errors.Is(err, http.ErrServerClosed) {
log.Println("shutting down due to startup error ...")
if stopErr := ca.Stop(); stopErr != nil {
err = fmt.Errorf("failed stopping CA after error occurred: %w: %w", err, stopErr)
} else {
err = fmt.Errorf("stopped CA after error occurred: %w", err)
}
}
wg.Wait()
return err
@ -498,10 +445,7 @@ func (ca *CA) Run() error {
// Stop stops the CA calling to the server Shutdown method.
func (ca *CA) Stop() error {
close(ca.compactStop)
if ca.renewer != nil {
ca.renewer.Stop()
}
ca.renewer.Stop()
if err := ca.auth.Shutdown(); err != nil {
log.Printf("error stopping ca.Authority: %+v\n", err)
}
@ -576,10 +520,7 @@ func (ca *CA) Reload() error {
// 2. Safely shutdown any internal resources (e.g. key manager)
// 3. Replace ca properties
// Do not replace ca.srv
if ca.renewer != nil {
ca.renewer.Stop()
}
ca.renewer.Stop()
ca.auth.CloseForReload()
ca.auth = newCA.auth
ca.config = newCA.config
@ -678,7 +619,7 @@ func (ca *CA) shouldServeSCEPEndpoints() bool {
//nolint:unused // useful for debugging
func dumpRoutes(mux chi.Routes) {
// helpful routine for logging all routes
walkFunc := func(method string, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
fmt.Printf("%s %s\n", method, route)
return nil
}

@ -289,9 +289,6 @@ ZEp7knvU2psWRw==
if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest {
var sign api.SignResponse
assert.FatalError(t, readJSON(body, &sign))
@ -328,7 +325,7 @@ ZEp7knvU2psWRw==
assert.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate)
} else {
err := readError(resp)
err := readError(body)
if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error"))
}
@ -372,9 +369,6 @@ func TestCAProvisioners(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest {
var resp api.ProvisionersResponse
@ -385,7 +379,7 @@ func TestCAProvisioners(t *testing.T) {
assert.FatalError(t, err)
assert.Equals(t, a, b)
} else {
err := readError(resp)
err := readError(body)
if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error"))
}
@ -442,15 +436,12 @@ func TestCAProvisionerEncryptedKey(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest {
var ek api.ProvisionerKeyResponse
assert.FatalError(t, readJSON(body, &ek))
assert.Equals(t, ek.Key, tc.expectedKey)
} else {
err := readError(resp)
err := readError(body)
if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error"))
}
@ -507,15 +498,12 @@ func TestCARoot(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest {
var root api.RootResponse
assert.FatalError(t, readJSON(body, &root))
assert.Equals(t, root.RootPEM.Certificate, rootCrt)
} else {
err := readError(resp)
err := readError(body)
if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error"))
}
@ -653,9 +641,6 @@ func TestCARenew(t *testing.T) {
if assert.Equals(t, rr.Code, tc.status) {
body := &ClosingBuffer{rr.Body}
resp := &http.Response{
Body: body,
}
if rr.Code < http.StatusBadRequest {
var sign api.SignResponse
assert.FatalError(t, readJSON(body, &sign))
@ -688,7 +673,7 @@ func TestCARenew(t *testing.T) {
assert.Equals(t, *sign.TLSOptions, authority.DefaultTLSOptions)
} else {
err := readError(resp)
err := readError(body)
if tc.errMsg == "" {
assert.FatalError(t, errors.New("must validate response error"))
}

@ -27,14 +27,12 @@ import (
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/ca/client"
"github.com/smallstep/certificates/ca/identity"
"github.com/smallstep/certificates/errs"
"go.step.sm/cli-utils/step"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil"
"go.step.sm/crypto/x509util"
"golang.org/x/net/http2"
"google.golang.org/protobuf/encoding/protojson"
@ -85,7 +83,8 @@ func (c *uaClient) GetWithContext(ctx context.Context, u string) (*http.Response
if err != nil {
return nil, errors.Wrapf(err, "create GET %s request failed", u)
}
return c.Do(req)
req.Header.Set("User-Agent", UserAgent)
return c.Client.Do(req)
}
func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) {
@ -98,43 +97,12 @@ func (c *uaClient) PostWithContext(ctx context.Context, u, contentType string, b
return nil, errors.Wrapf(err, "create POST %s request failed", u)
}
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
// requestIDHeader is the header name used for propagating request IDs from
// the CA client to the CA and back again.
const requestIDHeader = "X-Request-Id"
// newRequestID generates a new random UUIDv4 request ID. If it fails,
// the request ID will be the empty string.
func newRequestID() string {
requestID, err := randutil.UUIDv4()
if err != nil {
return ""
}
return requestID
}
// enforceRequestID checks if the X-Request-Id HTTP header is filled. If it's
// empty, the context is searched for a request ID. If that's also empty, a new
// request ID is generated.
func enforceRequestID(r *http.Request) {
if requestID := r.Header.Get(requestIDHeader); requestID == "" {
if reqID, ok := client.RequestIDFromContext(r.Context()); ok {
// TODO(hs): ensure the request ID from the context is fresh, and thus hasn't been
// used before by the client (unless it's a retry for the same request)?
requestID = reqID
} else {
requestID = newRequestID()
}
r.Header.Set(requestIDHeader, requestID)
}
req.Header.Set("User-Agent", UserAgent)
return c.Client.Do(req)
}
func (c *uaClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", UserAgent)
enforceRequestID(req)
return c.Client.Do(req)
}
@ -407,8 +375,8 @@ func getTransportFromSHA256(endpoint, sum string) (http.RoundTripper, error) {
if err != nil {
return nil, err
}
caClient := &Client{endpoint: u}
root, err := caClient.Root(sum)
client := &Client{endpoint: u}
root, err := client.Root(sum)
if err != nil {
return nil, err
}
@ -642,7 +610,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var version api.VersionResponse
if err := readJSON(resp.Body, &version); err != nil {
@ -672,7 +640,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var health api.HealthResponse
if err := readJSON(resp.Body, &health); err != nil {
@ -707,7 +675,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var root api.RootResponse
if err := readJSON(resp.Body, &root); err != nil {
@ -746,7 +714,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil {
@ -769,14 +737,14 @@ func (c *Client) Renew(tr http.RoundTripper) (*api.SignResponse, error) {
func (c *Client) RenewWithContext(ctx context.Context, tr http.RoundTripper) (*api.SignResponse, error) {
var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
httpClient := &http.Client{Transport: tr}
client := &http.Client{Transport: tr}
retry:
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(req)
resp, err := client.Do(req)
if err != nil {
return nil, clientError(err)
}
@ -785,7 +753,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil {
@ -822,7 +790,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil {
@ -846,14 +814,14 @@ func (c *Client) RekeyWithContext(ctx context.Context, req *api.RekeyRequest, tr
return nil, errors.Wrap(err, "error marshaling request")
}
u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"})
httpClient := &http.Client{Transport: tr}
client := &http.Client{Transport: tr}
retry:
httpReq, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewReader(body))
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(httpReq)
resp, err := client.Do(httpReq)
if err != nil {
return nil, clientError(err)
}
@ -862,7 +830,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var sign api.SignResponse
if err := readJSON(resp.Body, &sign); err != nil {
@ -885,16 +853,16 @@ func (c *Client) RevokeWithContext(ctx context.Context, req *api.RevokeRequest,
if err != nil {
return nil, errors.Wrap(err, "error marshaling request")
}
var uaClient *uaClient
var client *uaClient
retry:
if tr != nil {
uaClient = newClient(tr)
client = newClient(tr)
} else {
uaClient = c.client
client = c.client
}
u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"})
resp, err := uaClient.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
resp, err := client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil {
return nil, clientError(err)
}
@ -903,7 +871,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var revoke api.RevokeResponse
if err := readJSON(resp.Body, &revoke); err != nil {
@ -946,7 +914,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var provisioners api.ProvisionersResponse
if err := readJSON(resp.Body, &provisioners); err != nil {
@ -978,7 +946,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var key api.ProvisionerKeyResponse
if err := readJSON(resp.Body, &key); err != nil {
@ -1008,7 +976,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var roots api.RootsResponse
if err := readJSON(resp.Body, &roots); err != nil {
@ -1038,7 +1006,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var federation api.FederationResponse
if err := readJSON(resp.Body, &federation); err != nil {
@ -1072,7 +1040,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var sign api.SSHSignResponse
if err := readJSON(resp.Body, &sign); err != nil {
@ -1106,7 +1074,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var renew api.SSHRenewResponse
if err := readJSON(resp.Body, &renew); err != nil {
@ -1140,7 +1108,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var rekey api.SSHRekeyResponse
if err := readJSON(resp.Body, &rekey); err != nil {
@ -1174,7 +1142,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var revoke api.SSHRevokeResponse
if err := readJSON(resp.Body, &revoke); err != nil {
@ -1204,7 +1172,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var keys api.SSHRootsResponse
if err := readJSON(resp.Body, &keys); err != nil {
@ -1234,7 +1202,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var keys api.SSHRootsResponse
if err := readJSON(resp.Body, &keys); err != nil {
@ -1268,7 +1236,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var cfg api.SSHConfigResponse
if err := readJSON(resp.Body, &cfg); err != nil {
@ -1307,7 +1275,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var check api.SSHCheckPrincipalResponse
if err := readJSON(resp.Body, &check); err != nil {
@ -1336,7 +1304,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var hosts api.SSHGetHostsResponse
if err := readJSON(resp.Body, &hosts); err != nil {
@ -1368,7 +1336,7 @@ retry:
retried = true
goto retry
}
return nil, readError(resp)
return nil, readError(resp.Body)
}
var bastion api.SSHBastionResponse
if err := readJSON(resp.Body, &bastion); err != nil {
@ -1536,13 +1504,12 @@ func readProtoJSON(r io.ReadCloser, m proto.Message) error {
return protojson.Unmarshal(data, m)
}
func readError(r *http.Response) error {
defer r.Body.Close()
func readError(r io.ReadCloser) error {
defer r.Close()
apiErr := new(errs.Error)
if err := json.NewDecoder(r.Body).Decode(apiErr); err != nil {
return fmt.Errorf("failed decoding CA error response: %w", err)
if err := json.NewDecoder(r).Decode(apiErr); err != nil {
return err
}
apiErr.RequestID = r.Header.Get("X-Request-Id")
return apiErr
}

@ -1,18 +0,0 @@
package client
import "context"
type contextKey struct{}
// NewRequestIDContext returns a new context with the given request ID added to the
// context.
func NewRequestIDContext(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, contextKey{}, requestID)
}
// RequestIDFromContext returns the request ID from the context if it exists.
// and is not empty.
func RequestIDFromContext(ctx context.Context) (string, bool) {
v, ok := ctx.Value(contextKey{}).(string)
return v, ok && v != ""
}

File diff suppressed because it is too large Load Diff

@ -7,8 +7,6 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
@ -43,12 +41,14 @@ func getTestProvisioner(t *testing.T, caURL string) *Provisioner {
}
func TestNewProvisioner(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
want := getTestProvisioner(t, ca.URL)
caBundle, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
type args struct {
name string

@ -69,7 +69,7 @@ func init() {
GetClientCertificate: id.GetClientCertificateFunc(),
},
}
return func(ctx context.Context, _, _ string) (net.Conn, error) {
return func(ctx context.Context, network, address string) (net.Conn, error) {
return d.DialContext(ctx, "tcp", net.JoinHostPort(host, port))
}
}

@ -10,8 +10,6 @@ import (
"sort"
"testing"
"github.com/stretchr/testify/require"
"github.com/smallstep/certificates/api"
)
@ -132,7 +130,7 @@ func TestVerifyClientCertIfGiven(t *testing.T) {
//nolint:gosec // test tls config
func TestAddRootCA(t *testing.T) {
cert := parseCertificate(t, rootPEM)
cert := parseCertificate(rootPEM)
pool := x509.NewCertPool()
pool.AddCert(cert)
@ -165,7 +163,7 @@ func TestAddRootCA(t *testing.T) {
//nolint:gosec // test tls config
func TestAddClientCA(t *testing.T) {
cert := parseCertificate(t, rootPEM)
cert := parseCertificate(rootPEM)
pool := x509.NewCertPool()
pool.AddCert(cert)
@ -198,19 +196,25 @@ func TestAddClientCA(t *testing.T) {
//nolint:gosec // test tls config
func TestAddRootsToRootCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
cert := parseCertificate(t, string(root))
cert := parseCertificate(string(root))
pool := x509.NewCertPool()
pool.AddCert(cert)
@ -247,19 +251,25 @@ func TestAddRootsToRootCAs(t *testing.T) {
//nolint:gosec // test tls config
func TestAddRootsToClientCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
cert := parseCertificate(t, string(root))
cert := parseCertificate(string(root))
pool := x509.NewCertPool()
pool.AddCert(cert)
@ -296,23 +306,31 @@ func TestAddRootsToClientCAs(t *testing.T) {
//nolint:gosec // test tls config
func TestAddFederationToRootCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(t, string(federated))
crt1 := parseCertificate(string(root))
crt2 := parseCertificate(string(federated))
pool := x509.NewCertPool()
pool.AddCert(crt1)
pool.AddCert(crt2)
@ -353,23 +371,31 @@ func TestAddFederationToRootCAs(t *testing.T) {
//nolint:gosec // test tls config
func TestAddFederationToClientCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(t, string(federated))
crt1 := parseCertificate(string(root))
crt2 := parseCertificate(string(federated))
pool := x509.NewCertPool()
pool.AddCert(crt1)
pool.AddCert(crt2)
@ -410,19 +436,25 @@ func TestAddFederationToClientCAs(t *testing.T) {
//nolint:gosec // test tls config
func TestAddRootsToCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
cert := parseCertificate(t, string(root))
cert := parseCertificate(string(root))
pool := x509.NewCertPool()
pool.AddCert(cert)
@ -459,23 +491,31 @@ func TestAddRootsToCAs(t *testing.T) {
//nolint:gosec // test tls config
func TestAddFederationToCAs(t *testing.T) {
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport))
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
root, err := os.ReadFile("testdata/secrets/root_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
federated, err := os.ReadFile("testdata/secrets/federated_ca.crt")
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
crt1 := parseCertificate(t, string(root))
crt2 := parseCertificate(t, string(federated))
crt1 := parseCertificate(string(root))
crt2 := parseCertificate(string(federated))
pool := x509.NewCertPool()
pool.AddCert(crt1)
pool.AddCert(crt2)

@ -17,28 +17,27 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil"
)
func generateOTT(t *testing.T, subject string) string {
t.Helper()
func generateOTT(subject string) string {
now := time.Now()
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
require.NoError(t, err)
if err != nil {
panic(err)
}
opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts)
require.NoError(t, err)
if err != nil {
panic(err)
}
id, err := randutil.ASCII(64)
require.NoError(t, err)
if err != nil {
panic(err)
}
cl := struct {
jose.Claims
SANS []string `json:"sans"`
@ -54,8 +53,9 @@ func generateOTT(t *testing.T, subject string) string {
SANS: []string{subject},
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
require.NoError(t, err)
if err != nil {
panic(err)
}
return raw
}
@ -72,28 +72,32 @@ func startTestServer(baseContext context.Context, tlsConfig *tls.Config, handler
return srv
}
func startCATestServer(t *testing.T) *httptest.Server {
func startCATestServer() *httptest.Server {
config, err := authority.LoadConfiguration("testdata/ca.json")
require.NoError(t, err)
if err != nil {
panic(err)
}
ca, err := New(config)
require.NoError(t, err)
if err != nil {
panic(err)
}
// Use a httptest.Server instead
baseContext := buildContext(ca.auth, nil, nil, nil)
srv := startTestServer(baseContext, ca.srv.TLSConfig, ca.srv.Handler)
return srv
}
func sign(t *testing.T, domain string) (*Client, *api.SignResponse, crypto.PrivateKey) {
t.Helper()
srv := startCATestServer(t)
func sign(domain string) (*Client, *api.SignResponse, crypto.PrivateKey) {
srv := startCATestServer()
defer srv.Close()
return signDuration(t, srv, domain, 0)
return signDuration(srv, domain, 0)
}
func signDuration(t *testing.T, srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) {
t.Helper()
req, pk, err := CreateSignRequest(generateOTT(t, domain))
require.NoError(t, err)
func signDuration(srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) {
req, pk, err := CreateSignRequest(generateOTT(domain))
if err != nil {
panic(err)
}
if duration > 0 {
req.NotBefore = api.NewTimeDuration(time.Now())
@ -101,11 +105,13 @@ func signDuration(t *testing.T, srv *httptest.Server, domain string, duration ti
}
client, err := NewClient(srv.URL, WithRootFile("testdata/secrets/root_ca.crt"))
require.NoError(t, err)
if err != nil {
panic(err)
}
sr, err := client.Sign(req)
require.NoError(t, err)
if err != nil {
panic(err)
}
return client, sr, pk
}
@ -139,7 +145,7 @@ func serverHandler(t *testing.T, clientDomain string) http.Handler {
func TestClient_GetServerTLSConfig_http(t *testing.T) {
clientDomain := "test.domain"
client, sr, pk := sign(t, "127.0.0.1")
client, sr, pk := sign("127.0.0.1")
// Create mTLS server
ctx, cancel := context.WithCancel(context.Background())
@ -206,7 +212,7 @@ func TestClient_GetServerTLSConfig_http(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, sr, pk := sign(t, clientDomain)
client, sr, pk := sign(clientDomain)
cli := tt.getClient(t, client, sr, pk)
if cli == nil {
return
@ -240,18 +246,19 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
defer reset()
// Start CA
ca := startCATestServer(t)
ca := startCATestServer()
defer ca.Close()
clientDomain := "test.domain"
client, sr, pk := signDuration(t, ca, "127.0.0.1", 5*time.Second)
client, sr, pk := signDuration(ca, "127.0.0.1", 5*time.Second)
// Start mTLS server
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tlsConfig, err := client.GetServerTLSConfig(ctx, sr, pk)
require.NoError(t, err)
if err != nil {
t.Fatalf("Client.GetServerTLSConfig() error = %v", err)
}
srvMTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain))
defer srvMTLS.Close()
@ -259,26 +266,30 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
ctx, cancel = context.WithCancel(context.Background())
defer cancel()
tlsConfig, err = client.GetServerTLSConfig(ctx, sr, pk, VerifyClientCertIfGiven())
require.NoError(t, err)
if err != nil {
t.Fatalf("Client.GetServerTLSConfig() error = %v", err)
}
srvTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain))
defer srvTLS.Close()
// Transport
client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second)
client, sr, pk = signDuration(ca, clientDomain, 5*time.Second)
tr1, err := client.Transport(context.Background(), sr, pk)
require.NoError(t, err)
if err != nil {
t.Fatalf("Client.Transport() error = %v", err)
}
// Transport with tlsConfig
client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second)
client, sr, pk = signDuration(ca, clientDomain, 5*time.Second)
tlsConfig, err = client.GetClientTLSConfig(context.Background(), sr, pk)
require.NoError(t, err)
if err != nil {
t.Fatalf("Client.GetClientTLSConfig() error = %v", err)
}
tr2 := getDefaultTransport(tlsConfig)
// No client cert
root, err := RootCertificate(sr)
require.NoError(t, err)
if err != nil {
t.Fatalf("RootCertificate() error = %v", err)
}
tlsConfig = getDefaultTLSConfig(sr)
tlsConfig.RootCAs = x509.NewCertPool()
tlsConfig.RootCAs.AddCert(root)
@ -390,13 +401,13 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) {
}
func TestCertificate(t *testing.T) {
cert := parseCertificate(t, certPEM)
cert := parseCertificate(certPEM)
ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: cert},
CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
CertChainPEM: []api.Certificate{
{Certificate: cert},
{Certificate: parseCertificate(t, rootPEM)},
{Certificate: parseCertificate(rootPEM)},
},
}
tests := []struct {
@ -423,12 +434,12 @@ func TestCertificate(t *testing.T) {
}
func TestIntermediateCertificate(t *testing.T) {
intermediate := parseCertificate(t, rootPEM)
intermediate := parseCertificate(rootPEM)
ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
CaPEM: api.Certificate{Certificate: intermediate},
CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(t, certPEM)},
{Certificate: parseCertificate(certPEM)},
{Certificate: intermediate},
},
}
@ -456,24 +467,24 @@ func TestIntermediateCertificate(t *testing.T) {
}
func TestRootCertificateCertificate(t *testing.T) {
root := parseCertificate(t, rootPEM)
root := parseCertificate(rootPEM)
ok := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(t, certPEM)},
{Certificate: parseCertificate(t, rootPEM)},
{Certificate: parseCertificate(certPEM)},
{Certificate: parseCertificate(rootPEM)},
},
TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{
{root, root},
}},
}
noTLS := &api.SignResponse{
ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)},
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
CertChainPEM: []api.Certificate{
{Certificate: parseCertificate(t, certPEM)},
{Certificate: parseCertificate(t, rootPEM)},
{Certificate: parseCertificate(certPEM)},
{Certificate: parseCertificate(rootPEM)},
},
}
tests := []struct {

@ -116,8 +116,7 @@ type GetCertificateAuthorityRequest struct {
// GetCertificateAuthorityResponse is the response that contains
// the root certificate.
type GetCertificateAuthorityResponse struct {
RootCertificate *x509.Certificate
IntermediateCertificates []*x509.Certificate
RootCertificate *x509.Certificate
}
// CreateKeyRequest is the request used to generate a new key using a KMS.

@ -1,7 +1,6 @@
package apiv1
import (
"crypto"
"crypto/x509"
"net/http"
"strings"
@ -27,20 +26,13 @@ type CertificateAuthorityGetter interface {
GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error)
}
// CertificateAuthorityCreator is an interface implemented by a
// CertificateAuthorityCreator is an interface implamented by a
// CertificateAuthorityService that has a method to create a new certificate
// authority.
type CertificateAuthorityCreator interface {
CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error)
}
// CertificateAuthoritySigner is an optional interface implemented by a
// CertificateAuthorityService that has a method that returns a [crypto.Signer]
// using the same key used to issue certificates.
type CertificateAuthoritySigner interface {
GetSigner() (crypto.Signer, error)
}
// SignatureAlgorithmGetter is an optional implementation in a crypto.Signer
// that returns the SignatureAlgorithm to use.
type SignatureAlgorithmGetter interface {
@ -61,8 +53,6 @@ const (
StepCAS = "stepcas"
// VaultCAS is a CertificateAuthorityService using Hasicorp Vault PKI.
VaultCAS = "vaultcas"
// ExternalCAS is a CertificateAuthorityService using an external injected CA implementation
ExternalCAS = "externalcas"
)
// String returns a string from the type. It will always return the lower case
@ -75,14 +65,6 @@ func (t Type) String() string {
return strings.ToLower(string(t))
}
// TypeOf returns the type of the given CertificateAuthorityService.
func TypeOf(c CertificateAuthorityService) Type {
if ct, ok := c.(interface{ Type() Type }); ok {
return ct.Type()
}
return ExternalCAS
}
// NotImplementedError is the type of error returned if an operation is not implemented.
type NotImplementedError struct {
Message string

@ -4,24 +4,6 @@ import (
"testing"
)
type simpleCAS struct{}
func (*simpleCAS) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) {
return nil, NotImplementedError{}
}
func (*simpleCAS) RenewCertificate(req *RenewCertificateRequest) (*RenewCertificateResponse, error) {
return nil, NotImplementedError{}
}
func (*simpleCAS) RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) {
return nil, NotImplementedError{}
}
type fakeCAS struct {
simpleCAS
}
func (*fakeCAS) Type() Type { return SoftCAS }
func TestType_String(t *testing.T) {
tests := []struct {
name string
@ -31,7 +13,6 @@ func TestType_String(t *testing.T) {
{"default", "", "softcas"},
{"SoftCAS", SoftCAS, "softcas"},
{"CloudCAS", CloudCAS, "cloudcas"},
{"ExternalCAS", ExternalCAS, "externalcas"},
{"UnknownCAS", "UnknownCAS", "unknowncas"},
}
for _, tt := range tests {
@ -43,27 +24,6 @@ func TestType_String(t *testing.T) {
}
}
func TestTypeOf(t *testing.T) {
type args struct {
c CertificateAuthorityService
}
tests := []struct {
name string
args args
want Type
}{
{"ok", args{&simpleCAS{}}, ExternalCAS},
{"ok with type", args{&fakeCAS{}}, SoftCAS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := TypeOf(tt.args.c); got != tt.want {
t.Errorf("TypeOf() = %v, want %v", got, tt.want)
}
})
}
}
func TestNotImplementedError_Error(t *testing.T) {
type fields struct {
Message string

@ -154,11 +154,6 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
}, nil
}
// Type returns the type of this CertificateAuthorityService.
func (c *CloudCAS) Type() apiv1.Type {
return apiv1.CloudCAS
}
// GetCertificateAuthority returns the root certificate for the given
// certificate authority. It implements apiv1.CertificateAuthorityGetter
// interface.

@ -248,7 +248,6 @@ func mustParseECKey(t *testing.T, pemKey string) *ecdsa.PrivateKey {
block, _ := pem.Decode([]byte(pemKey))
if block == nil {
t.Fatal("failed to parse key")
return nil
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
@ -444,23 +443,6 @@ func TestNew_real(t *testing.T) {
}
}
func TestCloudCAS_Type(t *testing.T) {
tests := []struct {
name string
want apiv1.Type
}{
{"ok", apiv1.CloudCAS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{}
if got := c.Type(); got != tt.want {
t.Errorf("CloudCAS.Type() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudCAS_GetCertificateAuthority(t *testing.T) {
root := mustParseCertificate(t, testRootCertificate)
type fields struct {
@ -883,7 +865,7 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) {
defer srv.Stop()
// Create fake privateca client
conn, err := grpc.NewClient("localhost", grpc.WithTransportCredentials(insecure.NewCredentials()),
conn, err := grpc.DialContext(context.Background(), "", grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}))

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save