API Java KeyStore

API Java KeyStore

1. Vue d'ensemble

Dans ce didacticiel, nous examinons la gestion des clés et des certificats cryptographiques en Java à l'aide deKeyStore API.

2. Keystores

Si nous avons besoin de gérer des clés et des certificats en Java, nous avons besoin d'unkeystore, which is simply a secure collection of aliased entries of keys and certificates.

Nous sauvegardons généralement les magasins de clés dans un système de fichiers et nous pouvons le protéger avec un mot de passe.

Par défaut, Java a un fichier de stockage de clés situé àJAVA_HOME/jre_ / lib / security / cacerts_. Nous pouvons accéder à ce keystore en utilisant le mot de passe par défaut du keystorechangeit.

Maintenant, avec ce peu d'arrière-plan,let’s get to creating our first one.

3. Création d'un keystore

3.1. Construction

Nous pouvons facilement créer un keystore en utilisantkeytool, ou nous pouvons le faire par programme en utilisant l'APIKeyStore:

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

Ici, nous utilisons le type par défaut, bien qu'il y ait desa few keystore types disponibles commejceks oupcks12.

Nous pouvons remplacer le type par défaut «JKS» (un protocole de stockage de clés propriétaire d'Oracle) en utilisant un paramètre-Dkeystore.type:

-Dkeystore.type=pkcs12

Ou, nous pouvons, bien sûr, lister l'un des formats pris en charge dansgetInstance:

KeyStore ks = KeyStore.getInstance("pcks12");

3.2. Initialisation

Au départ, nous devonsload le keystore:

char[] pwdArray = "password".toCharArray();
ks.load(null, pwdArray);

Nous utilisonsload si nous créons un nouveau magasin de clés ou en ouvrons un existant.

Et, nous disons àKeyStore d'en créer un nouveau en passantnull comme premier paramètre.

Nous fournissons également un mot de passe, qui sera utilisé pour accéder au magasin de clés à l'avenir. Nous pouvons également définir ceci surnull, bien que cela rendrait nos secrets ouverts.

3.3. Espace de rangement

Enfin, nous sauvegardons notre nouveau magasin de clés dans le système de fichiers:

try (FileOutputStream fos = new FileOutputStream("newKeyStoreFileName.jks")) {
    ks.store(fos, pwdArray);
}

Notez que non montrées ci-dessus sont les nombreuses exceptions vérifiées quegetInstance,load, andstore lancent à chaque fois.

4. Chargement d'un keystore

Pour charger un keystore, nous devons d'abord créer une instanceKeyStore, comme auparavant.

Cette fois, cependant, spécifions le format puisque nous en chargeons un existant:

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("newKeyStoreFileName.jks"), pwdArray);

Si notre JVM ne prend pas en charge le type de fichier de clés que nous avons transmis, ou s'il ne correspond pas au type de fichier de clés sur le système de fichiers que nous ouvrons, nous obtiendrons unKeyStoreException:

java.security.KeyStoreException: KEYSTORE_TYPE not found

De plus, si le mot de passe est incorrect, nous obtiendrons unUnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Stockage des entrées

Dans le magasin de clés, nous pouvons stocker trois types d'entrées différents, chaque entrée se trouvant sous son alias:

  • Clés symétriques (appelées clés secrètes dans l'entreprise commune),

  • Clés asymétriques (appelées clés publique et privée dans l'entreprise criminelle commune), et

  • Certificats de confiance

Jetons un coup d'œil à chacun d'eux.

5.1. Enregistrer une clé symétrique

La chose la plus simple que nous puissions stocker dans un fichier de clés est une clé symétrique.

Pour enregistrer une clé symétrique, nous avons besoin de trois éléments:

  1. an alias - c'est simplement le nom que nous utiliserons à l'avenir pour faire référence à l'entrée

  2. a key - qui est enveloppé dans unKeyStore.SecretKeyEntry.

  3. a password - qui est enveloppé dans ce qu'on appelle unProtectionParam.

KeyStore.SecretKeyEntry secret
 = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter password
 = new KeyStore.PasswordProtection(pwdArray);
ks.setEntry("db-encryption-secret", secret, password);

Keep in mind that the password cannot be null, however, it can be an empty*String.* Si nous laissons le mot de passenull  pour une entrée, nous obtiendrons unKeyStoreException:

java.security.KeyStoreException: non-null password required to create SecretKeyEntry

Cela peut sembler un peu étrange que nous ayons besoin d'insérer la clé et le mot de passe dans des classes wrapper.

Nous enveloppons la clé carsetEntry est une méthode générique qui peut également être utilisée pour les autres types d'entrée. Le type d'entrée permet à l'APIKeyStore de la traiter différemment.

Nous enveloppons le mot de passe car leKeyStore API prend en charge les rappels vers les interfaces graphiques et les CLI pour collecter le mot de passe de l'utilisateur final. Consultezthe KeyStore.CallbackHandlerProtection Javadoc pour plus de détails.

Nous pouvons également utiliser cette méthode pour mettre à jour une clé existante. Nous avons juste besoin de le rappeler avec le même alias et mot de passe et nos nouveauxsecret.

5.2. Enregistrer une clé privée

Le stockage de clés asymétriques est un peu plus complexe car nous devons traiter des chaînes de certificats.

De plus, leKeyStore API nous donne une méthode dédiée appeléesetKeyEntry w, qui est plus pratique que la méthode génériquesetEntry .

Ainsi, pour enregistrer une clé asymétrique, nous avons besoin de quatre éléments:

  1. an alias, comme avant

  2. a private key. Étant donné que nous n'utilisons pas la méthode générique, la clé ne sera pas encapsulée. De plus, pour notre cas, il devrait s'agir d'une instance dePrivateKey

  3. a password pour accéder à l'entrée. Cette fois, le mot de passe est obligatoire

  4. a certificate chain qui certifie la clé publique correspondante

X509Certificate[] certificateChain = new X509Certificate[2];
chain[0] = clientCert;
chain[1] = caCert;
ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

Maintenant, beaucoup de choses peuvent mal tourner ici, bien sûr, comme sipwdArray estnull:

java.security.KeyStoreException: password can't be null

Mais, il y a une exception vraiment étrange à connaître, à savoir sipwdArray est un tableau vide:

java.security.UnrecoverableKeyException: Given final block not properly padded

Pour mettre à jour, nous pouvons simplement appeler à nouveau la méthode avec le même alias et un nouveauprivateKey etcertificateChain.

De plus, il peut être utile de faire un rappel rapide surhow to generate a certificate chain.

5.3. Enregistrer un certificat de confiance

Storing trusted certificates is quite simple. It only requires the alias and the certificate itself, qui est de typeCertificate:

ks.setCertificateEntry("google.com", trustedCertificate);

En général, le certificat est un certificat que nous n’avons pas généré, mais qui provient d’un tiers.

Pour cette raison, il est important de noter ici queKeyStore ne vérifie pas réellement ce certificat. Nous devrions le vérifier nous-mêmes avant de le stocker.

Pour mettre à jour, nous pouvons simplement appeler à nouveau la méthode avec le même alias et un nouveautrustedCertificate.

6. Lecture des entrées

Maintenant que nous avons écrit quelques entrées, nous voudrons certainement les lire.

6.1. Lire une seule entrée

Premièrement, nous pouvons extraire les clés et les certificats par leur alias:

Key ssoSigningKey = ks.getKey("sso-signing-key", pwdArray);
Certificate google = ks.getCertificate("google.com");

S'il n'y a aucune entrée portant ce nom ou si elle est d'un type différent, alorsgetKey  renvoie simplementnull:

public void whenEntryIsMissingOrOfIncorrectType_thenReturnsNull() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"

   Assert.assertNull(ks.getKey("some-other-api-secret"));
   Assert.assertNotNull(ks.getKey("widget-api-secret"));
   Assert.assertNull(ks.getCertificate("widget-api-secret"));
}

Mais, si le mot de passe de la clé est incorrect,we’ll get that same odd error we talked about earlier:

java.security.UnrecoverableKeyException: Given final block not properly padded

6.2. Vérifier si un keystore contient un alias

PuisqueKeyStore ne stocke que les entrées en utilisant unMap, il expose la possibilité de vérifier l'existence sans récupérer l'entrée:

public void whenAddingAlias_thenCanQueryWithoutSaving() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.containsAlias("some-other-api-secret"));
}

6.3. Vérification du type d'entrée

Ou,KeyStore#entryInstanceOf est un peu plus puissant.

C'est commecontainsAlias, sauf qu'il vérifie également le type d'entrée:

public void whenAddingAlias_thenCanQueryByType() {
    // ... initialize keystore
    // ... add a secret entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.entryInstanceOf(
      "widget-api-secret",
      KeyType.PrivateKeyEntry.class));
}

7. Supprimer des entrées

KeyStore, bien sûr,  prend en charge la suppression des entrées que nous avons ajoutées:

public void whenDeletingAnAlias_thenIdempotent() {
    // ... initialize a keystore
    // ... add an entry called "widget-api-secret"
    assertEquals(ks.size(), 1);
    ks.deleteEntry("widget-api-secret");
    ks.deleteEntry("some-other-api-secret");
    assertFalse(ks.size(), 0);
}

Heureusement,deleteEntry  est idempotent, donc la méthode réagit de la même manière, que l'entrée existe ou non.

8. Suppression d'un keystore

Si nous voulons supprimer notre magasin de clés, l'API ne nous aide pas, mais nous pouvons toujours utiliser Java pour le faire:

Files.delete(Paths.get(keystorePath));

Sinon, nous pouvons conserver le magasin de clés et supprimer simplement les entrées:

Enumeration aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    keyStore.deleteEntry(alias);
}

9. Conclusion

Dans cet article, nous avons parlé de la gestion des certificats et des clés à l'aide deKeyStore API. Nous avons expliqué ce qu'est un magasin de clés, comment en créer, en charger et en supprimer un, à la manière de stocker une clé ou un certificat dans le magasin de clés et au chargement et à la mise à jour des entrées existantes avec de nouvelles valeurs.

L'implémentation complète de l'exemple peut être trouvéeover on Github.