io7m | single-page | multi-page | epub | Canonmill User Manual

Canonmill User Manual

CREATOR Mark Raynsford
DATE 2022-07-01T12:36:21+00:00
DESCRIPTION Documentation for the Canonmill keystore.
IDENTIFIER dcc7df2a-0093-4b52-8204-88d6f61395d6
LANGUAGE en
RIGHTS Public Domain
TITLE Canonmill User Manual

Table Of Contents

The canonmill package provides a Keystore implementation designed to be less painful from an operational perspective than any of the Keystore implementations included in the standard JDK.

1.2. Features

  • Exposes a simple directory-based keystore with a single XML file that maps certificate aliases to files. Keys and certificates are expected to be PEM-encoded regular files.
  • Implicit compatibility with ACME systems; ACME clients can simply copy certificate files into the directory and, as long as the certificates have an entry in the XML index file, the new certificates will become available as soon as the Keystore is reloaded.
  • A small, easily auditable codebase with use of modularity for correctness.
  • An extensive automated test suite with high coverage.
  • Platform independence. No platform-dependent code is included in any form.
  • OSGi-ready
  • JPMS-ready
  • ISC license
The package does not support encryption of the keystore with a password, or encryption of the keystore entries with passwords. In the author's opinion, this offers no real security. To elaborate, keystore encryption is intended to ensure that private keys are encrypted at rest so that, in the event of a compromise, the private keys cannot be used by an attacker. Unfortunately, this is ineffectual for a couple of reasons. Firstly, because nobody wants to have to manually type in a password each time their server-based Java application starts, the password is typically stored along with the application configuration. This means if an attacker compromises the application, they have both the keys and the passwords anyway. Secondly, if an application is compromised and the keystore is stolen, the keys within the keystore are going to have to be blacklisted and reissued anyway, so "protecting" them with a passphrase is of very little utility.
Use the following Maven dependency:

2.1.2. Maven Dependency

<dependency>
  <groupId>com.io7m.canonmill</groupId>
  <artifactId>com.io7m.canonmill.core</artifactId>
  <version>2.1.0</version>
</dependency>
To use the canonmill keystore, it's first necessary to create a directory that will contain PEM-encoded private keys and certificates. It's then necessary to create an index file that maps aliases to keys/certificates.
An an example, assume the following files exist:

2.2.3. Example Keys/Certificates

Name Description
www.key The private key for www.example.com
www.crt The public certificate for www.example.com
mail.key The private key for mail.example.com
mail.crt The public certificate for mail.example.com
Now, all of these files could be placed in a directory /etc/certs. We can now create the index file and place it anywhere the application wants it to be. The index file has a strictly-defined XML format with a schema. An example index file might look like this:

2.2.5. Example Index

<?xml version="1.0" encoding="UTF-8" ?>

<Keystore xmlns="urn:com.io7m.canonmill.keystore:1"
          BaseDirectory="/etc/certs">
  <Key Name="www.example.com"
       File="www.key"/>
  <Key Name="mail.example.com"
       File="mail.key"/>
  <Certificate Name="www.example.com"
               File="www.crt"/>
  <Certificate Name="mail.example.com"
               File="mail.crt"/>
</Keystore>
Each Key and Certificate entry assigns an alias to a key or certificate, respectively. The filenames given are resolved relative to the directory given by the BaseDirectory attribute.
To load a keystore, the standard Keystore API should be used. Assuming that the keystore's XML index file exists at file:

2.3.2. Example Load

final var ks =
  KeyStore.getInstance("CANONMILL", new CMKeyStoreProvider());

try (var stream = Files.newInputStream(file)) {
  ks.load(stream, null);
}
The CMKeyStoreProvider class is registered using the name CANONMILL as service of type java.security.Provider and so can be used in the same manner as any other provider in the JDK security API.
The standard keystore API provides methods to reload KeyStore values at any time. Unfortunately, other parts of the security API such as the SSLContext will cache the keys and certificates internally and so reloading the KeyStore is ineffective.
The CMKeyStores class contains convenience methods to construct an SSLContext for use in applications, and to reload any given SSLContext.

2.4.3. Example Reload

final KeyStore ks =
  CMKeyStores.openKeyStore(Paths.get("ServerKeyStore.xml"));
final KeyStore ts =
  CMKeyStores.openKeyStore(Paths.get("ServerTrustStore.xml"));

final SSLContext context =
  CMKeyStores.createSSLContext(ks, ts, "TLSv1.3");

// Open server sockets, etc

CMKeyStores.reloadKeystoreFromFile(ks, Paths.get("ServerKeyStore.xml"));
CMKeyStores.reloadKeystoreFromFile(ts, Paths.get("ServerTrustStore.xml"));
CMKeyStores.reloadSSLContext(ks, ts, context);
The XML schema for the keystore's index file is as follows:

2.5.2. Keystore Schema

<?xml version="1.0" encoding="UTF-8" ?>

<!--
  Copyright © 2023 Mark Raynsford <code@io7m.com> https://www.io7m.com

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
  IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="urn:com.io7m.canonmill.keystore:1"
            xmlns:cm="urn:com.io7m.canonmill.keystore:1">

  <xsd:complexType name="KeystoreElementType"
                   abstract="true">
    <xsd:annotation>
      <xsd:documentation>
        The base type of elements that can appear in a keystore.
      </xsd:documentation>
    </xsd:annotation>

    <xsd:attribute name="Name"
                   type="xsd:string"
                   use="required">
      <xsd:annotation>
        <xsd:documentation>
          The name of the keystore element.
        </xsd:documentation>
      </xsd:annotation>
    </xsd:attribute>

    <xsd:attribute name="File"
                   type="xsd:string"
                   use="required">
      <xsd:annotation>
        <xsd:documentation>
          The file containing data for the keystore element.
        </xsd:documentation>
      </xsd:annotation>
    </xsd:attribute>
  </xsd:complexType>

  <xsd:complexType name="CertificateType">
    <xsd:annotation>
      <xsd:documentation>
        The type of certificates within a keystore.
      </xsd:documentation>
    </xsd:annotation>

    <xsd:complexContent>
      <xsd:extension base="cm:KeystoreElementType"/>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:element name="Certificate"
               type="cm:CertificateType"/>

  <xsd:complexType name="KeyType">
    <xsd:annotation>
      <xsd:documentation>
        The type of keys within a keystore.
      </xsd:documentation>
    </xsd:annotation>

    <xsd:complexContent>
      <xsd:extension base="cm:KeystoreElementType"/>
    </xsd:complexContent>
  </xsd:complexType>

  <xsd:element name="Key"
               type="cm:KeyType"/>

  <xsd:group name="KeystoreElementGroup">
    <xsd:annotation>
      <xsd:documentation>
        The group containing elements that can appear within a keystore.
      </xsd:documentation>
    </xsd:annotation>

    <xsd:choice>
      <xsd:element ref="cm:Certificate"/>
      <xsd:element ref="cm:Key"/>
    </xsd:choice>
  </xsd:group>

  <xsd:element name="Keystore">
    <xsd:annotation>
      <xsd:documentation>
        The keystore element.
      </xsd:documentation>
    </xsd:annotation>

    <xsd:complexType>
      <xsd:sequence minOccurs="0"
                    maxOccurs="unbounded">
        <xsd:group ref="cm:KeystoreElementGroup"/>
      </xsd:sequence>

      <xsd:attribute name="BaseDirectory"
                     use="required"
                     type="xsd:anyURI">
        <xsd:annotation>
          <xsd:documentation>
            The absolute path of the keystore directory.
          </xsd:documentation>
        </xsd:annotation>
      </xsd:attribute>
    </xsd:complexType>

    <xsd:key name="KeyUnique">
      <xsd:selector xpath="cm:Key"/>
      <xsd:field xpath="@Name"/>
    </xsd:key>

    <xsd:key name="CertificateUnique">
      <xsd:selector xpath="cm:Certificate"/>
      <xsd:field xpath="@Name"/>
    </xsd:key>
  </xsd:element>

</xsd:schema>
io7m | single-page | multi-page | epub | Canonmill User Manual