September 27, 2019

Minimalistic POM for Java 11 and Java EE 8

One of the greatest news with Java EE 7 and 8, is that there is ONE dependency for the entire Java EE.

And here is a minimalistic POM for your Java EE 8 projects with Java 11. I also added junit, hamcrest, mockito and in-memory DB H2 and mysql jdbc driver which are not necessary, but I use them a lot, so I added them for convenience.


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>se.magnuskkarlsson.examples</groupId>
    <artifactId>dockerwar</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Test Support -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.1</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                    <showDeprecation>true</showDeprecation>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

If you need in memory database checkout "H2 Database Engine Cheat Sheet" [http://www.h2database.com/html/cheatSheet.html]

September 21, 2019

HTTP Client in Java 8 HttpURLConnection

The standard HTTP Client in Java 8 is HttpURLConnection, but working with it is not so intuitively, so I have written a small helper class, that does:

  • All HTTP method: GET, POST, PUT, DELETE, etc.
  • Sets HTTP Request Headers
  • Sets HTTP POST Body
  • Read response
  • Read response Body
  • Read response Headers

package se.magnuskkarlsson.example.httpurlconnection;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HttpURLConnectionBuilder {

    // See javax.ws.rs.core.HttpHeaders

    // Body type in request
    public static final String CONTENT_TYPE = "Content-Type: application/json";

    // Accepted response by client
    public static final String ACCEPT = "Accept";

    // See javax.ws.rs.core.MediaType

    // Media Type 'application/json'
    public final static String APPLICATION_JSON = "application/json";

    private URL url;
    private String method;
    private Map<String, String> headers = new HashMap<String, String>();
    private String body;

    public HttpURLConnectionBuilder() {
    }

    // ----------------------- Logic Methods -----------------------

    public HttpURLConnectionBuilder get(URL url) {
        return method(url, "GET");
    }

    public HttpURLConnectionBuilder post(URL url) {
        return method(url, "POST");
    }

    public HttpURLConnectionBuilder put(URL url) {
        return method(url, "PUT");
    }

    public HttpURLConnectionBuilder delete(URL url) {
        return method(url, "DELETE");
    }

    public HttpURLConnectionBuilder method(URL url, String method) {
        this.url = url;
        this.method = method;
        return this;
    }

    public HttpURLConnectionBuilder header(String name, String value) {
        this.headers.put(name, value);
        return this;
    }

    public HttpURLConnectionBuilder body(String body) {
        this.body = body;
        return this;
    }

    public HttpURLConnectionResponse send() throws IOException {
        HttpURLConnection conn = (HttpURLConnection) this.url.openConnection();

        // Set Request Method
        conn.setRequestMethod(this.method);

        // Set Request Headers
        for (String name : this.headers.keySet()) {
            conn.setRequestProperty(name, this.headers.get(name));
        }

        // Trace Log Request
        System.out.println(conn.getRequestMethod() + " " + conn.getURL());
        for (String key : conn.getRequestProperties().keySet()) {
            System.out.println(
                    "  >> " + key + ": " + conn.getRequestProperty(key));
        }

        // Set Request Body
        if (this.body != null) {
            conn.setDoOutput(true);
            try (OutputStreamWriter output = new OutputStreamWriter(
                    new BufferedOutputStream(conn.getOutputStream()),
                    StandardCharsets.UTF_8)) {

                output.write(this.body);
            }
        }

        // Send Request and Get Response Code
        int responseCode = conn.getResponseCode();

        // Get Response Body
        InputStream responseStream = null;
        if (responseCode >= 200 && responseCode <= 299) {

            responseStream = conn.getInputStream();
        } else {

            responseStream = conn.getErrorStream();
        }
        String responseBody = null;
        try (BufferedReader in = new BufferedReader(new InputStreamReader(
                responseStream, StandardCharsets.UTF_8))) {

            StringBuilder builder = new StringBuilder();
            for (String line = null; (line = in.readLine()) != null;) {
                builder.append(line);
            }
            responseBody = builder.toString();
        }

        // Get Response Headers
        Map<String, List<String>> responseHeaders = conn.getHeaderFields();

        // Trace Log Response
        System.out.println("<< " + conn.getHeaderField(null));
        for (String key : conn.getHeaderFields().keySet()) {
            if (key != null) {
                System.out.println(
                        "  << " + key + ": " + conn.getHeaderField(key));
            }
        }

        return new HttpURLConnectionResponse(responseCode, responseBody,
                responseHeaders);
    }

    // ----------------------- Helper Methods -----------------------

    // ----------------------- Get and Set Methods -----------------------

}

And the response class is a simple immutable POJO.


package se.magnuskkarlsson.example.httpurlconnection;

import java.util.List;
import java.util.Map;

public class HttpURLConnectionResponse {

    private final int statusCode;
    private final String body;
    private final Map<String, List<String>> headers;

    public HttpURLConnectionResponse(int statusCode, String body,
            Map<String, List<String>> headers) {

        this.statusCode = statusCode;
        this.body = body;
        this.headers = headers;
    }

    // ----------------------- Logic Methods -----------------------

    // ----------------------- Helper Methods -----------------------

    // ----------------------- Get and Set Methods -----------------------

    public int getStatusCode() {
        return statusCode;
    }

    public String getBody() {
        return body;
    }

    public Map<String, List<String>> getHeaders() {
        return headers;
    }

}

Now lets test it, with a simple REST service.


package se.magnuskkarlsson.example.httpurlconnection;

import java.util.logging.Logger;

import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

@Path("/persons")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    private final Logger log = Logger.getLogger(PersonResource.class.getName());

    // ----------------------- Logic Methods -----------------------

    // Common Response Codes For All Requests
    // 200 OK Request was successful
    // 401 Unauthorized User not authenticated
    // 403 Forbidden User not authorized for endpoint request, often due to
    // missing security privileges

    // Common Error Response Codes For GET
    // 404 Not Found Resource not found
    // 400 Bad Request Filter parameters provided were not valid Recommended
    // Response Codes

    @GET
    @Path("/{personId}")
    public Response getById(@PathParam("personId") String personId) {
        if ("404".equals(personId)) {

            JsonObject json = createProblemDetails(null, "Not Found", 404,
                    "Person with ID " + personId + " does not exists.", null);
            return Response.status(Response.Status.NOT_FOUND).entity(json)
                    .build();
        }
        JsonObject json = Json.createObjectBuilder().add("personId", personId)
                .add("name", "Magnus").build();
        return Response.ok(json).build();
    }

    // Common Response Codes For POST
    // 200 OK A custom action or processing request was successful
    // 201 Created Created resource successfully
    // 202 Accepted Long-running request has been accepted
    // 400 Bad Request Fields were not valid or required fields were missing

    @POST
    public Response create(String name, @Context UriInfo uriInfo) {
        log.info("name=" + name);
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        builder.path("32");
        // CREATED 201
        return Response.created(builder.build()).build();
    }

    // Common Error Response Codes For PUT or PATCH
    // 200 OK Updated resource successfully
    // 400 Bad Request Fields were not valid or required fields were missing
    // 404 Not Found Resource not found

    // Common Error Response Codes For DELETE
    // 204 No Content Resource deleted
    // 404 Not Found Resource not found

    // ----------------------- Helper Methods -----------------------

    // Problem Details for HTTP APIs https://tools.ietf.org/html/rfc7807
    protected JsonObject createProblemDetails(String type, String title,
            int status, String detail, String instance) {
        type = (type != null) ? type : "about:blank";
        instance = (instance != null) ? instance : "";
        return Json.createObjectBuilder().add("type", type).add("title", title)
                .add("status", status).add("detail", detail)
                .add("instance", instance).build();
    }

    // ----------------------- Get and Set Methods -----------------------

}

And now lets write a System Test for your REST service.


package se.magnuskkarlsson.example.httpurlconnection;

import static org.hamcrest.CoreMatchers.is;
import static se.magnuskkarlsson.example.httpurlconnection.HttpURLConnectionBuilder.*;

import java.net.URL;

import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;

public class HttpURLConnectionST {

    @Test
    public void test() throws Exception {
        URL url = new URL(
                "http://localhost:8080/example-java8-httpurlconnection/rest/persons/12");

        HttpURLConnectionResponse response = new HttpURLConnectionBuilder()
                .get(url) //
                .header(CONTENT_TYPE, APPLICATION_JSON) //
                .header(ACCEPT, APPLICATION_JSON) //
                .send();

        Assert.assertThat(response.getStatusCode(), is(200));
        Assert.assertThat(response.getBody(),
                is("{\"personId\":\"12\",\"name\":\"Magnus\"}"));
    }

    @Test
    public void testNonExisting() throws Exception {
        URL url = new URL(
                "http://localhost:8080/example-java8-httpurlconnection/rest/persons/404");

        HttpURLConnectionResponse response = new HttpURLConnectionBuilder()
                .get(url) //
                .header(CONTENT_TYPE, APPLICATION_JSON) //
                .header(ACCEPT, APPLICATION_JSON) //
                .send();

        Assert.assertThat(response.getStatusCode(), is(404));
        Assert.assertThat(response.getBody(),
                CoreMatchers.containsString("\"title\":\"Not Found\""));
    }

    @Test
    public void testPost() throws Exception {
        URL url = new URL(
                "http://localhost:8080/example-java8-httpurlconnection/rest/persons");

        HttpURLConnectionResponse response = new HttpURLConnectionBuilder()
                .post(url) //
                .header(CONTENT_TYPE, APPLICATION_JSON) //
                .header(ACCEPT, APPLICATION_JSON) //
                .body("{\"name\":\"FOO\"}") //
                .send();

        Assert.assertThat(response.getStatusCode(), is(201));
        Assert.assertThat(response.getBody(), is(""));
        Assert.assertThat(response.getHeaders().get("Location").get(0),
                CoreMatchers.notNullValue());
    }

}

September 20, 2019

Reading NSS DB from Java 11 with SunPKCS11

Introduction

In my previous blog (Reading NSS DB from Java 8 with SunPKCS11) I showed how to read a NSS DB from Java 8 with SunPKCS11. In Java 11 a lot of internal packages is no longer visible and you will get compilation error if you try to access them. The same is true for sun.security.pkcs11.SunPKCS11, since it is a sun class. But SunPKCS11 is still available. What they have done is to make SunPKCS11 always available, i.e. you do not need to add, i.e. Security.addProvider(provider);

Java 8


String configName = "/home/magnuskkarlsson/NetBeansProjects/example-nssdb/pkcs11.cfg";
sun.security.pkcs11.SunPKCS11 provider = new sun.security.pkcs11.SunPKCS11(configName);
Security.addProvider(provider);

Java 11


String configName = "/home/magnuskkarlsson/NetBeansProjects/example-nssdb/pkcs11.cfg";
Provider prototype = Security.getProvider("SunPKCS11");
Provider provider = prototype.configure(configName);

One gotcha with this in Java 11, is that you need to specify the provider when you try to do cryptographic operation like signature, otherwise you will get the following error.

Exception in thread "main" java.security.InvalidKeyException: No installed provider supports this key: sun.security.pkcs11.P11Key$P11PrivateKey

So how have they solved, so that SunPKCS11 is always loaded? Through the java.security which has also moved, due to changes in directory layout in Java 11.

Java 8: $JAVA_HOME/lib/security/java.security

Java 11: $JAVA_HOME/conf/security/java.security

So in Java 11 you can see the following


security.provider.12=SunPKCS11
#security.provider.1=SunPKCS11 ${java.home}/lib/security/nss.cfg

And if you look inside $JAVA_HOME/lib/security/nss.cfg, you see a ready to use EMPTY NSS DB configuration. To read about all SunPKCS11 NSS DB configuration see https://docs.oracle.com/en/java/javase/11/security/pkcs11-reference-guide1.html#GUID-7989F8B4-7260-4908-8203-99056B2D060E


name = NSS
nssLibraryDirectory = /usr/lib64
nssDbMode = noDb
attributes = compatibility
handleStartupErrors = ignoreMultipleInitialisation

Testing Time

Now lets test the SunPKCS11 with Java 11. First lets create a new NSS DB with the new SQLite format (cert9.db, key4.db, and pkcs11.txt) and then add a keypair and a self-signed certificate in the internal token.


$ echo "redhat123" > password.internal
$ mkdir nssdb_sql

$ certutil -N -d sql:nssdb_sql -f password.internal

$ certutil -S -x -d sql:nssdb_sql -f password.internal -n mkk -s 'CN=MKK, O=MKK Consultancy, C=SE' -k rsa -g 2048 -v 24 -Z SHA256 -t ',,'

And don't forget to add an empty secmod.db.


$ touch nssdb_sql/secmod.db

Then create the SunPKCS11 configuration file pkcs11.cfg.


name = NSScrypto
nssLibraryDirectory = /usr/lib64 
nssSecmodDirectory = /home/magnuskkarlsson/NetBeansProjects/example-nssdb/nssdb_sql
nssDbMode = readWrite
nssModule = keystore

package se.magnuskkarlsson.example.nssdb;

import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;

import java.util.Enumeration;

// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#NSS
// https://docs.oracle.com/en/java/javase/11/security/pkcs11-reference-guide1.html#GUID-85EA1017-E59C-49B9-9207-65B7B2BF171E
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/NSS_tools_:_certutil
/*
          certutil supports two types of databases: the legacy
          security databases (cert8.db, key3.db, and secmod.db)
          and new SQLite databases (cert9.db, key4.db, and
          pkcs11.txt).
 */
 /*
name = NSScrypto
nssLibraryDirectory = /usr/lib64 
nssSecmodDirectory = /home/magnuskkarlsson/NetBeansProjects/example-nssdb/nssdb_sql
nssDbMode = readWrite
nssModule = keystore
 */
public class NSSSunPKCS11Tool {

    public static char[] password = "redhat123".toCharArray();

    public static void main(String[] args) throws Exception {
        String configName = "/home/magnuskkarlsson/NetBeansProjects/example-nssdb/pkcs11.cfg";

        // Java 8
//        sun.security.pkcs11.SunPKCS11 provider = new sun.security.pkcs11.SunPKCS11(configName);
//        Security.addProvider(provider);
//        
        // Java 11
        Provider prototype = Security.getProvider("SunPKCS11");
        Provider provider = prototype.configure(configName);

        KeyStore ks = KeyStore.getInstance("PKCS11", provider);
        ks.load(null, password);
        System.out.println("Successfully loaded NSS DB.");
        System.out.println("------------------------------");
        for (Enumeration aliases = ks.aliases(); aliases.hasMoreElements();) {
            String alias = aliases.nextElement();
            X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
            PublicKey publicKey = cert.getPublicKey();
            PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password);
            System.out.println("alias: " + alias);
            System.out.println("privateKey: " + privateKey);
            System.out.println("cert subject dn: " + cert.getSubjectX500Principal().toString());

            if (privateKey != null) {
                String plainText = "HELLO WORLD";

                Signature privateSignature = Signature.getInstance("SHA256withRSA", provider);
                privateSignature.initSign(privateKey);
                privateSignature.update(plainText.getBytes(UTF_8));
                byte[] signature = privateSignature.sign();
                String signatureBase64 = Base64.getEncoder().encodeToString(signature);
                System.out.println(signatureBase64);

                Signature publicSignature = Signature.getInstance("SHA256withRSA", provider);
                publicSignature.initVerify(publicKey);
                publicSignature.update(plainText.getBytes(UTF_8));
                boolean verify = publicSignature.verify(signature);
                System.out.println("verify: " + verify);
            }

            System.out.println("------------------------------");
        }
    }
}

Then lets run it. I have here added the debug flag -Djava.security.debug=sunpkcs11 which is of course not necessary, but useful when running labs to understand things better.


$ mvn clean install; java -cp target/example-nssdb-1.0.0-SNAPSHOT.jar -Djava.security.debug=sunpkcs11 se.magnuskkarlsson.example.nssdb.NSSSunPKCS11Tool

SunPKCS11 loading /home/magnuskkarlsson/NetBeansProjects/example-nssdb/pkcs11.cfg
NSS modules: [NSS Internal PKCS #11 Module (CRYPTO, /usr/lib64/libsoftokn3.so, slot 0), NSS Internal PKCS #11 Module (KEYSTORE, /usr/lib64/libsoftokn3.so, slot 1)]
sunpkcs11: Initializing PKCS#11 library /usr/lib64/libsoftokn3.so
Information for provider SunPKCS11-NSScrypto
Library info:
  cryptokiVersion: 2.20
  manufacturerID: Mozilla Foundation              
  flags: 0
  libraryDescription: NSS Internal Crypto Services    
  libraryVersion: 3.46
All slots: 1, 2
Slots with tokens: 1, 2
Slot info for slot 2:
  slotDescription: NSS User Private Key and Certificate Services                   
  manufacturerID: Mozilla Foundation              
  flags: CKF_TOKEN_PRESENT
  hardwareVersion: 3.46
  firmwareVersion: 0.00
Token info for token in slot 2:
  label: NSS Certificate DB              
  manufacturerID: Mozilla Foundation              
  model: NSS 3           
  serialNumber: 0000000000000000
  flags: CKF_RNG | CKF_LOGIN_REQUIRED | CKF_USER_PIN_INITIALIZED | CKF_DUAL_CRYPTO_OPERATIONS | CKF_TOKEN_INITIALIZED
  ulMaxSessionCount: CK_EFFECTIVELY_INFINITE
  ulSessionCount: 1
  ulMaxRwSessionCount: CK_EFFECTIVELY_INFINITE
  ulRwSessionCount: 0
  ulMaxPinLen: 500
  ulMinPinLen: 0
  ulTotalPublicMemory: 1
  ulFreePublicMemory: 1
  ulTotalPrivateMemory: 1
  ulFreePrivateMemory: 1
  hardwareVersion: 0.00
  firmwareVersion: 0.00
  utcTime: 0000000000000000
...
sunpkcs11: login succeeded
Successfully loaded NSS DB.
------------------------------
alias: mkk
privateKey: SunPKCS11-NSScrypto RSA private key, 2048 bitstoken object, sensitive, extractable)
cert subject dn: CN=MKK, O=MKK Consultancy, C=SE
bKqsRr+exiclVEB1Q59/M/KrElu+dCFKDK9+mertjlj1lJ7LG7Gwrws6CX/m6l3A9bf4nt+yQNYYt/2x3WFITquuBsbMKfyV6J7UHYS7J92+EUNHNXaFR2QVDo5v3Ecy4oPD9ln7LATl1jJnfSs0kiYB7HbOIWjufxfrY65sgQUyR+I3uQaj0+PDJ8WbrUbqCvzdFv3MH+Jv9kDvdp1eBkrPD+yczLdIQy7kDJRmzN34gU6tW85RZ0PpgjZomV3TO3S6hJxeZqH/ijd5yLKlpfQAM2V4dADsnlGmS9KUrZ6JOU/eIFRX6CD0X/eNsCqgUp+vn7JD/SE4NJabUdrVQw==
Demoting session, active: 3
verify: true
------------------------------

September 15, 2019

Reading NSS DB with SoftHSM Token with Java JSS

Introduction

In my previous blog I showed you how to read a NSS DB with SunPKCS11.. The SunPKCS11 working for both the legacy NSS database format (cert8.db, key3.db, and secmod.db) and the new SQLite format (cert9.db, key4.db, and pkcs11.txt), but SunPKCS11 cannot only read entries in the internal NSS database, not other tokens, such HSM or SmartCard.

To be able to read other tokens in a NSS DB you need to use Network Security Services for Java (JSS) for Java - https://github.com/dogtagpki/jss.

Here in this blog I will use SoftHSM to simulate a real HSM.

SoftHSM v2

References:

Install


# yum install -y softhsm

Configure a new token in SoftHSM named Dogtag and set PIN and SO (Security Officer/Admin) PIN to the first available slot.


# softhsm2-util --init-token --label "Dogtag" --so-pin redhat321 --pin redhat321 --free
Slot 0 has a free/uninitialized token.
The token has been initialized and is reassigned to slot 634761745

List the files for SoftHSM


# ls -ald /var/lib/softhsm
drwxr-x---. 3 ods ods 20 Sep 15 00:53 /var/lib/softhsm

# ls -ald /var/lib/softhsm/tokens
drwxrwx--T. 3 ods ods 50 Sep 15 00:55 /var/lib/softhsm/tokens

# ls -ald /var/lib/softhsm/tokens/372c8761-c4e8-baab-fa9c-6eb4a5d5b211
drwx------. 2 root root 62 Sep 15 00:55 /var/lib/softhsm/tokens/372c8761-c4e8-baab-fa9c-6eb4a5d5b211

# ls -al /var/lib/softhsm/tokens/372c8761-c4e8-baab-fa9c-6eb4a5d5b211/
total 8
drwx------. 2 root root  62 Sep 15 00:55 .
drwxrwx--T. 3 ods  ods   50 Sep 15 00:55 ..
-rw-------. 1 root root   8 Sep 15 00:55 generation
-rw-------. 1 root root   0 Sep 15 00:55 token.lock
-rw-------. 1 root root 320 Sep 15 00:55 token.object

Disable p11-kit

Here we are going to disable p11-kit and manually add SoftHSM module to a NSS DB.

Reference: https://pagure.io/freeipa/issue/7810


# rm -f /etc/crypto-policies/local.d/nss-p11-kit.config && update-crypto-policies

# reboot

NSS DB

Now lets create our NSS DB. We are going to use the new format SQLite and will explicitly use the prefix 'sql:' to mark that.

First install nss-tools.


# yum install -y nss-tools

Then create our new NSS DB.


# echo "redhat321" > password.softhsm
# echo "redhat123" > password.internal
# mkdir nssdb_sql_softhsm
# certutil -N -d sql:nssdb_sql_softhsm -f password.internal

Then we manually add the SoftHSM module to that NSS DB.


# rpm -ql softhsm | grep libsofthsm2.so
/usr/lib64/libsofthsm2.so
/usr/lib64/pkcs11/libsofthsm2.so

# modutil -dbdir sql:nssdb_sql_softhsm -add softhsm -libfile /usr/lib64/pkcs11/libsofthsm2.so -force

# modutil -dbdir sql:nssdb_sql_softhsm -add softhsm -libfile /usr/lib64/pkcs11/libsofthsm2.so -force

# certutil -U -d sql:nssdb_sql_softhsm

    slot: NSS Internal Cryptographic Services
   token: NSS Generic Crypto Services
     uri: pkcs11:token=NSS%20Generic%20Crypto%20Services;manufacturer=Mozilla%20Foundation;serial=0000000000000000;model=NSS%203

    slot: NSS User Private Key and Certificate Services
   token: NSS Certificate DB
     uri: pkcs11:token=NSS%20Certificate%20DB;manufacturer=Mozilla%20Foundation;serial=0000000000000000;model=NSS%203

    slot: SoftHSM slot ID 0x25d5b211
   token: Dogtag
     uri: pkcs11:token=Dogtag;manufacturer=SoftHSM%20project;serial=fa9c6eb4a5d5b211;model=SoftHSM%20v2

Now lets create a RSA keypair and self-sign a certificate containing the public key. There are a lot of arguments for that so I copied the help text below, to easier follow.


# certutil -S -x -d sql:nssdb_sql_softhsm -h Dogtag -f password.softhsm -n mkk -s 'CN=MKK, O=MKK Consultancy, C=SE' -k rsa -g 2048 -v 24 -Z SHA256 -t ',,'
...
"certutil: could not change trust on certificate: SEC_ERROR_TOKEN_NOT_LOGGED_IN: The operation failed because the PKCS#11 token is not logged in."

# certutil --help
...
-S              Make a certificate and add to database
   -n key-name       Specify the nickname of the cert
   -s subject        Specify the subject name (using RFC1485)
   -c issuer-name    The nickname of the issuer cert
   -t trustargs      Set the certificate trust attributes (see -A above)
   -k key-type-or-id Type of key pair to generate ("dsa", "ec", "rsa" (default))
   -h token-name     Name of token in which to generate key (default is internal)
   -g key-size       Key size in bits, RSA keys only (min 512, max 8192, default 2048)
   --pss             Create a certificate restricted to RSA-PSS (rsa only)
   -q pqgfile        Name of file containing PQG parameters (dsa only)
   -q curve-name     Elliptic curve name (ec only)
                     See the "-G" option for a full list of supported names.
   -x                Self sign
   --pss-sign        Sign the certificate with RSA-PSS (the issuer key must be rsa)
   -m serial-number  Cert serial number
   -w warp-months    Time Warp
   -v months-valid   Months valid (default is 3)
   -f pwfile         Specify the password file
   -d certdir        Cert database directory (default is ~/.netscape)
   -P dbprefix       Cert & Key database prefix
   -p phone          Specify the contact phone number ("123-456-7890")
   -Z hashAlg        
                     Specify the hash algorithm to use. Possible keywords:
                     "MD2", "MD4", "MD5", "SHA1", "SHA224",
                     "SHA256", "SHA384", "SHA512"
   -1                Create key usage extension
   -2                Create basic constraint extension
   -3                Create authority key ID extension
   -4                Create crl distribution point extension
   -5                Create netscape cert type extension
   -6                Create extended key usage extension
   -7 emailAddrs     Create an email subject alt name extension
   -8 DNS-names      Create a DNS subject alt name extension
   --extAIA          Create an Authority Information Access extension
   --extSIA          Create a Subject Information Access extension
   --extCP           Create a Certificate Policies extension
   --extPM           Create a Policy Mappings extension
   --extPC           Create a Policy Constraints extension
   --extIA           Create an Inhibit Any Policy extension
   --extSKID         Create a subject key ID extension
   See -G for available key flag options 
   --extNC           Create a name constraints extension
   --extSAN type:name[,type:name]... 
                     Create a Subject Alt Name extension with one or multiple names
                     - type: directory, dn, dns, edi, ediparty, email, ip, ipaddr,
                             other, registerid, rfc822, uri, x400, x400addr
   --extGeneric OID:critical-flag:filename[,OID:critical-flag:filename]... 
                     Add one or multiple extensions that certutil cannot encode yet,
                     by loading their encodings from external files.
                     - OID (example): 1.2.3.4
                     - critical-flag: critical or not-critical
                     - filename: full path to a file containing an encoded extension
...

To test it we list the certificates (-L) and the private keys (-K) for the NSS DB.


# certutil -L -d sql:nssdb_sql_softhsm -h all -f password.softhsm 

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI

mkk                                                          u,u,u
Dogtag:mkk                                                   u,u,u

# certutil -K -d sql:nssdb_sql_softhsm -f password.internal 
certutil: Checking token "NSS Certificate DB" in slot "NSS User Private Key and Certificate Services"
certutil: no keys found

# certutil -K -d sql:nssdb_sql_softhsm -h Dogtag -f password.softhsm 
certutil: Checking token "Dogtag" in slot "SoftHSM slot ID 0x25d5b211"
< 0> rsa      1e1581006057a890e9a757314893352dc721a959   Dogtag:mkk

# ll nssdb_sql_softhsm/
total 68
-rw-------. 1 root root 28672 Sep 15 01:02 cert9.db
-rw-------. 1 root root 36864 Sep 15 00:59 key4.db
-rw-------. 1 root root   483 Sep 15 00:59 pkcs11.txt

Network Security Services, NSS, for Java (JSS)

So we have now set up our NSS DB with a SoftHSM token Dogtag. Now move onto the Java part with JSS. First install it jss and its dependency.


# yum install -y jss

# vi NSSJSSTool.java

import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Enumeration;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.crypto.CryptoStore;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.ObjectNotFoundException;
import org.mozilla.jss.crypto.TokenException;

// NEW project page: https://github.com/dogtagpki/jss
// https://github.com/dogtagpki/jss/blob/master/org/mozilla/jss/tests/KeyStoreTest.java
public class NSSJSSTool {

    public static void main(String[] args) throws Exception {
        String nssDatabasePath = "/home/magnuskkarlsson/NetBeansProjects/example-nssdb/nssdb_sql_softhsm";

        CryptoManager.initialize(nssDatabasePath);

        CryptoManager cryptoManager = CryptoManager.getInstance();

        for (Enumeration tokens = cryptoManager.getAllTokens(); tokens.hasMoreElements();) {

            CryptoToken token = tokens.nextElement();
            System.out.println("----------------------------------");
            System.out.println(token.getName());
            System.out.println("----------------------------------");
            CryptoStore store = token.getCryptoStore();

            for (org.mozilla.jss.crypto.X509Certificate cert : store.getCertificates()) {

                System.out.println(cert.getNickname() + "\t" + cert.getSubjectDN());
                org.mozilla.jss.crypto.PrivateKey privateKey = findPrivateKey(store, cert);
                if (privateKey != null) {
                    checkKeys(privateKey, cert.getPublicKey());
                }
            }
        }
    }

    // loop through all private key to find match
    public static org.mozilla.jss.crypto.PrivateKey findPrivateKey(CryptoStore store,
            org.mozilla.jss.crypto.X509Certificate cert) throws TokenException, ObjectNotFoundException {

        for (org.mozilla.jss.crypto.PrivateKey privateKey : store.getPrivateKeys()) {

            // ObjectNotFoundException If the corresponding public key is not found.
            java.security.PublicKey publicKey = store.findPublicKey(privateKey);
            if (Arrays.equals(cert.getPublicKey().getEncoded(), publicKey.getEncoded())) {
                System.out.println(privateKey);
                return privateKey;
            }
        }
        return null;
    }

    public static void checkKeys(java.security.PrivateKey privateKey, java.security.PublicKey publicKey) throws
            InvalidKeyException, SignatureException, NoSuchAlgorithmException {

        byte[] data = "HELLO WORLD".getBytes(UTF_8);

        byte[] signatureBytes = sign(privateKey, data);

        boolean verify = verify(publicKey, data, signatureBytes);
    }

    public static byte[] sign(java.security.PrivateKey privateKey, byte[] data) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data);
        byte[] signatureBytes = signature.sign();
        String signatureEncoded = Base64.getEncoder().encodeToString(signatureBytes);
        System.out.println(signatureEncoded);
        return signatureBytes;
    }

    public static boolean verify(java.security.PublicKey publicKey, byte[] data, byte[] signatureBytes) throws
            NoSuchAlgorithmException, InvalidKeyException, SignatureException {

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(data);
        boolean verify = signature.verify(signatureBytes);
        System.out.println("verify: " + verify);
        return verify;
    }
}

Now lets compile it and run it.


# javac -cp /usr/lib64/jss/jss4.jar:/usr/share/java/slf4j/slf4j-api.jar:/usr/share/java/slf4j/slf4j-simple.jar NSSJSSTool.java

# java -cp .:/usr/lib64/jss/jss4.jar:/usr/share/java/slf4j/slf4j-api.jar:/usr/share/java/slf4j/slf4j-simple.jar NSSJSSTool
[main] INFO org.mozilla.jss.CryptoManager - CryptoManager: loading JSS library
[main] INFO org.mozilla.jss.CryptoManager - CryptoManager: loaded JSS library from /usr/lib64/jss/libjss4.so
[main] INFO org.mozilla.jss.CryptoManager - CryptoManager: initializing NSS database at /root/nssdb_sql_softhsm
----------------------------------
NSS Generic Crypto Services
----------------------------------
----------------------------------
Internal Key Storage Token
----------------------------------
mkk CN=MKK,O=MKK Consultancy,C=SE
Enter password for Internal Key Storage Token

----------------------------------
Dogtag
----------------------------------
Enter password for Dogtag

Dogtag:mkk CN=MKK,O=MKK Consultancy,C=SE
org.mozilla.jss.pkcs11.PK11RSAPrivateKey@736e9adb
KaFqQUy+mv9nSrEkoIP/8rqG7D2bgXD9R48xw40eP/Sly4dkblSsVyBxIuZ1N89TLbFNoAQZVdP9T0sosPeZmiYOJOrxD6PMDRQoBzXsXLT1guFv6rBshV3FfcCEbdBOYKay8oEmxrg9Ks7/3PujwE0SwufPEtIqEReNFBjp4kQlegmBEGKYp+3lkMB5006kAoM66YlI8Rr68oBzTaf5fFXmIUFjPJJ1U+LiNWU/54qxEOsqePZxms9l1lU8uB01x5GV5r0sSXNPA2Nug3pQKhbVILJfNXXxqHmSKqGAGHgOPOMgiqlqtrNoqjXraZegXSg5RdvVi588FXS/tji9oA==
verify: true

September 12, 2019

Reading NSS DB from Java 8 with SunPKCS11

Introduction

"Network Security Services (NSS) is a set of libraries designed to support cross-platform development of security-enabled client and server applications. Applications built with NSS can support SSL v3, TLS, PKCS #5, PKCS #7, PKCS #11, PKCS #12, S/MIME, X.509 v3 certificates, and other security standards." [https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS]

And NSS DB is a binary storage for certificates and keys on disk. In this blog I will show you how to read that NSS DB from Java with SunPKCS11

Prerequisite

Install nss-tools on Fedora, RHEL, CentOS.


$ sudo yum install -y nss-tools

NSS DB Format

NSS DB has two types of databases:

  • "The legacy security databases (cert8.db, key3.db, and secmod.db)"
  • "And new SQLite databases (cert9.db, key4.db, and pkcs11.txt)"

Reference:https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/NSS_tools_:_certutil

Java according to official SunPKCS11 only supports the legacy database, but as we will see later, it does supports the new SQLite database.

"nssSecmodDirectory directory containing the NSS DB files The full pathname of the directory containing the NSS configuration and key information (secmod.db, key3.db, and cert8.db). This directive must be specified unless NSS has already been initialized by another component (see above) or NSS is used without database files as described below." [https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#NSS]

The Legacy NSS DB

Lets create a new legacy NSS DB. Note that we explicitly use the prefix dbm: when we create the DB.


$ mkdir nssdb_dbm
$ echo "redhat123" > password.txt
$ certutil -N -d dbm:nssdb_dbm -f password.txt

Now lets add a self-signed certificate and a matching private key in our legacy NSS DB.


$ certutil -S -n mkk -s 'CN=MKK, O=MKK Consultancy, C=SE' -t ',,' -k rsa -g 2048 -x -v 24 -f password.txt -d dbm:nssdb_dbm -Z SHA256

Then verify the certificate, keys and the legacy NSS DB files.


$ certutil -L -d dbm:nssdb_dbm 

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI

mkk                                                          u,u,u

$ certutil -K -d dbm:nssdb_dbm -f password.txt 
certutil: Checking token "NSS Certificate DB" in slot "NSS User Private Key and Certificate Services"
< 0> rsa      bc4e3491a87df6cd843f551a3f2bb714ecfa2bf7   NSS Certificate DB:mkk

$ ll nssdb_dbm/
total 80
-rw-------. 1 magnuskkarlsson magnuskkarlsson 65536 Sep 12 22:59 cert8.db
-rw-------. 1 magnuskkarlsson magnuskkarlsson 16384 Sep 12 22:59 key3.db
-rw-------. 1 magnuskkarlsson magnuskkarlsson 16384 Sep 12 22:58 secmod.db

Now lets write our Java program


package se.magnuskkarlsson.example.nssdb;

import java.security.Key;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;

import java.util.Enumeration;

// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#NSS
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Reference/NSS_tools_:_certutil
/*
          certutil supports two types of databases: the legacy
          security databases (cert8.db, key3.db, and secmod.db)
          and new SQLite databases (cert9.db, key4.db, and
          pkcs11.txt).
 */
public class NSSTool {

    public static char[] password = "redhat123".toCharArray();

    public static void main(String[] args) throws Exception {
        String configName = "/home/magnuskkarlsson/NetBeansProjects/example-nssdb/pkcs11.cfg";
        Provider p = new sun.security.pkcs11.SunPKCS11(configName);
        Security.addProvider(p);
        KeyStore ks = KeyStore.getInstance("PKCS11", p); //p is the provider created above
        ks.load(null, password);
        System.out.println("Successfully loaded NSS DB.");
        for (Enumeration aliases = ks.aliases(); aliases.hasMoreElements();) {
            String alias = aliases.nextElement();
            X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
            Key key = ks.getKey(alias, password);
            System.out.println(key);
            System.out.println(cert);
            System.out.println("------------------------------");
        }
    }
}

name = NSScrypto
# nssLibraryDirectory = 
nssSecmodDirectory = /home/magnuskkarlsson/NetBeansProjects/example-nssdb/nssdb_dbm
nssDbMode = readWrite
nssModule = keystore

The New SQLite NSS DB

Though the official Java documentation does state that it does not support the new format, it can read the new format, but with one hack and that is you need to create an empty secmod.db file.

But first lets create a new NSS DB with the new SQLite format, we explicitly use the prefix sql: when creating the new NSS DB.


$ mkdir nssdb_sql
$ echo "redhat123" > password.txt
$ certutil -N -d sql:nssdb_sql -f password.txt

And then the fix.


$ touch nssdb_sql/secmod.db

And finally lets create a self-signed certificate, so we have something to read.


$ certutil -S -n mkk -s 'CN=MKK, O=MKK Consultancy, C=SE' -t ',,' -k rsa -g 2048 -x -v 24 -f password.txt -d sql:nssdb_sql -Z SHA256

We now have the following files.


$ ll nssdb_sql/
total 68
-rw-------. 1 magnuskkarlsson magnuskkarlsson 28672 Sep 12 23:03 cert9.db
-rw-------. 1 magnuskkarlsson magnuskkarlsson 36864 Sep 12 23:03 key4.db
-rw-------. 1 magnuskkarlsson magnuskkarlsson   420 Sep 12 23:02 pkcs11.txt
-rw-rw-r--. 1 magnuskkarlsson magnuskkarlsson     0 Sep 12 23:04 secmod.db

The Java code stays the same, but we need to update the pkcs11.cfg with our new path


name = NSScrypto
# nssLibraryDirectory = 
nssSecmodDirectory = /home/magnuskkarlsson/NetBeansProjects/example-nssdb/nssdb_sql
nssDbMode = readWrite
nssModule = keystore

September 3, 2019

Extract Windows Certificate from Windows Certificate Store

Windows certificate stored in Windows Certificate Store (certmgr.msc) are sometime called Sticky Certificate, since they are stored not safe locally and can be extracted.

https://blog.nviso.be/2019/08/28/extracting-certificates-from-the-windows-registry/