项目作者: adorsys

项目描述 :
Working with Java keystore made easier. Use fluent API to generate secret/private keys and certificates, store them in keystore and read them back. Assign arbitary key metadata, write it to keystore with keys and read it back from keystore using sql-like syntax
高级语言: Java
项目地址: git://github.com/adorsys/keystore-management.git
创建时间: 2019-10-21T09:16:28Z
项目社区:https://github.com/adorsys/keystore-management

开源协议:Apache License 2.0

下载


codecov
Maintainability

Project overview

This library allows to generate keys and keystores using fluent-like API instead of dealing with JCA intricacies.
Additionally to key persistence it provides capability of persisting key metadata directly within Java-KeyStore.
Querying keys and their metadata is done using CQEngine under the hood - this allows writing complex queries.
For example one can query for key instanceof SecretKey.

Problems solved with this library

  • AES,RSA,etc. fluent encrypting key generation.
  • RSA,DSA,etc. fluent signing key generation.
  • Fluent storing of key sets into keystore.
  • KeyStore querying for keys by alias, key type, key metadata, etc.
  • KeyStore manipulation - duplicating, changing key protection password, etc.
  • Key metadata persistence directly inside KeyStore.

Using library

Maven

Add dependency (Uses BouncyCastle security provider):

  1. <dependency>
  2. <groupId>de.adorsys.keymanagement</groupId>
  3. <artifactId>juggler-bouncycastle</artifactId>
  4. <version>0.0.6</version>
  5. </dependency>

API description

API flow diagram

API flow

Getting access to services

All services are available through Juggler interface. To obtain instance of it one should call following:

  1. Juggler juggler = DaggerBCJuggler.builder().build();

This call will provide you with default Juggler implementation.

Juggler is composed of 5 services representing different kind of operations:

  • generateKeys() to generate Secret/Private/Signing keys(or their set) from simple template
  • toKeystore() to persist generated key set into keystore
  • readKeys() to read keys from Java keystore and query them by alias/metadata/type/…
  • decode() to decode key bytes read from keystore into i.e. String for PBE raw keys
  • serializeDeserialize() to serialize/deserialize KeyStore to/from byte array.

API examples

Generate keystore

Example:Generate keystore

  1. // Obtain Juggler service instance:
  2. BCJuggler juggler = DaggerBCJuggler.builder().build();
  3. // We want our keystore to have:
  4. KeySetTemplate template = KeySetTemplate.builder()
  5. .providedKey(ProvidedKey.with().alias("MY-KEY").key(stubSecretKey()).build()) // One provided key (i.e. existing) that has alias `MY-KEY`
  6. .generatedSecretKey(Secret.with().prefix("SEC").build()) // One generated secret key that has alias `SEC` + random UUID
  7. .generatedSigningKey(Signing.with().algo("DSA").alias("SIGN").build()) // One generated signing key that has alias `SIGN` + random UUID and uses DSA algorithm
  8. .generatedEncryptionKeys(Encrypting.with().prefix("ENC").build().repeat(10)) // Ten generated private keys (with certificates) that have alias `ENC` + random UUID
  9. .build();
  10. // Provide key protection password:
  11. Supplier<char[]> password = "PASSWORD!"::toCharArray;
  12. // Generate key set
  13. KeySet keySet = juggler.generateKeys().fromTemplate(template);
  14. // Generate KeyStore
  15. KeyStore store = juggler.toKeystore().generate(keySet, password);
  16. // Validate that keystore has 13 keys:
  17. // One provided, one secret, one signing, ten private
  18. assertThat(countKeys(store)).isEqualTo(13);

Change keystore password or clone it

Example:Clone keystore and change key password

  1. // Obtain Juggler service instance:
  2. BCJuggler juggler = DaggerBCJuggler.builder().build();
  3. // We want our keystore to have:
  4. KeySetTemplate template = KeySetTemplate.builder()
  5. .providedKey(ProvidedKey.with().alias("MY-KEY").key(stubSecretKey()).build()) // One provided key (i.e. existing) that has alias `MY-KEY`
  6. .generatedEncryptionKeys(Encrypting.with().prefix("ENC").build().repeat(10)) // Ten generated private keys (with certificates) that have alias `ENC` + random UUID
  7. .build();
  8. // Provide key protection password:
  9. Supplier<char[]> password = "PASSWORD!"::toCharArray;
  10. // Generate key set
  11. KeySet keySet = juggler.generateKeys().fromTemplate(template);
  12. // Generate KeyStore with each key protected with `PASSWORD!` password
  13. KeyStore store = juggler.toKeystore().generate(keySet, password);
  14. // Clone generated KeyStore:
  15. Supplier<char[]> newPassword = "NEW_PASSWORD!"::toCharArray;
  16. // Create key set from old keystore that has new password `NEW_PASSWORD!`:
  17. KeySet clonedSet = juggler.readKeys()
  18. .fromKeyStore(store, id -> password.get())
  19. .copyToKeySet(id -> newPassword.get());
  20. // Generate cloned KeyStore with each key protected with `NEW_PASSWORD!` password (provided on key set)
  21. KeyStore newKeystore = juggler.toKeystore().generate(clonedSet, () -> null);
  22. // Validate old keystore has same key count as new keystore:
  23. assertThat(countKeys(store)).isEqualTo(countKeys(newKeystore));
  24. // Validate old keystore has key password `PASSWORD!`
  25. assertThat(store.getKey("MY-KEY", "PASSWORD!".toCharArray())).isNotNull();
  26. // Validate new keystore has key password `NEW_PASSWORD!`
  27. assertThat(newKeystore.getKey("MY-KEY", "NEW_PASSWORD!".toCharArray())).isNotNull();

Store your own char[] or String securely inside Java Keystore

It is possible your own char sequence in encrypted form inside Keystore using password-based-encryption. This way
you can store any data in form of SecretKey within java KeyStore.

Example:Store your own char array securely in KeyStore

  1. // Obtain Juggler service instance:
  2. BCJuggler juggler = DaggerBCJuggler.builder().build();
  3. // Generate PBE (password-based encryption) raw key (only transformed to be stored in keystore,
  4. // encryption IS PROVIDED by keystore - i.e. BCFKS or UBER keystore provide it):
  5. Supplier<char[]> keyPassword = "WOW"::toCharArray;
  6. ProvidedKey key = juggler.generateKeys().secretRaw(
  7. Pbe.with()
  8. .alias("AES-KEY") // with alias `AES-KEY` if we will save it to keystore from KeySet
  9. .data("MY SECRET DATA Тест!".toCharArray()) // This data will be encrypted inside KeyStore when stored
  10. .password(keyPassword) // Password that will be used to protect key in KeyStore
  11. .build()
  12. );
  13. // Send key to keystore
  14. KeyStore ks = juggler.toKeystore().generate(KeySet.builder().key(key).build());
  15. // Read key back
  16. SecretKeySpec keyFromKeyStore = (SecretKeySpec) ks.getKey("AES-KEY", keyPassword.get());
  17. // Note that BouncyCastle keys are encoded in PKCS12 byte format - UTF-16 big endian + 2 0's padding
  18. assertThat(juggler.decode().decodeAsString(keyFromKeyStore.getEncoded())).isEqualTo("MY SECRET DATA Тест!");

Generate secret key

Example:Generate secret key

  1. // Obtain Juggler service instance:
  2. BCJuggler juggler = DaggerBCJuggler.builder().build();
  3. // Generate key:
  4. Key key = juggler.generateKeys().secret(
  5. Secret.with()
  6. .alias("AES-KEY") // with alias `AES-KEY` if we will save it to keystore from KeySet
  7. .algo("AES") // for AES encryption
  8. .keySize(128) // for AES-128 encryption
  9. .build()
  10. ).getKey();
  11. assertThat(key.getAlgorithm()).isEqualTo("AES");
  12. assertThat(key.getEncoded()).hasSize(16); // 16 * 8 (sizeof byte) = 128 bits

Open and analyze keystore

Example:Query keystore

  1. // Obtain Juggler service
  2. BCJuggler juggler = DaggerBCJuggler.builder().build();
  3. KeySetTemplate template = KeySetTemplate.builder()
  4. .generatedSecretKey(Secret.with().prefix("SEC").build()) // Secret key to be generated with name `SEC` + random UUID
  5. .generatedSigningKey(Signing.with().algo("DSA").alias("SIGN-1").build()) // DSA-based signing key with name `SIGN-1`
  6. .generatedEncryptionKey(Encrypting.with().alias("ENC-1").build()) // Private key with name `ENC-1`
  7. .generatedEncryptionKeys(Encrypting.with().prefix("GEN").build().repeat(10)) // Ten private keys with name `GEN` + random UUID
  8. .build();
  9. // Generate key set from template:
  10. KeySet keySet = juggler.generateKeys().fromTemplate(template);
  11. // Key protection password:
  12. Supplier<char[]> password = "PASSWORD!"::toCharArray;
  13. // Create KeyStore
  14. KeyStore store = juggler.toKeystore().generate(keySet, password);
  15. // Open KeyStore-view to query it:
  16. KeyStoreView source = juggler.readKeys().fromKeyStore(store, id -> password.get());
  17. // Acquire Key-Entry view, so we can query for KeyEntry entities
  18. EntryView<Query<KeyEntry>> entryView = source.entries();
  19. // Query for fact that KeyStore has 13 keys in total:
  20. assertThat(entryView.all()).hasSize(13);
  21. // Query for fact that KeyStore has 1 key with name `SEC`
  22. assertThat(entryView.retrieve("SELECT * FROM keys WHERE alias = 'ENC-1'").toCollection()).hasSize(1);
  23. // Query for fact that KeyStore has 10 keys with prefix `GEN`
  24. assertThat(entryView.retrieve("SELECT * FROM keys WHERE alias LIKE 'GEN%'").toCollection()).hasSize(10);
  25. // Query for fact that KeyStore has 1 secret key:
  26. assertThat(entryView.retrieve("SELECT * FROM keys WHERE is_secret = true").toCollection()).hasSize(1);
  27. // Query for fact that KeyStore has 1 secret key:
  28. assertThat(entryView.privateKeys()).hasSize(12);
  29. // Query for fact that KeyStore has 1 secret key:
  30. assertThat(entryView.secretKeys()).hasSize(1);
  31. // Query for fact that KeyStore has 0 trusted certs:
  32. assertThat(entryView.trustedCerts()).hasSize(0);

Persist key with metadata into keystore

Example:Save metadata to keystore

  1. // Obtain Juggler service
  2. BCJuggler juggler = DaggerBCJuggler.builder()
  3. .metadataPersister(new WithPersister()) // enable metadata persistence
  4. .metadataConfig(
  5. MetadataPersistenceConfig.builder()
  6. .metadataClass(KeyExpirationMetadata.class) // define metadata class
  7. .build()
  8. )
  9. .build();
  10. // Key set template that is going to be saved into KeyStore
  11. KeySetTemplate template = KeySetTemplate.builder()
  12. // One private key that can be used for encryption:
  13. .generatedEncryptionKey(
  14. Encrypting.with()
  15. .alias("ENC-KEY-1") // key with alias `ENC-KEY-1` in KeyStore
  16. .metadata(new KeyExpirationMetadata(Instant.now())) // Associated metadata with this key, pretend it is `expired` key
  17. .build()
  18. )
  19. .build();
  20. // Generate key set:
  21. KeySet keySet = juggler.generateKeys().fromTemplate(template);
  22. // Key protection password:
  23. Supplier<char[]> password = "PASSWORD!"::toCharArray;
  24. // Generate new KeyStore, it will have metadata in it
  25. KeyStore ks = juggler.toKeystore().generate(keySet, password);
  26. // Open KeyStore view to query it:
  27. KeyStoreView source = juggler.readKeys().fromKeyStore(ks, id -> password.get());
  28. // Open alias view to query key alias by metadata
  29. AliasView<Query<KeyAlias>> view = source.aliases();
  30. // Assert that key has been expired
  31. assertThat(
  32. view.retrieve(
  33. and(
  34. has(META), // Key has metadata
  35. lessThan(
  36. attribute(key -> ((KeyExpirationMetadata) key.getMeta()).getExpiresAfter()), // Key expiration date
  37. Instant.now() // current date, so that if expiresAfter < now() key is expired
  38. )
  39. )
  40. ).toCollection()
  41. ).hasSize(1);

Update key in keystore based on its metadata

Example:Rotate expired key in keystore

  1. // Obtain Juggler service
  2. BCJuggler juggler = DaggerBCJuggler.builder()
  3. .metadataPersister(new WithPersister()) // enable metadata persistence
  4. .metadataConfig(
  5. MetadataPersistenceConfig.builder()
  6. .metadataClass(KeyValidity.class) // define metadata class
  7. .build()
  8. )
  9. .build();
  10. // Key protection password:
  11. Supplier<char[]> password = "PASSWORD!"::toCharArray;
  12. // Lazy key template:
  13. Function<Instant, Encrypting> keyTemplate = expiryDate -> Encrypting.with()
  14. .alias("ENC-KEY-1") // key with alias `ENC-KEY-1` in KeyStore// Associated metadata with this key, pretend it is `expired` key
  15. .metadata(new KeyValidity(expiryDate))
  16. .password(password)
  17. .build();
  18. // Key set template that is going to be saved into KeyStore
  19. KeySetTemplate template = KeySetTemplate.builder()
  20. // One private key that can be used for encryption:
  21. .generatedEncryptionKey(
  22. keyTemplate.apply(Instant.now().minusSeconds(10)) // Will pretend that key has expired
  23. )
  24. .build();
  25. // Generate key set:
  26. KeySet keySet = juggler.generateKeys().fromTemplate(template); // Key metadata will indicate that key has expired
  27. // Generate new KeyStore, it will have metadata in it
  28. KeyStore ks = juggler.toKeystore().generate(keySet, () -> null);
  29. // Open KeyStore view to query it:
  30. KeyStoreView source = juggler.readKeys().fromKeyStore(ks, id -> password.get());
  31. // Open alias view to query key alias by metadata
  32. AliasView<Query<KeyAlias>> view = source.aliases();
  33. // Find expired key:
  34. KeyAlias expired = view.uniqueResult(KeyValidity.EXPIRED);
  35. // replace expired key:
  36. view.update(
  37. Collections.singleton(expired),
  38. Collections.singleton(
  39. juggler.generateKeys().encrypting(
  40. keyTemplate.apply(Instant.now().plus(10, ChronoUnit.HOURS)) // Valid for 10 hours from now
  41. )
  42. )
  43. );
  44. // validate there is only one `ENC-KEY-1` key
  45. assertThat(view.retrieve(equal(A_ID, "ENC-KEY-1")).toCollection()).hasSize(1);
  46. // and this key is NOT expired
  47. assertThat(view.retrieve(KeyValidity.EXPIRED).toCollection()).hasSize(0);

Project details

Main service provider - Juggler is built using Dagger2 framework. This allows user to re-compose this service
in his own project by providing replacing modules.

JavaDoc

You can read JavaDoc here