Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFE - provide Enrollment over Secure Transport / EST interface to Dogtag / RFC 7030 to obsolete SCEP #3297

Closed
pki-bot opened this issue Oct 3, 2020 · 1 comment

Comments

@pki-bot
Copy link

pki-bot commented Oct 3, 2020

This issue was migrated from Pagure Issue #3180. Originally filed by msauton (@msauton) on 2020-06-22 19:15:16:

  • Assigned to nobody

RFE - provide Enrollment over Secure Transport / EST interface to Dogtag / RFC 7030 to obsolete SCEP

This is for both RHEL IdM and RHCS, in the pki-core component.

RHEL-8 , Fedora 32+
pki-core-10.x ( 10.8+ ? )

https://bugzilla.redhat.com/1849834

Additional information:

https://github.com/cisco/libest

existing certmonger component bug and ticket:
https://bugzilla.redhat.com/show_bug.cgi?id=1841055
https://pagure.io/certmonger/issue/53

frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 5, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 6, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 6, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 6, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: dogtagpki#3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 6, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit that referenced this issue Jul 6, 2022
PKIService.resolveFormat is used to check a media type, or list of
media types (possibly including wildcards) against the default list
of content types understood and produced or processed by Dogtag.

The current variants compare the parameter against a hardcoded list
of supported content types.  However, it would be useful to also
provide general variants that allow the caller to specify both the
candidate type(s) and the valid/accepted types.  This commit adds
those variants.

Related: #3297
Signed-off-by: Fraser Tweedale <ftweedal@redhat.com>
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 6, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 7, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 7, 2022
Read backend configuration from <instance-dir>/est/backend.conf.
This includes the "class" property which specifies the backend
implementation to use.  Other properties are loaded and made
available to the backend implementation.  In this way, different
implementations may recognise or require different configuration
properties.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Implement the EST service as an integrated part of the CA subsystem.
The various interfaces allow for it to be extracted as a separate
subsystem.  The only "big step" to do that is to implement an
alternative `ESTBackend` (i.e. the bits that actually issue
certificates and retrieve CA certificates)

This is a work in progress with the following caveats:

- No authentication of EST client

- No authorization of enrollment requests

- Hardcoded to use `caIPAserviceCert` profile

- Hardcoded `AuthToken` to issue the certificate as user `ipara`,
  with membership in the `Certificate Management Agents` group.

- Only the `/cacerts` and `/simpleenroll` endpoints have been
  implemented.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
This commit extracts the EST service as a separate Dogtag subsystem,
called "est".  This commit gets the JAR and RPM building cleanly.
However, the following critical features are still to come:

- An "RA" backend unto the CA subsystem (for cert issuance)
- The ESTApplication class (i.e. the Tomcat application itself)
- Deployment capability (e.g. `pki-server est-deploy` command)

These will be provided in subsequent commits.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Implement a stub RA backend that returns 404 (/cacerts) or 500
(/simpleenroll and /simplereenroll).  The behaviour to communicate
with Dogtag CA subsystem will be added in a later commit.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
ProxyRealm seems to be causing issues (realm is null, resulting in
HTTP status 500).  Not yet sure why this is happening but disable it
for now.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Add the RewriteValve to the <Host> configuration in server.xml, and
add a rule to send requests for /.well-known/est/... to the EST
service.

The rules are defined in Catalina/localhost/rewrite.config, which is
shipped as part of the pki-server package, rather than pki-est.
This is because the rewrite rules (as required for this application)
are top-level server configuration.  If the EST server is not
deployed, requests for /.well-known/est/... will still be rewritten,
but will result in 404.

This commit DOES NOT provide upgrade scripts to add the RewriteValue
and rewrite.config on existing deployments.  If it is necessary,
this will be added in a subsequent commit.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Use PKIExceptionMapper to map the various web application exceptions
to the corresponding HTTP response types.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
At least one EST client is known to send requests with HTTP header
`Accept: text/plain`; see
thales-e-security/estclient#5.

This behaviour is dubious.  It is problematic when communicating
with servers/frameworks that have rigid content negotiation
behaviour (such as JAX-RS).  Nevertheless, the EST protocol uses a
narrow range of media types.  The method and path are sufficient to
determine the request and response media types, regardless of
Content-Type and Accept header values.

To tolerate bogus Accept header values, define and apply a
ContainerRequestFilter that detects when the Accept header does not
match any of the response types used in the EST protocol.  If it
detects this condition it removes the Accept header from the
request.

NOTE: the JAX-RS spec is ambiguous as to whether our use of the API
is legal.  Per the spec,
`ContainerRequestContext.getAcceptableMediaTypes()` returns an
IMMUTABLE `List<MediaType>`.  However,
`ContainerRequestContext.getHeaders()` returns a MUTABLE map of
headers.  We are able to delete the Accept header via that map.  It
seems to be a RestEasy implementation detail that
`getAcceptableMediaTypes()` always reads the Accept header afresh
from the mutable map.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Read backend configuration from <instance-dir>/est/backend.conf.
This includes the "class" property which specifies the backend
implementation to use.  Other properties are loaded and made
available to the backend implementation.  In this way, different
implementations may recognise or require different configuration
properties.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 12, 2022
Overwriting the "singleton" ESTEngine.INSTANCE value upon each
instantiation of an ESTInstance is unclean.  It also prevents the
use of multiple EST services within a single Tomcat instance (e.g.
an EST server for multiple DNS domains).

Remove this limitation by maintaining a map of ESTEngine instances
keyed by servlet context path.  The contextInitialized() and
contextDestroyed() event hooks add and remove the current instance
from the map.  The ESTFrontend.getBackend() helper method uses the
application's servlet context path to retrieve the corresponding
ESTEngine instance.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 13, 2022
Overwriting the "singleton" ESTEngine.INSTANCE value upon each
instantiation of an ESTInstance is unclean.  It also prevents the
use of multiple EST services within a single Tomcat instance (e.g.
an EST server for multiple DNS domains).

Remove this limitation by maintaining a map of ESTEngine instances
keyed by servlet context path.  The contextInitialized() and
contextDestroyed() event hooks add and remove the current instance
from the map.  The ESTFrontend.getBackend() helper method uses the
application's servlet context path to retrieve the corresponding
ESTEngine instance.

Part of: dogtagpki#3297
frasertweedale added a commit to frasertweedale/pki that referenced this issue Jul 14, 2022
The LWCA REST API recognises the special identifier "host-authority"
in the getCA method, as well as as a parameter in some other methods
(e.g. nominating the parent CA when creating a new LWCA).  However,
"host-authority" is not recognised when retrieving the certificate
or certificate chain of a CA.  The following resources respond with
400 Bad Request because "host-authority" is not a UUID:

   /ca/rest/authorities/host-authority/(chain|cert)

Update these resources to recognise "host-authority" as referring to
the primary CA in the instance.

As well as being a general improvement to the API, this work was
undertaken specifically to make cert chain retrieval easier for the
EST service.

Related: dogtagpki#3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Implement the /cacerts method in the Dogtag RA backend.

The implementation includes a substantial amount of code to prepare
the configuration for the PKIClient.  Parameters are read from the
ESTBackendConfig following a similar pattern to the ACME PKIIssuer.

The code to actually retrieve the certificate chain from the CA
subsystem is quite succinct.  The AuthorityClient retrieves the
chain as a PEM-encoded PKCS #7 object.  We then parse the response
and extract the certificates.  Nevermind that the EST front-end will
then put those certificates in a new PKCS #7 object and serialise
it.  Fundamentally it is unnecessary work.  But the API is cleaner
and more understandable through the use of proper data types rather
than strings and byte arrays.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Restrict ESTBackend endpoint methods to throwing PKIException.
PKIExceptionMapper will turn PKIException (and its subclasses) into
proper HTTP responses.

Also remove the ESTEnrollResult class which is no longer required.
Enrollment failure details will be conveyed by throwing a [subclass
of] PKIException.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
The recent commit f4aeb60 extracted
the ServletContextListener implementation from the "engine" class
for each existing subsystem.  This commit does likewise for the EST
subsystem.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Following the pattern of the ACME PKIIssuer, implement the issuance
behaviour for the EST /simpleenroll and /simplereenroll endpoints.
The RA account credentials are taken from
<subsystem-dir>/backend.conf, for example:

    class=org.dogtagpki.est.DogtagRABackend
    url=https://f36-0.ipa.test:8443
    profile=estServerCert
    username=estra
    password=4me2Test

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
In ESTFrontend, implement /{label}/simpleenroll, /simplereenroll,
and /{label}/simplereenroll.  Authentication and authorization
remain as TODOs and will be implemented in subsequent commits.

As part of this change, extract the CSR parsing and enrollment
response building to separate static methods.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
The ESTBackend issuing methods currently return a
java.security.cert.X509Certificate.  The ESTFrontend injects the
returned certificate into a JSS CertificateChain object.  Although
that type accepts an X509Certificate, in fact it will throw an
IOException if the value is not a JSS X509CertImpl (which implements
X509Certificate).

To avoid the possibility of a backend implementation returning an
incompatible value, modify the API to explicitly return values of
type X509CertImpl.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Add some commentary explaining explaining why DogtagRABackend has
the same behaviour for simpleenroll() and simplereenroll().  Or to
look at it another way, why there are two different methods, even
though this backend currently has the same behaviour for both
methods.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
The value of the response Content-Type header is derived from the
"@produces" annotation on the service method.  For example:

    @produces("application/pkcs7-mime; smime-type=certs-only")

The JAX-RS machinery then converts this into a value of type
javax.ws.rs.core.MediaType, and it is set as such in the
response headers (a MultivaluedMap<String, Object>).

When serialising the Response, header values are stringified via
types that implement the  RuntimeDelegate.HeaderDelegate<T>
interface, where T is the real type of the header value Object
(e.g. MediaType).  The HeaderDelegate implementations are
supplied by the JAX-RS implementation.  In our case that's
Resteasy, and the class in question:

    public class MediaTypeHeaderDelegate
        implements RuntimeDelegate.HeaderDelegate<MediaType>;

The toString(MediaType type) method provided by this class
prints the media type WITHOUT a space between the subtype
and the parameters.  In the example from the @produces above,
it results in the header value:

    application/pkcs7-mime;smime-type=certs-only

This is a legal production in the HTTP grammar.  From the RFCs
7230 and 7231:

    media-type = type "/" subtype *( OWS ";" OWS parameter )
    OWS = *( SP / HTAB )

However, at least one EST client is unable to process this
value.  libest expects a SPACE after the ';'.  From
src/est/est_client_http.c:

    ...
    } else if (!strncmp(ct, "application/pkcs7-mime; smime-type=certs-only", 45)) {
    ...

The string libest expects is also a valid production.  But it is
not the one being sent from Tomcat/Resteasy.  As a consequence,
the enrollment operation fails.

To make our EST implementation compatible with libest, we need to
override how the MediaType gets stringified.  I was unable to
find a way in the JAX-RS to override the HeaderDelegate.  But we
can solve it in a case-by-case way via this response filter.

At the time response filters are applied, the Content-Type header
value is an object of type MediaType.  If the value is equal
(including parameters) to a value whose serialisation we need
to precisely control, we replace it with the exact String
required.  The String value will be used in the response "as is".
These substitutions are stored in a Map generated from the list
of all verbatim target headers.

If it emerges that different stringifications of the same
MediaType value are required for different client
implementations, we could inspect the request User-Agent header
to further refine the behaviour.  We could, for example, create a
Map for each User-Agent that requires header substitutions, or
change the key of the map to the User-Agent×MediaType pair.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Update the DogtagRABackend to interpret the {label} path component,
if given, as an AuthorityID.  Issue the request to the nominated
authority (a.k.a. lightweight CA / LWCA).

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Add security contraints to require an authenticated user for
enrollment requests.

This is implemented by requiring an authenticated user for all POST
requests.  It would have been preferable to use paths; unfortunately
we cannot.  In EST, paths such as
`.well-known/est/{label}/simpleenroll` are used, where {label} is
user-specified and interpreted by the server.  But Servlet
<url-pattern> can only match path prefixes.

So far, the set of resources we have implemented are such that all
POST requests require authentication, and all GET requests are
anonymous.  If this changes in the future, we will have to move
authentication down into the application itself.

The valid role name is "**" meaning any authenticated user will be
granted access.  The EST application performs its own authorization
checks (to be implemented in a subsequent commit).

The realm configuration is also left to the administator.  Future
commits may deal with providing alternative means of realm
configuration.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Add the EST authorization interface.  Design sketch:

- abstract class ESTRequestAuthorizer defines the authorization
  interface.  Each enrollment method (currently: simpleenroll and
  simplereenroll) has its own authorization method.

- The arguments of the authorization methods are an
  ESTRequestAuthorizationData and the CSR.

- ESTRequestAuthorizationData contains common data used for making
  authorization decisions, including:

  - authenticated principal
  - client IP address
  - EST path label (if any)

- ESTEngine instantiates and initializes a concrete subclasses of
  ESTRequestAuthorizer according to configuration.

- ESTFrontend enrollment methods construct the
  ESTRequestAuthorization data, accesses the ESTRequestAuthorizer
  via the ESTEngine and invokes the relevant authorization method.

- (Next commit) ESTFrontend passes the Object returned by the
  authorization method to the ESTBackend enrollment method.  In this
  way, arbitrary data generated by the authorizer implementation can
  be consumed by the ESTBackend implementation.

Subsequent commits will implement the authorization result Object
propagation, and provide an authorizer implementation that invokes
an external process to compute the authorization result.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Propagate the output of the ESTRequestAuthorizer authorization
method to the ESTBackend issuance method.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Implement the ExternalProcessRequestAuthorizer.  This implementation
executes the configured executable as a subprocess.  It pipes the
request authz data, CSR and label to the subprocess standard input
as a JSON object.  The request is authorized if the subprocess exits
normally (exit status 0).  If the subprocess does get created but
exits abnormally, throws ForbiddenException using the subprocess
standard output as the message.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
When the EST client uses TLS client certificate authentication,
provide the client certificate chain in the
ESTRequestAuthorizationData.

Also update the ExternalProcessRequestAuthorizer to put the
certificate chain in the JSON object provided to the subprocess
standard input.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Some classes and fields had unsuitable visibility, which this commit
addresses.  Specifically:

- The `ESTBackend` `config` field had default (package) visibility,
  but needs to be visible to subclasses (which may be implemented by
  anyone).  Make it `protected`.

- The `ESTRequestAuthorizationData` class had default (package)
  visibility, but it needs to be visible to arbitrary subclasses of
  `ESTRequestAuthorizer`.  Make it `public`.

- The fields of `ESTRequestAuthorizationData` had default (package)
  visibility.  Make them `public` so as to be readable by arbitrary
  subclasses of `ESTRequestAuthorizer`.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
When deploying via `pki-server create`, we fail to install the
`rewrite.config`.  Add this behaviour.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
The `pki-server create` deployment regime copies and modifies
`/etc/tomcat/server.xml`, rather than copying the `server.xml`
shipped in the pki-server package.  This `server.xml` does not
include the RewriteValve, which we use to rewrite requests
`/.well-known/est` to the actual application path.

Update the deployment procedure to add RewriteValve to `<Host>`
elements in the `server.xml`.  The behaviour is encapsulated in the
`add_rewrite_valve(document)` staticmethod.  The operation is
idempotent - a no-op if RewriteValve is already present.  Therefore
this method may be useful in upgrade scripts too.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
DogtagRABackend re-raises PKIException thrown by the CAClient.  If
something is misconfigured resulting in the CA subsystem responding
401 or 403, the EST responds the same way.  This is a bug.

Modify DogtagRABackend to inspect the int code of the caught
PKIException.  If  401 or 403, throw a new PKIException (status
500).  Otherwise re-raise, as previously.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
RFC 7030 Section 4.2.2. "Simple Re-enrollment of Clients" states:

  A certificate request employs the same format as the "simpleenroll"
  request, using the same HTTP content-type.  The request Subject field
  and SubjectAltName extension MUST be identical to the corresponding
  fields in the certificate being renewed/rekeyed.

Implement this requirement.

Note that we really do check that the values are __identical__, by
comparing their DER.  StringPrep, caseIgnoreMatch and GeneralName
type-specific equality rules are not applied.  Nor do we consider
that equivalent SAN values could appear in different orders.

So, we indeed implement the RFC correctly.  But we should not be
surprised if this turns out to be too strict.  Real world scenarios
could necessitate relaxing the check from "identical" to
"equivalent".  But for now, we stay with the strict - and much
simpler! - behaviour.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Some deployment scenarios involve using `pki-server create` and then
running `pkispawn`.  For example, you can explicitly create the
server instance and NSSDB, then install the CA with the existing
database.

Such a scenario currently fails because both steps try to create the
`rewrite.config` symlink.  pkispawn fails with FileExistsError.
Update the code to gracefully handle this scenario.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Some platforms we target have jackson-2.10.  Build is failing due to
use of a JsonGenerator method that was only added in v2.11:

    error: no suitable method found for writeArray(String[],int,int)
        generator.writeArray(roles, 0, roles.length);

Update our implementation to avoid use of this method.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Building against the older JSS on target platforms for v10.2.1 fails
due to:

    cert.getSubjectName() symbol not found

Instead of porting patch 1ced9c8cf from jss v5 to v4.9.x, replace
`cert.getSubjectName()` with `cert.getSubjectDN()` as a workaround
to allow EST to build against jss v4.9.x.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
In general (although not in our implementation - yet) the
to-be-renewed certificate is not necessarily the same as the client
certificate used to authenticate.  Add a parameter for the
to-be-renewed certificate to the
`ESTRequestAuthorizer.authorizeSimplereenroll()` method.

Also update the `ExternalProcessRequestAuthorizer` to include the
certificate in the `toBeRenewed` field of the JSON data sent to the
child process.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Enable the use of "native" Tomcat RealmBase subclasses (e.g.
MemoryRealm) with the EST subsystem.  The main aspects of this
change are:

- Invoke the LifecycleBase start()/stop() methods at the
  corresponding WebListener lifecycle changes.

- Register the realm with ProxyRealm *before* invoking start().
  ProxyRealm.registerRealm() invokes setContainer(), a prerequisite
  for start() to succeed.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
Currently there is no way to configure instances of Tomcat "bundled"
realms.  For trivial implementations like `MemoryRealm` this is not
an issue.  But for other implementations (e.g. `JNDIRealm`,
`JAASRealm`, etc) a configuration mechanism is necessary.

Tomcat's own configuration mechanism uses introspection to propagate
attributes in the XML `<Realm>` element to the new `Realm` object.
It looks for a setter method whose name is the capitalisation of the
attribute name, prepended with `"set"`.

Update `ESTEngine` to do likewise for the properties in
`realm.conf`, when the realm is an instance of `RealmBase`.  The
`IntrospectionUtils.setProperty` helper method from Tomcat does the
heavy lifting.  We just need to iterate the properties and set each
one.

Properties that have no corresponding setter method or which throw
`IllegalArgumentException` result in a `RuntimeException` that
reports the bad property.  This behaviour forces administrators to
resolve or remove bad properties in `realm.conf`.

Part of: #3297
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
fmarco76 pushed a commit that referenced this issue Dec 14, 2022
@ckelleyRH
Copy link
Contributor

This RFE is now implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants