From dedd16f584c6c6d2811fa667097cb83a11fbba58 Mon Sep 17 00:00:00 2001 From: Leo Galambos Date: Thu, 30 Jul 2026 20:28:43 +0200 Subject: [PATCH] chore(text): source code format --- app/src/main/java/zeroecho/Guard.java | 58 +- app/src/main/java/zeroecho/Kem.java | 201 ++++--- .../java/zeroecho/KeyStoreManagement.java | 25 +- .../main/java/zeroecho/KeyringUnlocks.java | 3 +- app/src/main/java/zeroecho/Tag.java | 22 +- app/src/test/java/zeroecho/GuardTest.java | 28 +- app/src/test/java/zeroecho/KemTest.java | 11 +- .../java/zeroecho/KeyStoreManagementTest.java | 11 +- app/src/test/java/zeroecho/TagTest.java | 31 +- .../java/zeroecho/TestKeyringUnlocks.java | 3 +- .../covert/jpeg/JpegExifIntegrationTest.java | 23 +- .../main/java/zeroecho/core/Capability.java | 26 +- .../java/zeroecho/core/CryptoAlgorithm.java | 115 ++-- .../java/zeroecho/core/CryptoAlgorithms.java | 6 +- .../java/zeroecho/core/CryptoCatalog.java | 56 +- .../java/zeroecho/core/KeyOperationInfo.java | 18 +- .../core/alg/AbstractCryptoAlgorithm.java | 4 +- .../core/alg/aes/AesCipherContext.java | 12 +- .../core/alg/aes/AesKeyImportSpec.java | 4 +- .../chacha/AbstractChaChaCipherContext.java | 9 +- .../core/alg/chacha/ChaChaAlgorithm.java | 1 + .../core/alg/chacha/package-info.java | 8 +- .../agreement/GenericJcaAgreementContext.java | 6 +- .../core/alg/common/eddsa/package-info.java | 6 +- .../common/sig/SignatureInteropProfile.java | 3 +- .../core/alg/common/sig/package-info.java | 6 +- .../core/alg/digest/Sha2Sha3Algorithm.java | 4 +- .../zeroecho/core/alg/ecdh/EcdhAlgorithm.java | 3 +- .../core/alg/ecdh/EcdhKeyGenBuilder.java | 4 +- .../core/alg/ecdsa/EcdsaAlgorithm.java | 7 +- .../core/alg/ecdsa/EcdsaKeyGenBuilder.java | 14 +- .../alg/ecdsa/EcdsaPrivateKeyBuilder.java | 8 +- .../core/alg/ecdsa/EcdsaPublicKeyBuilder.java | 8 +- .../alg/ed25519/Ed25519KeyGenBuilder.java | 4 +- .../alg/ed25519/Ed25519PrivateKeyBuilder.java | 4 +- .../alg/ed25519/Ed25519PublicKeyBuilder.java | 4 +- .../alg/ed448/Ed448PrivateKeyBuilder.java | 3 +- .../core/alg/ed448/Ed448PublicKeyBuilder.java | 3 +- .../core/alg/kyber/KyberKeyGenSpec.java | 3 +- .../core/alg/kyber/KyberPrivateKeySpec.java | 6 +- .../zeroecho/core/alg/rsa/BlockGeometry.java | 3 +- .../core/alg/saber/SaberKeyGenSpec.java | 3 +- .../sphincsplus/SphincsPlusKeyGenBuilder.java | 10 +- .../SphincsPlusPrivateKeyBuilder.java | 6 +- .../SphincsPlusPrivateKeySpec.java | 3 +- .../SphincsPlusPublicKeyBuilder.java | 6 +- .../core/alg/xdh/XdhKeyGenBuilder.java | 3 +- .../zeroecho/core/audit/AuditListeners.java | 10 +- .../java/zeroecho/core/audit/AuditMode.java | 6 +- .../zeroecho/core/audit/AuditedContexts.java | 13 +- .../core/audit/JulAuditListenerStd.java | 9 +- .../core/err/UnsupportedRoleException.java | 6 +- .../io/AbstractChunkTransformInputStream.java | 17 +- .../io/CipherTransformInputStreamBuilder.java | 12 +- .../zeroecho/core/io/SmartBlockStream.java | 1 + .../core/io/SmartPaddedBlockStream.java | 1 + .../java/zeroecho/core/io/package-info.java | 8 +- .../java/zeroecho/core/marshal/PairSeq.java | 11 +- .../zeroecho/core/marshal/PairSeqCodec.java | 17 +- .../zeroecho/core/spec/AlgorithmKeySpec.java | 1 - .../core/spi/AsymmetricKeyPairGenerator.java | 3 +- .../zeroecho/core/spi/ContextFactoryKS.java | 8 +- .../core/spi/KeyringUnlockProvider.java | 6 +- .../zeroecho/core/spi/PrivateKeyImporter.java | 4 +- .../java/zeroecho/core/spi/package-info.java | 18 +- .../core/storage/KeyringException.java | 20 +- .../core/storage/KeyringFileOperations.java | 18 +- .../core/storage/KeyringImportRegistry.java | 193 +++---- .../storage/KeyringNonceReservationKdf.java | 15 +- .../core/storage/KeyringPassword.java | 14 +- .../core/storage/KeyringProtection.java | 7 +- .../core/storage/KeyringRandomBytes.java | 6 +- .../zeroecho/core/storage/KeyringStore.java | 492 ++++++++--------- .../zeroecho/core/storage/package-info.java | 18 +- .../zeroecho/core/tag/TagEngineBuilder.java | 27 +- .../main/java/zeroecho/sdk/KeyBuilders.java | 80 +-- .../main/java/zeroecho/sdk/Pbkdf2Limits.java | 11 +- .../java/zeroecho/sdk/ZeroEchoSession.java | 38 +- .../sdk/builders/HybridKexBuilder.java | 6 +- .../SignatureTrailerDataContentBuilder.java | 17 +- .../alg/ChaChaDataContentBuilder.java | 6 +- .../alg/DigestDataContentBuilder.java | 1 + .../alg/ElgamalEncDataContentBuilder.java | 6 +- .../builders/alg/HmacDataContentBuilder.java | 4 +- .../alg/RsaEncDataContentBuilder.java | 6 +- .../alg/RsaSigDataContentBuilder.java | 1 + .../zeroecho/sdk/builders/package-info.java | 3 +- .../sdk/content/builtin/SecretPassword.java | 4 +- .../java/zeroecho/sdk/guard/Decryptor.java | 16 +- .../java/zeroecho/sdk/guard/Encryptor.java | 6 +- .../zeroecho/sdk/guard/KemCtxRecipient.java | 6 +- .../zeroecho/sdk/guard/KemKeyDerivation.java | 6 +- .../sdk/guard/MultiRecipientContent.java | 16 +- .../MultiRecipientDataSourceBuilder.java | 26 +- .../zeroecho/sdk/guard/PasswordOpener.java | 1 + .../zeroecho/sdk/guard/PasswordRecipient.java | 4 +- .../zeroecho/sdk/guard/RecipientKekSizes.java | 6 +- .../zeroecho/sdk/guard/UnlockMaterial.java | 12 +- .../java/zeroecho/sdk/guard/package-info.java | 4 +- .../sdk/hybrid/derived/HybridDerived.java | 12 +- .../sdk/hybrid/kex/HybridKexContext.java | 13 +- .../sdk/hybrid/kex/HybridKexContexts.java | 30 +- .../sdk/hybrid/kex/HybridKexExporter.java | 6 +- .../signature/HybridSignatureContext.java | 6 +- .../signature/HybridSignatureContexts.java | 3 +- .../main/java/zeroecho/sdk/util/Password.java | 1 - .../core/CapabilityValueSemanticsTest.java | 8 +- .../zeroecho/core/CatalogContractTest.java | 13 +- .../core/CryptoAlgorithmsAuditWrapTest.java | 8 +- .../zeroecho/core/CryptoArchitectureTest.java | 23 +- .../core/SecretSpecLifecycleTest.java | 32 +- .../zeroecho/core/TargetArchitectureTest.java | 32 +- .../ZeroEchoSessionWrapIntegrationTest.java | 74 ++- .../alg/aes/AesDecryptionSecurityTest.java | 46 +- .../core/alg/aes/AesRandomSupportTest.java | 3 +- .../core/alg/chacha/ChaChaLargeDataTest.java | 48 +- .../alg/chacha/ChaChaNonceLifecycleTest.java | 8 +- .../AgreementAlgorithmsRoundTripTest.java | 20 +- .../core/alg/ecdsa/EcdsaLargeDataTest.java | 9 +- .../alg/ed25519/Ed25519LargeDataTest.java | 9 +- .../core/alg/ed448/Ed448LargeDataTest.java | 9 +- .../alg/elgamal/ElgamalLargeDataTest.java | 24 +- .../core/alg/hmac/HmacLargeDataTest.java | 3 +- .../core/alg/mldsa/MldsaLargeDataTest.java | 9 +- .../core/alg/rsa/BlockGeometryTest.java | 9 +- .../core/alg/rsa/RsaLargeDataTest.java | 18 +- .../core/alg/slhdsa/SlhDsaLargeDataTest.java | 9 +- .../audit/AuditedContextsRegressionTest.java | 23 +- .../JulAuditListenerStdSecurityTest.java | 4 +- ...CipherTransformInputStreamBuilderTest.java | 16 +- .../zeroecho/core/marshal/PairSeqTest.java | 7 +- .../storage/KeyringAlgorithmCoverageTest.java | 149 ++---- .../storage/KeyringAtomicPersistenceTest.java | 130 ++--- .../KeyringCryptographicFormatTest.java | 388 ++++++-------- .../KeyringFilesystemSecurityTest.java | 104 ++-- .../storage/KeyringImportRegistryTest.java | 76 +-- .../storage/KeyringNonceReservationTest.java | 72 +-- .../core/storage/KeyringStoreTest.java | 76 +-- .../sdk/ZeroEchoSessionDestroyKeyTest.java | 5 +- .../sdk/builders/HybridKexBuilderTest.java | 212 ++++---- .../TagTrailerDataContentBuilderTest.java | 227 +++++--- .../builders/alg/KemHybridRoundTripTest.java | 34 +- .../builders/alg/SessionBoundBuilderTest.java | 19 +- .../sdk/guard/DecryptorCekCleanupTest.java | 7 +- .../sdk/guard/EncryptorCekAllocationTest.java | 16 +- .../sdk/guard/KemRecipientLifecycleTest.java | 12 +- .../sdk/guard/MultiRecipientEnvelopeTest.java | 384 +++++++------ .../sdk/guard/PasswordRecipientTest.java | 33 +- .../SessionRecipientOpenerContractTest.java | 18 +- .../sdk/hybrid/derived/HybridDerivedTest.java | 38 +- .../hybrid/kex/HybridKexFrameCodecTest.java | 14 +- .../sdk/hybrid/kex/HybridKexTest.java | 36 +- .../hybrid/signature/HybridSignatureTest.java | 85 ++- .../java/zeroecho/pki/api/ProfileService.java | 4 +- .../pki/api/credential/Credential.java | 11 +- .../EffectiveCredentialStatusResolver.java | 8 +- .../pki/api/issuance/VerificationPolicy.java | 7 +- .../pki/api/orch/SigningSubmissionId.java | 25 +- .../api/profile/ActiveCertificateProfile.java | 5 +- .../BuiltInCertificateProfileCatalog.java | 64 +-- .../BuiltInCertificateProfileTemplate.java | 15 +- .../pki/api/profile/CaCertificatePolicy.java | 11 +- .../pki/api/profile/CertificateProfile.java | 24 +- .../profile/CertificateProfileDefinition.java | 25 +- .../CertificateProfileDocumentCodec.java | 226 +++----- .../api/profile/CertificateProfileRef.java | 14 +- .../ImportedCertificateProfileVersion.java | 16 +- .../api/profile/LeafCertificatePolicy.java | 19 +- .../profile/SubjectAlternativeNamePolicy.java | 30 +- .../profile/SubjectAlternativeNameRule.java | 19 +- .../pki/api/profile/SubjectRdnRule.java | 16 +- .../request/ParsedCertificationRequest.java | 48 +- .../api/request/SubjectAlternativeName.java | 7 +- .../pki/api/revocation/RevocationCommand.java | 8 +- .../pki/api/revocation/RevocationJournal.java | 6 +- .../api/revocation/RevocationTransition.java | 8 +- .../pki/impl/ProfileLifecycleFailure.java | 19 +- .../core/CaCertificateProfileValidator.java | 29 +- .../zeroecho/pki/impl/core/CaProofGate.java | 10 +- .../core/CertificateProfileValidator.java | 27 +- .../impl/core/CredentialProfileBindings.java | 9 +- .../pki/impl/core/CredentialSnapshots.java | 3 +- .../pki/impl/core/CredentialTrustAudit.java | 6 +- .../pki/impl/core/DefaultCaService.java | 188 +++---- .../pki/impl/core/DefaultIssuanceService.java | 53 +- .../pki/impl/core/DefaultProfileService.java | 47 +- .../impl/core/DefaultRevocationService.java | 12 +- .../impl/core/DefaultStatusObjectService.java | 34 +- .../core/ValidatedCaCertificateRequest.java | 17 +- .../core/ValidatedCertificateRequest.java | 3 +- .../pki/impl/core/async/PkiSigningBus.java | 86 ++- .../ZeroEchoLibSignatureWorkflow.java | 101 ++-- .../ZeroEchoLibSignatureWorkflowProvider.java | 55 +- .../bc/BcX509CertificationRequestParser.java | 5 +- .../x509/bc/BcX509CredentialFramework.java | 10 +- .../bc/BcX509CredentialIssuerBackend.java | 22 +- .../x509/bc/BcX509ProfileSupport.java | 14 +- .../x509/bc/BcX509StatusObjectGenerator.java | 5 +- .../x509/bc/PkiBusContentSigner.java | 3 +- .../pki/impl/fs/FilesystemPkiStore.java | 147 +++-- .../java/zeroecho/pki/impl/fs/FsCodec.java | 218 +++----- .../zeroecho/pki/impl/fs/FsOperations.java | 22 +- .../java/zeroecho/pki/impl/fs/FsPaths.java | 10 +- .../pki/impl/fs/FsSnapshotExporter.java | 9 +- .../pki/impl/fs/ValidatedImportedProfile.java | 27 +- .../zeroecho/pki/impl/fs/package-info.java | 6 +- .../pki/spi/bootstrap/PkiBootstrap.java | 16 +- .../pki/spi/crypto/SignatureWorkflow.java | 58 +- .../spi/crypto/SignatureWorkflowProvider.java | 22 +- .../SignatureWorkflowRuntimeDependencies.java | 28 +- .../spi/framework/CredentialFramework.java | 4 +- .../framework/CredentialIssuerBackend.java | 30 +- .../zeroecho/pki/spi/framework/CrlEntry.java | 10 +- .../spi/framework/StatusObjectGenerator.java | 6 +- .../java/zeroecho/pki/spi/store/PkiStore.java | 18 +- .../pki/spi/store/SignWorkflowStore.java | 75 ++- .../BuiltInCertificateProfileCatalogTest.java | 171 +++--- .../BuiltInProfileImportIntegrityTest.java | 16 +- .../CertificateProfileDocumentCodecTest.java | 413 ++++++-------- .../e2e/CaProfileIssuanceEnforcementTest.java | 363 +++++++------ .../pki/e2e/H7EndEntityAcceptanceE2eTest.java | 354 +++++------- .../e2e/H7EndEntityCsrRejectionE2eTest.java | 129 +++-- .../java/zeroecho/pki/e2e/PkiCoreE2eTest.java | 77 ++- .../zeroecho/pki/e2e/PkiProofGateE2eTest.java | 505 +++++++++--------- .../impl/core/DefaultProfileServiceTest.java | 79 ++- .../core/DefaultRevocationServiceTest.java | 8 +- .../DefaultStatusObjectServiceCrlTest.java | 136 ++--- .../impl/core/H7ProfileEnforcementTest.java | 111 ++-- ...EffectiveCredentialStatusResolverTest.java | 51 +- .../core/async/PkiSigningBusFailureTest.java | 138 ++--- .../PkiSigningBusOperatorApprovalTest.java | 7 +- .../zeroecholib/TestKeyringUnlocks.java | 3 +- .../ZeroEchoLibKeyRefParsingTest.java | 16 +- ...hoLibSignatureWorkflowPersistenceTest.java | 59 +- ...gnatureWorkflowVerifyEncodedEcdsaTest.java | 6 +- ...LibSignatureWorkflowVerifyEncodedTest.java | 6 +- .../bc/PkiBusContentSignerCleanupTest.java | 5 +- ...WorkflowProofOfPossessionVerifierTest.java | 8 +- .../fs/FilesystemPkiStoreOwnershipTest.java | 26 +- .../pki/impl/fs/FilesystemPkiStoreTest.java | 46 +- .../fs/FilesystemProfileLifecycleTest.java | 78 ++- .../fs/FilesystemRevocationJournalTest.java | 227 ++++---- .../fs/FilesystemSignWorkflowStoreTest.java | 211 ++++---- .../zeroecho/pki/impl/fs/FsCodecTest.java | 42 +- .../pki/spi/bootstrap/PkiBootstrapTest.java | 109 ++-- .../pki/spi/framework/CrlEntryTest.java | 14 +- .../DurableDelayedSignatureWorkflow.java | 4 +- ...ableOperatorApprovalSignatureWorkflow.java | 4 +- .../pki/testkit/H7ProfileDocuments.java | 119 ++--- .../testkit/InMemorySignatureWorkflow.java | 8 +- .../OperatorApprovalSignatureWorkflow.java | 3 +- .../pki/testkit/PkiSigningBusWorker.java | 6 +- .../zeroecho/pki/testkit/PkiTestRuntime.java | 22 +- .../pki/util/async/DurableAsyncBusTest.java | 78 ++- samples/src/test/java/demo/AesTest.java | 9 +- .../test/java/demo/AgreementVariantsTest.java | 6 +- .../test/java/demo/CombinedDeliveryTest.java | 9 +- .../java/demo/HybridDerivedAesDemoTest.java | 9 +- .../src/test/java/demo/HybridKexDemoTest.java | 25 +- .../test/java/demo/HybridSigningAesTest.java | 36 +- .../src/test/java/demo/PostQuantumTest.java | 3 +- .../src/test/java/demo/SigningAesTest.java | 27 +- 262 files changed, 4867 insertions(+), 5717 deletions(-) diff --git a/app/src/main/java/zeroecho/Guard.java b/app/src/main/java/zeroecho/Guard.java index d036ca9..7c2ad60 100644 --- a/app/src/main/java/zeroecho/Guard.java +++ b/app/src/main/java/zeroecho/Guard.java @@ -138,18 +138,16 @@ public final class Guard { /** * Executes Guard with an explicit keyring unlock source. * - * @param args command arguments - * @param options dispatcher options + * @param args command arguments + * @param options dispatcher options * @param keyringUnlockProvider destroyable-password provider * @return process exit code - * @throws ParseException if parsing fails - * @throws IOException if I/O fails + * @throws ParseException if parsing fails + * @throws IOException if I/O fails * @throws GeneralSecurityException if cryptographic processing fails */ - @SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity", - "PMD.CyclomaticComplexity", "PMD.NPathComplexity" }) - public static int main(final String[] args, final Options options, - KeyringUnlockProvider keyringUnlockProvider) + @SuppressWarnings({ "PMD.NcssCount", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" }) + public static int main(final String[] args, final Options options, KeyringUnlockProvider keyringUnlockProvider) throws ParseException, IOException, GeneralSecurityException { // ---- operation selection final Option OPT_ENCRYPT = Option.builder("e").longOpt("encrypt").hasArg().argName("in-file") @@ -206,8 +204,7 @@ public final class Guard { .desc("Recipient KEK length: exactly 16 or 32 bytes (default 32)").get(); final Option OPT_PBKDF2_MAX = Option.builder().longOpt("pbkdf2-max").hasArg().argName("iterations") .desc("Operational PBKDF2 ceiling; required for password operations").get(); - final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg() - .argName("iterations") + final Option OPT_PBKDF2_HARD_MAX = Option.builder().longOpt("pbkdf2-hard-max").hasArg().argName("iterations") .desc("Absolute decoded PBKDF2 safety ceiling; required for password operations").get(); // ---- decoys (all types) @@ -260,8 +257,7 @@ public final class Guard { final CommandLine cmd = parser.parse(options, args); final boolean passwordOperation = cmd.hasOption(OPT_TO_PSW) || cmd.hasOption(OPT_DECOY_PSW) || cmd.hasOption(OPT_DECOY_PSW_RAND) || cmd.hasOption(OPT_PASSWORD); - final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX, - passwordOperation); + final ZeroEchoSession session = createSession(cmd, OPT_PBKDF2_MAX, OPT_PBKDF2_HARD_MAX, passwordOperation); final boolean encrypt = cmd.hasOption(OPT_ENCRYPT); final Path inPath = Paths.get(cmd.getOptionValue(encrypt ? OPT_ENCRYPT : OPT_DECRYPT)); @@ -359,8 +355,7 @@ public final class Guard { // envelope builder (new API) final MultiRecipientDataSourceBuilder env = MultiRecipientDataSourceBuilder.builder(session) - .payloadKeyBytes(cekBytes) - .headerLimits(maxRecipients, maxEntryLen); + .payloadKeyBytes(cekBytes).headerLimits(maxRecipients, maxEntryLen); UnlockMaterial borrowedUnlockMaterial = null; try (env) { if (aes != null) { @@ -374,11 +369,10 @@ public final class Guard { if (encrypt) { final int iter = Integer.parseInt(cmd.getOptionValue(OPT_PSW_ITER, "200000")); final int saltLen = Integer.parseInt(cmd.getOptionValue(OPT_PSW_SALT, "16")); - final int kekLen = RecipientKekSizes.requireSupported( - Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32"))); + final int kekLen = RecipientKekSizes + .requireSupported(Integer.parseInt(cmd.getOptionValue(OPT_PSW_KEK, "32"))); - KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING, - keyringUnlockProvider); + KeyringStore ks = loadKeyringIfPresent(cmd, OPT_KEYRING, keyringUnlockProvider); try (ks) { for (String alias : cmd.getOptionValues(OPT_TO_ALIAS) == null ? new String[0] : cmd.getOptionValues(OPT_TO_ALIAS)) { @@ -424,8 +418,7 @@ public final class Guard { throw new ParseException("Specify exactly one of --priv-alias or --password for decryption"); } if (privAlias != null) { - try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING, - keyringUnlockProvider)) { + try (KeyringStore ks = requireKeyring(cmd, OPT_KEYRING, keyringUnlockProvider)) { final KeyringStore.PrivateWithId pr = ks.getPrivateWithId(privAlias); borrowedUnlockMaterial = new UnlockMaterial.Private(pr.key()); } @@ -474,8 +467,7 @@ public final class Guard { boolean hasAbsoluteMaximum = cmd.hasOption(absoluteMaximumOption); if (!hasOperationalMaximum && !hasAbsoluteMaximum) { if (passwordOperation) { - throw new ParseException( - "Password operations require --pbkdf2-max and --pbkdf2-hard-max"); + throw new ParseException("Password operations require --pbkdf2-max and --pbkdf2-hard-max"); } return new ZeroEchoSession(); } @@ -485,11 +477,9 @@ public final class Guard { try { int operationalMaximum = Integer.parseInt(cmd.getOptionValue(operationalMaximumOption)); int absoluteMaximum = Integer.parseInt(cmd.getOptionValue(absoluteMaximumOption)); - return new ZeroEchoSession().withPbkdf2Limits( - new Pbkdf2Limits(operationalMaximum, absoluteMaximum)); + return new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(operationalMaximum, absoluteMaximum)); } catch (IllegalArgumentException exception) { - ParseException parseException = - new ParseException("Invalid PBKDF2 limits: " + exception.getMessage()); + ParseException parseException = new ParseException("Invalid PBKDF2 limits: " + exception.getMessage()); parseException.initCause(exception); throw parseException; } @@ -524,9 +514,9 @@ public final class Guard { * * *

- * In both cases, the created context is consumed by - * the matching {@link MultiRecipientDataSourceBuilder} recipient method and is - * closed internally by the resulting content. + * In both cases, the created context is consumed by the matching + * {@link MultiRecipientDataSourceBuilder} recipient method and is closed + * internally by the resulting content. *

* * @param env target builder to which the recipient is added @@ -542,8 +532,8 @@ public final class Guard { */ @SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" }) private static void addRecipientFromAlias(ZeroEchoSession session, MultiRecipientDataSourceBuilder env, - KeyringStore ks, String alias, - int kekBytes, int saltLen, boolean decoy) throws GeneralSecurityException, IOException { + KeyringStore ks, String alias, int kekBytes, int saltLen, boolean decoy) + throws GeneralSecurityException, IOException { KeyringStore.PublicWithId r = ks.getPublicWithId(alias); final String algId = r.algorithm(); final java.security.PublicKey pub = r.key(); @@ -602,16 +592,14 @@ public final class Guard { } private static KeyringStore loadKeyringIfPresent(CommandLine cmd, Option optKs, - KeyringUnlockProvider unlockProvider) - throws IOException, GeneralSecurityException { + KeyringUnlockProvider unlockProvider) throws IOException, GeneralSecurityException { if (!cmd.hasOption(optKs)) { return null; } return KeyringUnlocks.open(Paths.get(cmd.getOptionValue(optKs)), unlockProvider); } - private static KeyringStore requireKeyring(CommandLine cmd, Option optKs, - KeyringUnlockProvider unlockProvider) + private static KeyringStore requireKeyring(CommandLine cmd, Option optKs, KeyringUnlockProvider unlockProvider) throws IOException, ParseException, GeneralSecurityException { if (!cmd.hasOption(optKs)) { throw new ParseException("--keyring is required when aliases are used"); diff --git a/app/src/main/java/zeroecho/Kem.java b/app/src/main/java/zeroecho/Kem.java index 6d9d9d1..81dc966 100644 --- a/app/src/main/java/zeroecho/Kem.java +++ b/app/src/main/java/zeroecho/Kem.java @@ -210,30 +210,27 @@ public final class Kem { // NOPMD * @param args command arguments * @param opts command options * @return process exit code - * @throws ParseException if arguments are invalid - * @throws IOException if I/O fails + * @throws ParseException if arguments are invalid + * @throws IOException if I/O fails * @throws GeneralSecurityException if cryptographic processing fails */ - public static int main(String[] args, Options opts) - throws ParseException, IOException, GeneralSecurityException { + public static int main(String[] args, Options opts) throws ParseException, IOException, GeneralSecurityException { return main(args, opts, KeyringUnlocks.console()); } /** * Executes the KEM command with an explicit keyring unlock source. * - * @param args command arguments - * @param opts command options + * @param args command arguments + * @param opts command options * @param unlockProvider keyring password provider * @return process exit code - * @throws ParseException if arguments are invalid - * @throws IOException if I/O fails + * @throws ParseException if arguments are invalid + * @throws IOException if I/O fails * @throws GeneralSecurityException if cryptographic processing fails */ - @SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", - "PMD.NPathComplexity" }) - public static int main(String[] args, Options opts, - KeyringUnlockProvider unlockProvider) + @SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", "PMD.NPathComplexity" }) + public static int main(String[] args, Options opts, KeyringUnlockProvider unlockProvider) throws ParseException, IOException, GeneralSecurityException { ZeroEchoSession session = new ZeroEchoSession(); defineOptions(opts); @@ -275,105 +272,105 @@ public final class Kem { // NOPMD final Path keyringPath = Path.of(cmd.getOptionValue(OPT_KEYRING.getLongOpt())); try (KeyringStore keyring = KeyringUnlocks.open(keyringPath, unlockProvider)) { - // Configure KEM envelope - KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId); - if (cmd.hasOption(OPT_DIRECT.getLongOpt())) { - kem = kem.directSecret(); - } else { - byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes()); - kem = kem.hkdfSha256(info); - } - // typed numeric options - Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES); - if (keyBytes != null) { - kem = kem.derivedKeyBytes(keyBytes); - } - Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT); - if (maxKemCt != null) { - kem = kem.maxKemCiphertextLen(maxKemCt); - } + // Configure KEM envelope + KemDataContentBuilder kem = KemDataContentBuilder.builder(session).kem(kemId); + if (cmd.hasOption(OPT_DIRECT.getLongOpt())) { + kem = kem.directSecret(); + } else { + byte[] info = parseOptionalHex(cmd, OPT_HKDF, "ZeroEcho-KEM".getBytes()); + kem = kem.hkdfSha256(info); + } + // typed numeric options + Integer keyBytes = parsedIntOpt(cmd, OPT_KEY_BYTES); + if (keyBytes != null) { + kem = kem.derivedKeyBytes(keyBytes); + } + Integer maxKemCt = parsedIntOpt(cmd, OPT_MAX_KEM_CT); + if (maxKemCt != null) { + kem = kem.maxKemCiphertextLen(maxKemCt); + } - // Common symmetric knobs - final byte[] aad = parseHexOpt(cmd, OPT_AAD); - final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt()); + // Common symmetric knobs + final byte[] aad = parseHexOpt(cmd, OPT_AAD); + final boolean wantHeader = cmd.hasOption(OPT_HEADER.getLongOpt()); - // AES payload - if (wantAes) { - String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT); - AesDataContentBuilder aes = AesDataContentBuilder.builder(session); - switch (mode) { - case "gcm" -> { - Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS); - int tagBits = tagBitsOpt == null ? 128 : tagBitsOpt; + // AES payload + if (wantAes) { + String mode = cmd.getOptionValue(OPT_AES_CIPHER.getLongOpt(), "gcm").toLowerCase(Locale.ROOT); + AesDataContentBuilder aes = AesDataContentBuilder.builder(session); + switch (mode) { + case "gcm" -> { + Integer tagBitsOpt = parsedIntOpt(cmd, OPT_AES_TAG_BITS); + int tagBits = tagBitsOpt == null ? 128 : tagBitsOpt; - aes = aes.modeGcm(tagBits); + aes = aes.modeGcm(tagBits); + } + case "ctr" -> aes = aes.modeCtr(); + case "cbc" -> aes = aes.modeCbcPkcs5(); + default -> throw new ParseException("Unsupported --aes-cipher: " + mode); } - case "ctr" -> aes = aes.modeCtr(); - case "cbc" -> aes = aes.modeCbcPkcs5(); - default -> throw new ParseException("Unsupported --aes-cipher: " + mode); + byte[] iv = parseHexOpt(cmd, OPT_AES_IV); + if (iv != null) { + aes = aes.withDecryptionIv(iv); + } + if (aad != null && aad.length > 0) { + aes = aes.withAad(aad); + } + if (wantHeader) { + aes = aes.withHeader(); + } + kem = kem.withAes(aes); } - byte[] iv = parseHexOpt(cmd, OPT_AES_IV); - if (iv != null) { - aes = aes.withDecryptionIv(iv); - } - if (aad != null && aad.length > 0) { - aes = aes.withAad(aad); - } - if (wantHeader) { - aes = aes.withHeader(); - } - kem = kem.withAes(aes); - } - // ChaCha payload - if (wantChaCha) { - ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session); - byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE); - if (nonce != null) { - cc = cc.withDecryptionNonce(nonce); + // ChaCha payload + if (wantChaCha) { + ChaChaDataContentBuilder cc = ChaChaDataContentBuilder.builder(session); + byte[] nonce = parseHexOpt(cmd, OPT_CHACHA_NONCE); + if (nonce != null) { + cc = cc.withDecryptionNonce(nonce); + } + // counter is an integer, not bytes; use typed parsed option + Integer counter = parsedIntOpt(cmd, OPT_CHACHA_COUNTER); + if (counter != null) { + cc = cc.withCounter(counter); + } + Integer initial = parsedIntOpt(cmd, OPT_CHACHA_INITIAL); + if (initial != null) { + cc = cc.initialCounter(initial); + } + if (aad != null && aad.length > 0) { + cc = cc.withAad(aad); // selects AEAD + } + if (wantHeader) { + cc = cc.withHeader(); + } + kem = kem.withChaCha(cc); } - // counter is an integer, not bytes; use typed parsed option - Integer counter = parsedIntOpt(cmd, OPT_CHACHA_COUNTER); - if (counter != null) { - cc = cc.withCounter(counter); - } - Integer initial = parsedIntOpt(cmd, OPT_CHACHA_INITIAL); - if (initial != null) { - cc = cc.initialCounter(initial); - } - if (aad != null && aad.length > 0) { - cc = cc.withAad(aad); // selects AEAD - } - if (wantHeader) { - cc = cc.withHeader(); - } - kem = kem.withChaCha(cc); - } - // Pipeline: source -> kem payload stage - DataContent chain; - if (encrypt) { - String alias = require(cmd, OPT_PUB, "Missing --pub for encryption"); - PublicKey recipient = keyring.getPublic(alias); - chain = DataContentChainBuilder.encrypt() - .add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL())) - .add(kem.recipientPublic(recipient)).build(); - } else { - String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption"); - PrivateKey recipient = keyring.getPrivate(alias); - chain = DataContentChainBuilder.decrypt() - .add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL())) - .add(kem.recipientPrivate(recipient)).build(); - } - - try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) { - in.transferTo(out); - } catch (IOException ex) { - if (LOG.isLoggable(Level.SEVERE)) { - LOG.log(Level.SEVERE, "I/O error", ex); + // Pipeline: source -> kem payload stage + DataContent chain; + if (encrypt) { + String alias = require(cmd, OPT_PUB, "Missing --pub for encryption"); + PublicKey recipient = keyring.getPublic(alias); + chain = DataContentChainBuilder.encrypt() + .add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL())) + .add(kem.recipientPublic(recipient)).build(); + } else { + String alias = require(cmd, OPT_PRIV, "Missing --priv for decryption"); + PrivateKey recipient = keyring.getPrivate(alias); + chain = DataContentChainBuilder.decrypt() + .add(PlainFileBuilder.builder().url(Path.of(input).toUri().toURL())) + .add(kem.recipientPrivate(recipient)).build(); + } + + try (InputStream in = chain.getStream(); OutputStream out = Files.newOutputStream(output)) { + in.transferTo(out); + } catch (IOException ex) { + if (LOG.isLoggable(Level.SEVERE)) { + LOG.log(Level.SEVERE, "I/O error", ex); + } + return 1; } - return 1; - } return 0; } } diff --git a/app/src/main/java/zeroecho/KeyStoreManagement.java b/app/src/main/java/zeroecho/KeyStoreManagement.java index b63b36c..c52181c 100644 --- a/app/src/main/java/zeroecho/KeyStoreManagement.java +++ b/app/src/main/java/zeroecho/KeyStoreManagement.java @@ -192,16 +192,15 @@ public final class KeyStoreManagement { /** * Executes the command with an explicit unlock source. * - * @param args arguments passed by the application dispatcher + * @param args arguments passed by the application dispatcher * @param dispatcherOptions dispatcher options - * @param unlockProvider explicit destroyable-password provider + * @param unlockProvider explicit destroyable-password provider * @return process exit code - * @throws ParseException if parsing fails - * @throws IOException if keyring I/O fails + * @throws ParseException if parsing fails + * @throws IOException if keyring I/O fails * @throws GeneralSecurityException if cryptographic processing fails */ - public static int main(final String[] args, final Options dispatcherOptions, - KeyringUnlockProvider unlockProvider) + public static int main(final String[] args, final Options dispatcherOptions, KeyringUnlockProvider unlockProvider) throws ParseException, IOException, GeneralSecurityException { ZeroEchoSession session = new ZeroEchoSession(); defineOptions(dispatcherOptions); @@ -218,8 +217,7 @@ public final class KeyStoreManagement { } Path keyringPath = Path.of(cmd.getOptionValue(KEYSTORE_OPTION.getLongOpt())); - try (KeyringStore store = Files.exists(keyringPath) - ? KeyringUnlocks.open(keyringPath, unlockProvider) + try (KeyringStore store = Files.exists(keyringPath) ? KeyringUnlocks.open(keyringPath, unlockProvider) : KeyringUnlocks.create(keyringPath, unlockProvider)) { if (cmd.hasOption(LIST_ALIASES_OPTION.getLongOpt())) { listAliases(store); @@ -310,8 +308,8 @@ public final class KeyStoreManagement { * @param store keyring store to mutate * @param cmd parsed command line */ - public static void doGenerate(final ZeroEchoSession session, final KeyringStore store, - final CommandLine cmd) throws IOException, GeneralSecurityException { + public static void doGenerate(final ZeroEchoSession session, final KeyringStore store, final CommandLine cmd) + throws IOException, GeneralSecurityException { String algId = required(cmd, ALG_OPTION, "--alg is required for --generate"); String aliasBase = required(cmd, ALIAS_OPTION, "--alias is required for --generate"); String kind = cmd.getOptionValue(KIND_OPTION.getLongOpt()); @@ -391,8 +389,7 @@ public final class KeyStoreManagement { } private static void generateSymmetric(ZeroEchoSession session, KeyringStore store, CryptoAlgorithm algorithm, - String algorithmId, String alias, boolean overwrite) - throws IOException, GeneralSecurityException { + String algorithmId, String alias, boolean overwrite) throws IOException, GeneralSecurityException { GeneratedSecret generated = firstGeneratedSecret(session, algorithm, algorithmId); ensureWritable(store, alias, overwrite); store.putSecret(alias, algorithmId, generated.key()); @@ -430,9 +427,7 @@ public final class KeyStoreManagement { /** Selects the exact key-generation operation requested by the command. */ private enum GenerationKind { - ASYMMETRIC, - SYMMETRIC, - NONE + ASYMMETRIC, SYMMETRIC, NONE } private static String required(CommandLine cmd, Option opt, String message) { diff --git a/app/src/main/java/zeroecho/KeyringUnlocks.java b/app/src/main/java/zeroecho/KeyringUnlocks.java index 9d2c01b..549b20e 100644 --- a/app/src/main/java/zeroecho/KeyringUnlocks.java +++ b/app/src/main/java/zeroecho/KeyringUnlocks.java @@ -53,8 +53,7 @@ final class KeyringUnlocks { } } - private static KeyringPassword acquire(KeyringUnlockProvider provider) - throws IOException { + private static KeyringPassword acquire(KeyringUnlockProvider provider) throws IOException { KeyringPassword password = provider.acquire(); if (password == null) { throw new IOException("Keyring unlock provider returned no password"); diff --git a/app/src/main/java/zeroecho/Tag.java b/app/src/main/java/zeroecho/Tag.java index 6171f2a..e755410 100644 --- a/app/src/main/java/zeroecho/Tag.java +++ b/app/src/main/java/zeroecho/Tag.java @@ -158,24 +158,22 @@ public final class Tag { // NOPMD * @throws GeneralSecurityException if a cryptographic error occurs during * signature or digest processing */ - public static int main(String[] args, Options root) - throws ParseException, IOException, GeneralSecurityException { + public static int main(String[] args, Options root) throws ParseException, IOException, GeneralSecurityException { return main(args, root, KeyringUnlocks.console()); } /** * Executes the command with an explicit keyring unlock source. * - * @param args command arguments - * @param root root options + * @param args command arguments + * @param root root options * @param unlockProvider keyring password provider * @return process exit code - * @throws ParseException if parsing fails - * @throws IOException if I/O fails + * @throws ParseException if parsing fails + * @throws IOException if I/O fails * @throws GeneralSecurityException if cryptographic processing fails */ - public static int main(String[] args, Options root, - KeyringUnlockProvider unlockProvider) + public static int main(String[] args, Options root, KeyringUnlockProvider unlockProvider) throws ParseException, IOException, GeneralSecurityException { ZeroEchoSession session = new ZeroEchoSession(); Options opts = root; @@ -222,13 +220,13 @@ public final class Tag { // NOPMD if (produce) { String privAlias = require(cli, PRIV_OPT, "signature produce requires --priv "); PrivateKey priv = keyring.getPrivate(privAlias); - tail = new TagTrailerDataContentBuilder<>( - TagEngineBuilder.signature(session, alg, priv, spec)).build(true); + tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, priv, spec)) + .build(true); } else { String pubAlias = require(cli, PUB_OPT, "signature verify requires --pub "); PublicKey pub = keyring.getPublic(pubAlias); - tail = new TagTrailerDataContentBuilder<>( - TagEngineBuilder.signature(session, alg, pub, spec)).build(false); + tail = new TagTrailerDataContentBuilder<>(TagEngineBuilder.signature(session, alg, pub, spec)) + .build(false); } } } else { // digest diff --git a/app/src/test/java/zeroecho/GuardTest.java b/app/src/test/java/zeroecho/GuardTest.java index 8c08be4..70a678d 100644 --- a/app/src/test/java/zeroecho/GuardTest.java +++ b/app/src/test/java/zeroecho/GuardTest.java @@ -124,16 +124,16 @@ public class GuardTest { // Encrypt String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", password, - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", - "aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex }; + "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", + "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex }; System.out.println("...encrypt: " + Arrays.toString(encArgs)); int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider()); assertEquals(0, e, "... encrypt expected exit code 0"); // Decrypt (using password) String[] decArgs = { "--decrypt", enc.toString(), "--output", dec.toString(), "--password", password, - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", - "aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex }; + "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", + "--tag-bits", Integer.toString(tagBits), "--aad-hex", aadHex }; System.out.println("...decrypt: " + Arrays.toString(decArgs)); int d = Guard.main(decArgs, new Options(), TestKeyringUnlocks.provider()); assertEquals(0, d, "... decrypt expected exit code 0"); @@ -163,10 +163,8 @@ public class GuardTest { System.out.println(method); Path input = writeRandom(tmp.resolve("invalid-kek.bin"), 32, 0x4B454B); Path output = tmp.resolve("invalid-kek.enc"); - String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(), - "--to-psw", "controlled", "--to-kek-bytes", "24", - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, - "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, + String[] arguments = { "--encrypt", input.toString(), "--output", output.toString(), "--to-psw", "controlled", + "--to-kek-bytes", "24", "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm" }; IllegalArgumentException failure = assertThrows(IllegalArgumentException.class, @@ -290,8 +288,8 @@ public class GuardTest { // plus 2 random password decoys. Recipients are shuffled by default. String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--keyring", ring.toString(), "--to-alias", rsa.pub, "--to-psw", password, "--decoy-alias", elg.pub, "--decoy-psw-rand", "2", - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", - "aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad }; + "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", + "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad }; System.out.println("...encrypt: " + Arrays.toString(encArgs)); int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider()); assertEquals(0, e, "... encrypt rc"); @@ -308,8 +306,8 @@ public class GuardTest { // Decrypt via password instead of key String[] decPwd = { "--decrypt", enc.toString(), "--output", dec2.toString(), "--password", password, - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", - "aes-gcm", "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad }; + "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", + "--tag-bits", Integer.toString(tagBits), "--aad-hex", aad }; System.out.println("...decrypt(password): " + Arrays.toString(decPwd)); int d2 = Guard.main(decPwd, new Options(), TestKeyringUnlocks.provider()); assertEquals(0, d2, "... decrypt(password) rc"); @@ -334,9 +332,9 @@ public class GuardTest { Path enc = tmp.resolve("pt-neg.bin.enc"); String pwd = "x"; - String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd, - "--pbkdf2-max", TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", - "aes-gcm", "--tag-bits", "128" }; + String[] encArgs = { "--encrypt", in.toString(), "--output", enc.toString(), "--to-psw", pwd, "--pbkdf2-max", + TEST_PBKDF2_MAXIMUM, "--pbkdf2-hard-max", TEST_PBKDF2_MAXIMUM, "--alg", "aes-gcm", "--tag-bits", + "128" }; int e = Guard.main(encArgs, new Options(), TestKeyringUnlocks.provider()); assertEquals(0, e, "... encrypt rc"); diff --git a/app/src/test/java/zeroecho/KemTest.java b/app/src/test/java/zeroecho/KemTest.java index e7c022b..1722cd0 100644 --- a/app/src/test/java/zeroecho/KemTest.java +++ b/app/src/test/java/zeroecho/KemTest.java @@ -165,8 +165,7 @@ public class KemTest { KeyAliases aliases = generateKemIntoKeyStore(ring, kemId, "alias-" + shortId(kemId)); // Sanity: re-open to ensure the file is valid - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore ks = KeyringStore.open(ring, password)) { if (!(ks.contains(aliases.pub) && ks.contains(aliases.prv))) { throw new IllegalStateException("Keyring does not contain expected aliases for " + kemId); @@ -210,15 +209,15 @@ public class KemTest { Files.write(plain, content); System.out.println("...[" + kemId + "] ChaCha encrypt"); int e = Kem.main(new String[] { "--encrypt", plain.toString(), "--output", enc.toString(), - "--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha", - "--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider()); + "--keyring", ring.toString(), "--pub", aliases.pub, "--kem", kemId, "--chacha", "--aad", + aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider()); if (e != 0) { throw new IllegalStateException("ChaCha encrypt rc=" + e); } System.out.println("...[" + kemId + "] ChaCha decrypt"); int d = Kem.main(new String[] { "--decrypt", enc.toString(), "--output", dec.toString(), - "--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha", - "--aad", aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider()); + "--keyring", ring.toString(), "--priv", aliases.prv, "--kem", kemId, "--chacha", "--aad", + aadChaCha, "--header" }, new Options(), TestKeyringUnlocks.provider()); if (d != 0) { throw new IllegalStateException("ChaCha decrypt rc=" + d); } diff --git a/app/src/test/java/zeroecho/KeyStoreManagementTest.java b/app/src/test/java/zeroecho/KeyStoreManagementTest.java index 54a0aed..85f673b 100644 --- a/app/src/test/java/zeroecho/KeyStoreManagementTest.java +++ b/app/src/test/java/zeroecho/KeyStoreManagementTest.java @@ -139,8 +139,7 @@ public class KeyStoreManagementTest { assertTrue(attempted > 0, "No generation attempts were successful"); // Verify by reloading and materializing. - zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore store; try { store = KeyringStore.open(ring, password); @@ -197,15 +196,13 @@ public class KeyStoreManagementTest { // ---- helpers ---- private static boolean hasAsymmetricDefault(CryptoAlgorithm alg) { - return alg.keyOperations().stream() - .anyMatch(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE - && info.defaultSpec() != null); + return alg.keyOperations().stream().anyMatch( + info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE && info.defaultSpec() != null); } private static boolean hasSymmetricDefault(CryptoAlgorithm alg) { return alg.keyOperations().stream() - .anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE - && info.defaultSpec() != null); + .anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE && info.defaultSpec() != null); } private static String sanitize(String id) { diff --git a/app/src/test/java/zeroecho/TagTest.java b/app/src/test/java/zeroecho/TagTest.java index aa4fd77..1ce7560 100644 --- a/app/src/test/java/zeroecho/TagTest.java +++ b/app/src/test/java/zeroecho/TagTest.java @@ -127,8 +127,7 @@ public class TagTest { Path ring = tmp.resolve("ring-ed25519.txt"); KeyAliases ed = generateIntoKeyStore(ring, "Ed25519", "ed"); // sanity - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore ks = KeyringStore.open(ring, password)) { assertTrue(ks.contains(ed.pub) && ks.contains(ed.prv), "missing expected aliases"); } @@ -200,12 +199,18 @@ public class TagTest { Files.write(plain, pt); // produce - assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in", - plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider())); + assertEquals(0, + Tag.main( + new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in", + plain.toString(), "--out", tagged.toString() }, + new Options(), TestKeyringUnlocks.provider())); // verify (match) - assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in", - tagged.toString(), "--out", recovered.toString() }, new Options(), TestKeyringUnlocks.provider())); + assertEquals(0, + Tag.main( + new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in", + tagged.toString(), "--out", recovered.toString() }, + new Options(), TestKeyringUnlocks.provider())); assertArrayEquals(pt, Files.readAllBytes(recovered), "digest round-trip mismatch"); @@ -223,15 +228,21 @@ public class TagTest { Files.write(plain, pt); // produce - assertEquals(0, Tag.main(new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in", - plain.toString(), "--out", tagged.toString() }, new Options(), TestKeyringUnlocks.provider())); + assertEquals(0, + Tag.main( + new String[] { "--type", "digest", "--mode", "produce", "--alg", "SHA-256", "--in", + plain.toString(), "--out", tagged.toString() }, + new Options(), TestKeyringUnlocks.provider())); // corrupt last byte -> break digest flipLastByte(tagged); // verify (mismatch): expect throw + default marker ("digest invalid") - assertEquals(1, Tag.main(new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in", - tagged.toString(), "--out", out.toString() }, new Options(), TestKeyringUnlocks.provider())); + assertEquals(1, + Tag.main( + new String[] { "--type", "digest", "--mode", "verify", "--alg", "SHA-256", "--in", + tagged.toString(), "--out", out.toString() }, + new Options(), TestKeyringUnlocks.provider())); assertTrue(Files.notExists(out, LinkOption.NOFOLLOW_LINKS)); diff --git a/app/src/test/java/zeroecho/TestKeyringUnlocks.java b/app/src/test/java/zeroecho/TestKeyringUnlocks.java index bce4419..fe52199 100644 --- a/app/src/test/java/zeroecho/TestKeyringUnlocks.java +++ b/app/src/test/java/zeroecho/TestKeyringUnlocks.java @@ -8,7 +8,6 @@ final class TestKeyringUnlocks { } static KeyringUnlockProvider provider() { - return () -> new KeyringPassword( - new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' }); + return () -> new KeyringPassword(new char[] { 't', 'e', 's', 't', '-', 'k', 'e', 'y', 'r', 'i', 'n', 'g' }); } } diff --git a/ext/src/test/java/zeroecho/ext/integrations/covert/jpeg/JpegExifIntegrationTest.java b/ext/src/test/java/zeroecho/ext/integrations/covert/jpeg/JpegExifIntegrationTest.java index d60a531..d575848 100644 --- a/ext/src/test/java/zeroecho/ext/integrations/covert/jpeg/JpegExifIntegrationTest.java +++ b/ext/src/test/java/zeroecho/ext/integrations/covert/jpeg/JpegExifIntegrationTest.java @@ -91,17 +91,17 @@ class JpegExifIntegrationTest { // AES encryption setup /* - * SecretKey key = zeroEchoSession.keyBuilders().symmetric() - * .generate("AES", AesKeyGenSpec.aes256()); AesSpec spec = + * SecretKey key = zeroEchoSession.keyBuilders().symmetric() .generate("AES", + * AesKeyGenSpec.aes256()); AesSpec spec = * AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build(); - * EncryptionContext enc = zeroEchoSession.createContext("AES", KeyUsage.ENCRYPT, key, - * spec); CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + - * System.nanoTime()); session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware) + * EncryptionContext enc = zeroEchoSession.createContext("AES", + * KeyUsage.ENCRYPT, key, spec); CtxInterface session = + * Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime()); + * session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware) * enc).setContext(session); */ ZeroEchoSession zeroEchoSession = new ZeroEchoSession(); - SecretKey key = zeroEchoSession.keyBuilders().symmetric() - .generate("AES", AesKeyGenSpec.aes256()); + SecretKey key = zeroEchoSession.keyBuilders().symmetric().generate("AES", AesKeyGenSpec.aes256()); CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime()); byte[] encryptedBytes; @@ -152,7 +152,8 @@ class JpegExifIntegrationTest { // input .add(PlainBytesBuilder.builder().bytes(extractedEncryptedBytes)) // encryption - .add(AesDataContentBuilder.builder(zeroEchoSession).importKeyRaw(key.getEncoded()).spec(AesSpec.gcm128(null)) + .add(AesDataContentBuilder.builder(zeroEchoSession).importKeyRaw(key.getEncoded()) + .spec(AesSpec.gcm128(null)) // let us use the default header for AAD and IV .withHeader().withAad(aad).context(session)) // and create the pipeline @@ -164,9 +165,9 @@ class JpegExifIntegrationTest { /* * AesSpec spec = * AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build(); - * EncryptionContext dec1 = zeroEchoSession.createContext("AES", KeyUsage.DECRYPT, - * key, spec); ((ContextAware) dec1).setContext(session); // same IV/AAD in ctx - * byte[] pt1 = readAll(dec1.attach(new + * EncryptionContext dec1 = zeroEchoSession.createContext("AES", + * KeyUsage.DECRYPT, key, spec); ((ContextAware) dec1).setContext(session); // + * same IV/AAD in ctx byte[] pt1 = readAll(dec1.attach(new * ByteArrayInputStream(extractedEncryptedBytes))); dec1.close(); */ String decrypted = new String(pt1, StandardCharsets.UTF_8); diff --git a/lib/src/main/java/zeroecho/core/Capability.java b/lib/src/main/java/zeroecho/core/Capability.java index 3303f9e..86dfd22 100644 --- a/lib/src/main/java/zeroecho/core/Capability.java +++ b/lib/src/main/java/zeroecho/core/Capability.java @@ -16,29 +16,31 @@ import zeroecho.core.spec.ContextSpec; /** * Immutable value descriptor of one algorithm context capability. * - *

The default specification is resolved once during provider construction. - * All components therefore have stable value semantics and are safe for - * concurrent reads.

+ *

+ * The default specification is resolved once during provider construction. All + * components therefore have stable value semantics and are safe for concurrent + * reads. + *

* * @param algorithmId canonical algorithm identifier - * @param family algorithm family - * @param role supported key usage + * @param family algorithm family + * @param role supported key usage * @param contextType produced context type - * @param keyType accepted key type - * @param specType accepted specification type + * @param keyType accepted key type + * @param specType accepted specification type * @param defaultSpec non-null resolved default specification * @since 1.0 */ public record Capability(String algorithmId, AlgorithmFamily family, KeyUsage role, - Class contextType, Class keyType, - Class specType, ContextSpec defaultSpec) { + Class contextType, Class keyType, Class specType, + ContextSpec defaultSpec) { /** * Validates the capability metadata. * - * @throws NullPointerException if a component is {@code null} - * @throws IllegalArgumentException if {@code defaultSpec} is incompatible - * with {@code specType} + * @throws NullPointerException if a component is {@code null} + * @throws IllegalArgumentException if {@code defaultSpec} is incompatible with + * {@code specType} */ public Capability { Objects.requireNonNull(algorithmId, "algorithmId must not be null"); diff --git a/lib/src/main/java/zeroecho/core/CryptoAlgorithm.java b/lib/src/main/java/zeroecho/core/CryptoAlgorithm.java index 39f2337..6e7c2d4 100644 --- a/lib/src/main/java/zeroecho/core/CryptoAlgorithm.java +++ b/lib/src/main/java/zeroecho/core/CryptoAlgorithm.java @@ -106,8 +106,8 @@ import zeroecho.core.spi.SymmetricKeyImporter; *

* Security note: Algorithms must enforce strong validation of keys and * specs during registration and - * {@link #createContext(KeyUsage, Key, ContextSpec)} to - * prevent downgrade or misuse attacks. + * {@link #createContext(KeyUsage, Key, ContextSpec)} to prevent downgrade or + * misuse attacks. *

* * @since 1.0 @@ -123,16 +123,11 @@ public abstract class CryptoAlgorithm { // NOPMD private final List capabilities = new ArrayList<>(); private final Map>> ctxBindings = new EnumMap<>(KeyUsage.class); - private final Map, AsymmetricKeyPairGenerator> keyPairGenerators = - new LinkedHashMap<>(); - private final Map, PublicKeyImporter> publicKeyImporters = - new LinkedHashMap<>(); - private final Map, PrivateKeyImporter> privateKeyImporters = - new LinkedHashMap<>(); - private final Map, SymmetricKeyGenerator> symmetricKeyGenerators = - new LinkedHashMap<>(); - private final Map, SymmetricKeyImporter> symmetricKeyImporters = - new LinkedHashMap<>(); + private final Map, AsymmetricKeyPairGenerator> keyPairGenerators = new LinkedHashMap<>(); + private final Map, PublicKeyImporter> publicKeyImporters = new LinkedHashMap<>(); + private final Map, PrivateKeyImporter> privateKeyImporters = new LinkedHashMap<>(); + private final Map, SymmetricKeyGenerator> symmetricKeyGenerators = new LinkedHashMap<>(); + private final Map, SymmetricKeyImporter> symmetricKeyImporters = new LinkedHashMap<>(); private final Map, AlgorithmKeySpec> asymmetricDefaults = new LinkedHashMap<>(); private final Map, AlgorithmKeySpec> symmetricDefaults = new LinkedHashMap<>(); @@ -296,8 +291,9 @@ public abstract class CryptoAlgorithm { // NOPMD *

* Concrete algorithms call this during construction to declare support for * specific roles (e.g., {@code ENCRYPT}, {@code VERIFY}). When - * {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the provided - * {@code key} and optional {@code spec} are matched against these bindings. + * {@link #createContext(KeyUsage, Key, ContextSpec)} is later invoked, the + * provided {@code key} and optional {@code spec} are matched against these + * bindings. *

* * @param role supported {@link KeyUsage} role @@ -397,8 +393,7 @@ public abstract class CryptoAlgorithm { // NOPMD if (rb.accepts(key, spec)) { S resolved = (spec != null) ? spec : Objects.requireNonNull(rb.defaultSpec.get(), "defaultSpec value must not be null"); - C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved), - _id + " factory returned null"); + C ctx = Objects.requireNonNull(rb.factory.createContext(key, resolved), _id + " factory returned null"); // Enforce the declared context type contract: if (!rb.ctxType.isInstance(ctx)) { throw new IllegalStateException(_id + " factory returned " + ctx.getClass().getName() @@ -411,8 +406,7 @@ public abstract class CryptoAlgorithm { // NOPMD + (spec == null ? " (default spec)" : " and spec=" + spec.getClass().getName())); } - private S resolveDefault(Class specType, - Supplier defaultSpecOrNull) { + private S resolveDefault(Class specType, Supplier defaultSpecOrNull) { if (defaultSpecOrNull == null) { return null; } @@ -426,16 +420,18 @@ public abstract class CryptoAlgorithm { // NOPMD /** * Registers asymmetric key-pair generation for one exact specification class. * - *

The optional default is resolved and validated during registration. + *

+ * The optional default is resolved and validated during registration. * Registered generators must be safe for concurrent invocation after the - * algorithm is published.

+ * algorithm is published. + *

* - * @param specType exact specification class - * @param generator non-null generator + * @param specType exact specification class + * @param generator non-null generator * @param defaultSpecOrNull optional default supplier, evaluated once - * @param specification type - * @throws NullPointerException if a required argument or supplied default is - * {@code null} + * @param specification type + * @throws NullPointerException if a required argument or supplied default + * is {@code null} * @throws IllegalArgumentException if the supplied default has the wrong type */ protected final void registerAsymmetricKeyPairGenerator(Class specType, @@ -450,7 +446,7 @@ public abstract class CryptoAlgorithm { // NOPMD * * @param specType exact specification class * @param importer non-null importer safe for concurrent invocation - * @param specification type + * @param specification type * @throws NullPointerException if an argument is {@code null} */ protected final void registerPublicKeyImporter(Class specType, @@ -464,7 +460,7 @@ public abstract class CryptoAlgorithm { // NOPMD * * @param specType exact specification class * @param importer non-null importer safe for concurrent invocation - * @param specification type + * @param specification type * @throws NullPointerException if an argument is {@code null} */ protected final void registerPrivateKeyImporter(Class specType, @@ -476,14 +472,16 @@ public abstract class CryptoAlgorithm { // NOPMD /** * Registers symmetric-key generation for one exact specification class. * - *

The optional default is resolved and validated during registration.

+ *

+ * The optional default is resolved and validated during registration. + *

* - * @param specType exact specification class - * @param generator non-null generator safe for concurrent invocation + * @param specType exact specification class + * @param generator non-null generator safe for concurrent invocation * @param defaultSpecOrNull optional default supplier, evaluated once - * @param specification type - * @throws NullPointerException if a required argument or supplied default is - * {@code null} + * @param specification type + * @throws NullPointerException if a required argument or supplied default + * is {@code null} * @throws IllegalArgumentException if the supplied default has the wrong type */ protected final void registerSymmetricKeyGenerator(Class specType, @@ -498,7 +496,7 @@ public abstract class CryptoAlgorithm { // NOPMD * * @param specType exact specification class * @param importer non-null importer safe for concurrent invocation - * @param specification type + * @param specification type * @throws NullPointerException if an argument is {@code null} */ protected final void registerSymmetricKeyImporter(Class specType, @@ -515,12 +513,14 @@ public abstract class CryptoAlgorithm { // NOPMD * Returns the asymmetric key-pair generator registered for an exact * specification class. * - *

The returned implementation may be shared and invoked concurrently.

+ *

+ * The returned implementation may be shared and invoked concurrently. + *

* * @param specType exact specification class; subclasses are not matched - * @param specification type + * @param specification type * @return registered generator - * @throws NullPointerException if {@code specType} is {@code null} + * @throws NullPointerException if {@code specType} is {@code null} * @throws IllegalArgumentException if no generator is registered */ @SuppressWarnings("unchecked") @@ -537,12 +537,14 @@ public abstract class CryptoAlgorithm { // NOPMD /** * Returns the public-key importer registered for an exact specification class. * - *

The returned implementation may be shared and invoked concurrently.

+ *

+ * The returned implementation may be shared and invoked concurrently. + *

* * @param specType exact specification class; subclasses are not matched - * @param specification type + * @param specification type * @return registered importer - * @throws NullPointerException if {@code specType} is {@code null} + * @throws NullPointerException if {@code specType} is {@code null} * @throws IllegalArgumentException if no importer is registered */ @SuppressWarnings("unchecked") @@ -556,15 +558,16 @@ public abstract class CryptoAlgorithm { // NOPMD } /** - * Returns the private-key importer registered for an exact specification - * class. + * Returns the private-key importer registered for an exact specification class. * - *

The returned implementation may be shared and invoked concurrently.

+ *

+ * The returned implementation may be shared and invoked concurrently. + *

* * @param specType exact specification class; subclasses are not matched - * @param specification type + * @param specification type * @return registered importer - * @throws NullPointerException if {@code specType} is {@code null} + * @throws NullPointerException if {@code specType} is {@code null} * @throws IllegalArgumentException if no importer is registered */ @SuppressWarnings("unchecked") @@ -581,12 +584,14 @@ public abstract class CryptoAlgorithm { // NOPMD * Returns the symmetric-key generator registered for an exact specification * class. * - *

The returned implementation may be shared and invoked concurrently.

+ *

+ * The returned implementation may be shared and invoked concurrently. + *

* * @param specType exact specification class; subclasses are not matched - * @param specification type + * @param specification type * @return registered generator - * @throws NullPointerException if {@code specType} is {@code null} + * @throws NullPointerException if {@code specType} is {@code null} * @throws IllegalArgumentException if no generator is registered */ @SuppressWarnings("unchecked") @@ -603,12 +608,14 @@ public abstract class CryptoAlgorithm { // NOPMD * Returns the symmetric-key importer registered for an exact specification * class. * - *

The returned implementation may be shared and invoked concurrently.

+ *

+ * The returned implementation may be shared and invoked concurrently. + *

* * @param specType exact specification class; subclasses are not matched - * @param specification type + * @param specification type * @return registered importer - * @throws NullPointerException if {@code specType} is {@code null} + * @throws NullPointerException if {@code specType} is {@code null} * @throws IllegalArgumentException if no importer is registered */ @SuppressWarnings("unchecked") @@ -628,14 +635,12 @@ public abstract class CryptoAlgorithm { // NOPMD */ public final List keyOperations() { List result = new ArrayList<>(); - addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators, - asymmetricDefaults); + addOperationInfo(result, KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE, keyPairGenerators, asymmetricDefaults); addOperationInfo(result, KeyOperation.ASYMMETRIC_PUBLIC_IMPORT, publicKeyImporters, Map.of()); addOperationInfo(result, KeyOperation.ASYMMETRIC_PRIVATE_IMPORT, privateKeyImporters, Map.of()); addOperationInfo(result, KeyOperation.SYMMETRIC_GENERATE, symmetricKeyGenerators, symmetricDefaults); addOperationInfo(result, KeyOperation.SYMMETRIC_IMPORT, symmetricKeyImporters, Map.of()); - result.sort(Comparator.comparing(KeyOperationInfo::operation) - .thenComparing(info -> info.specType().getName())); + result.sort(Comparator.comparing(KeyOperationInfo::operation).thenComparing(info -> info.specType().getName())); return List.copyOf(result); } diff --git a/lib/src/main/java/zeroecho/core/CryptoAlgorithms.java b/lib/src/main/java/zeroecho/core/CryptoAlgorithms.java index 61713be..058ba83 100644 --- a/lib/src/main/java/zeroecho/core/CryptoAlgorithms.java +++ b/lib/src/main/java/zeroecho/core/CryptoAlgorithms.java @@ -17,10 +17,12 @@ import java.util.TreeMap; /** * Immutable registry of {@link CryptoAlgorithm} providers. * - *

Providers are discovered once through {@link ServiceLoader}, sorted by + *

+ * Providers are discovered once through {@link ServiceLoader}, sorted by * canonical algorithm identifier, and retained in one immutable registry. * Runtime policy and auditing belong exclusively to explicitly created - * {@link zeroecho.sdk.ZeroEchoSession} instances.

+ * {@link zeroecho.sdk.ZeroEchoSession} instances. + *

* * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/core/CryptoCatalog.java b/lib/src/main/java/zeroecho/core/CryptoCatalog.java index 86d5690..d038c25 100644 --- a/lib/src/main/java/zeroecho/core/CryptoCatalog.java +++ b/lib/src/main/java/zeroecho/core/CryptoCatalog.java @@ -93,9 +93,9 @@ public final class CryptoCatalog { * {@link CryptoAlgorithms}. * *

- * Provider discovery, deterministic ordering, and duplicate checking occur - * once in {@code CryptoAlgorithms}. This method neither scans providers nor - * copies their collection. + * Provider discovery, deterministic ordering, and duplicate checking occur once + * in {@code CryptoAlgorithms}. This method neither scans providers nor copies + * their collection. *

* * @return an immutable {@code CryptoCatalog} with all discovered algorithms @@ -158,40 +158,16 @@ public final class CryptoCatalog { /** * Serializes the catalog to a compact JSON document. * - *

- * The schema is: - *

- *
{@code
-     * {
-     *   "algorithms": [
-     *     {
-     *       "id": "AES/GCM",
-     *       "displayName": "AES-GCM",
-     *       "capabilities": [
-     *         {
-     *           "family": "SYMMETRIC",
-     *           "role": "ENCRYPT",
-     *           "contextType": "AeadEncryptContext",
-     *           "keyType": "SecretKey",
-     *           "specType": "AeadSpec",
-     *           "defaultSpec": "Random nonce, 128-bit tag"
-     *         }
-     *       ],
-     *       "asymmetricKeyBuilders": [
-     *         { "specType": "Ed25519Spec", "defaultKeySpec": "Ed25519 default" }
-     *       ],
-     *       "symmetricKeyBuilders": [
-     *         { "specType": "AesKeySpec", "defaultKeySpec": "AES-256" }
-     *       ]
-     *     }
-     *   ]
-     * }
-     * }
+ *

The schema is:

{@code { "algorithms": [ { "id": "AES/GCM",
+     * "displayName": "AES-GCM", "capabilities": [ { "family": "SYMMETRIC", "role":
+     * "ENCRYPT", "contextType": "AeadEncryptContext", "keyType": "SecretKey",
+     * "specType": "AeadSpec", "defaultSpec": "Random nonce, 128-bit tag" } ],
+     * "asymmetricKeyBuilders": [ { "specType": "Ed25519Spec", "defaultKeySpec":
+     * "Ed25519 default" } ], "symmetricKeyBuilders": [ { "specType": "AesKeySpec",
+     * "defaultKeySpec": "AES-256" } ] } ] } }
* - *

- * String values are escaped for quotes and backslashes. The method does not - * attempt to pretty-print; callers can format the output if needed. - *

+ *

String values are escaped for quotes and backslashes. The method does not + * attempt to pretty-print; callers can format the output if needed.

* * @return a JSON string describing algorithms, capabilities, and key builders */ @@ -230,8 +206,7 @@ public final class CryptoCatalog { } firstOperation = false; sb.append('{').append(jsonField("operation", operation.operation().name())).append(',') - .append(jsonField("specType", operation.specType().getSimpleName())) - .append(",\"defaultSpec\":") + .append(jsonField("specType", operation.specType().getSimpleName())).append(",\"defaultSpec\":") .append(operation.defaultSpec() == null ? "null" : jsonString(labelOf(operation.defaultSpec()))) .append('}'); } @@ -273,9 +248,8 @@ public final class CryptoCatalog { } sb.append(""); for (KeyOperationInfo operation : a.keyOperations()) { - sb.append("") + sb.append("") .append(operation.defaultSpec() == null ? "" : esc(labelOf(operation.defaultSpec()))) .append(""); } diff --git a/lib/src/main/java/zeroecho/core/KeyOperationInfo.java b/lib/src/main/java/zeroecho/core/KeyOperationInfo.java index 5126673..3c45210 100644 --- a/lib/src/main/java/zeroecho/core/KeyOperationInfo.java +++ b/lib/src/main/java/zeroecho/core/KeyOperationInfo.java @@ -14,20 +14,20 @@ import zeroecho.core.spec.AlgorithmKeySpec; /** * Immutable metadata for one exact key operation. * - * @param operation operation guaranteed by the associated lookup - * @param specType exact accepted specification type + * @param operation operation guaranteed by the associated lookup + * @param specType exact accepted specification type * @param defaultSpec resolved generation default, or {@code null} for import * operations and generators without a default * @since 1.0 */ -public record KeyOperationInfo(KeyOperation operation, - Class specType, AlgorithmKeySpec defaultSpec) { +public record KeyOperationInfo(KeyOperation operation, Class specType, + AlgorithmKeySpec defaultSpec) { /** * Validates the metadata invariant. * - * @throws NullPointerException if {@code operation} or {@code specType} is - * {@code null} + * @throws NullPointerException if {@code operation} or {@code specType} is + * {@code null} * @throws IllegalArgumentException if a default is incompatible with * {@code specType}, or an import operation * declares a default @@ -38,9 +38,9 @@ public record KeyOperationInfo(KeyOperation operation, if (defaultSpec != null && !specType.isInstance(defaultSpec)) { throw new IllegalArgumentException("defaultSpec must be an instance of " + specType.getName()); } - if (defaultSpec != null && (operation == KeyOperation.SYMMETRIC_IMPORT - || operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT - || operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) { + if (defaultSpec != null + && (operation == KeyOperation.SYMMETRIC_IMPORT || operation == KeyOperation.ASYMMETRIC_PUBLIC_IMPORT + || operation == KeyOperation.ASYMMETRIC_PRIVATE_IMPORT)) { throw new IllegalArgumentException("import operations cannot declare a default specification"); } } diff --git a/lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java b/lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java index f451fd9..b38632f 100644 --- a/lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java +++ b/lib/src/main/java/zeroecho/core/alg/AbstractCryptoAlgorithm.java @@ -147,8 +147,8 @@ public abstract class AbstractCryptoAlgorithm extends CryptoAlgorithm { * *

Validation

Type checks happen at creation time (via {@code bind}) * and again when - * {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is - * called. If a factory returns a context not assignable to {@code ctxType}, an + * {@link CryptoAlgorithm#createContext(KeyUsage, Key, ContextSpec)} is called. + * If a factory returns a context not assignable to {@code ctxType}, an * {@link IllegalStateException} will be thrown. * * @param family high-level algorithm family classification diff --git a/lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java b/lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java index 6f79e5b..debc5a5 100644 --- a/lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java +++ b/lib/src/main/java/zeroecho/core/alg/aes/AesCipherContext.java @@ -63,9 +63,10 @@ import zeroecho.core.util.RandomSupport; * *

* IV and optional AAD are exchanged via a {@link conflux.CtxInterface} set with - * {@link #setContext(conflux.CtxInterface)}. Encryption always generates a fresh - * IV after atomically claiming the context; a caller-provided IV is never used - * for encryption. Decryption requires the IV from the context or encoded header. + * {@link #setContext(conflux.CtxInterface)}. Encryption always generates a + * fresh IV after atomically claiming the context; a caller-provided IV is never + * used for encryption. Decryption requires the IV from the context or encoded + * header. *

* *

@@ -311,10 +312,7 @@ public final class AesCipherContext implements EncryptionContext, ContextAware { /** Single-use encryption lifecycle states. */ private enum OperationState { - NEW, - ENCRYPTING, - COMPLETED, - FAILED + NEW, ENCRYPTING, COMPLETED, FAILED } /** Marks the owning encryption context terminal as its stream is consumed. */ diff --git a/lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java b/lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java index bb3e828..9f8453b 100644 --- a/lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java +++ b/lib/src/main/java/zeroecho/core/alg/aes/AesKeyImportSpec.java @@ -60,8 +60,8 @@ import zeroecho.core.spec.AlgorithmKeySpec; *

* *

- * Objects of this type are thread-safe while active and may be destroyed to wipe - * their owned key bytes. Access and marshalling fail after destruction. + * Objects of this type are thread-safe while active and may be destroyed to + * wipe their owned key bytes. Access and marshalling fail after destruction. *

* * @since 1.0 diff --git a/lib/src/main/java/zeroecho/core/alg/chacha/AbstractChaChaCipherContext.java b/lib/src/main/java/zeroecho/core/alg/chacha/AbstractChaChaCipherContext.java index 9bda202..e4026b8 100644 --- a/lib/src/main/java/zeroecho/core/alg/chacha/AbstractChaChaCipherContext.java +++ b/lib/src/main/java/zeroecho/core/alg/chacha/AbstractChaChaCipherContext.java @@ -243,8 +243,8 @@ abstract class AbstractChaChaCipherContext implements * Ensures a nonce is available in the context. * *
    - *
  • For encryption, always generates a new nonce after the context has - * been atomically claimed and stores a copy in the context.
  • + *
  • For encryption, always generates a new nonce after the context has been + * atomically claimed and stores a copy in the context.
  • *
  • For decryption, validates presence and correct length.
  • *
* @@ -283,10 +283,7 @@ abstract class AbstractChaChaCipherContext implements /** Single-use encryption lifecycle states. */ private enum OperationState { - NEW, - ENCRYPTING, - COMPLETED, - FAILED + NEW, ENCRYPTING, COMPLETED, FAILED } /** Marks the owning encryption context terminal as its stream is consumed. */ diff --git a/lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java b/lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java index 6dd61ad..eaafb0f 100644 --- a/lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java +++ b/lib/src/main/java/zeroecho/core/alg/chacha/ChaChaAlgorithm.java @@ -34,6 +34,7 @@ package zeroecho.core.alg.chacha; import zeroecho.core.util.RandomSupport; + /** *

ChaCha20 (stream) algorithm

* diff --git a/lib/src/main/java/zeroecho/core/alg/chacha/package-info.java b/lib/src/main/java/zeroecho/core/alg/chacha/package-info.java index 0220272..d0f2cf7 100644 --- a/lib/src/main/java/zeroecho/core/alg/chacha/package-info.java +++ b/lib/src/main/java/zeroecho/core/alg/chacha/package-info.java @@ -39,10 +39,10 @@ * the stream cipher ChaCha20 and the AEAD construction ChaCha20-Poly1305. The * module contains algorithm descriptors, streaming cipher contexts, * configuration specifications, optional header codecs for runtime parameters, - * and symmetric key import/generation specifications. Key import - * specifications are destroyable. The design favors safe defaults - * (12-byte nonces, 128-bit AEAD tag), explicit role-to-context binding, and a - * clear separation between static configuration and per-operation parameters. + * and symmetric key import/generation specifications. Key import specifications + * are destroyable. The design favors safe defaults (12-byte nonces, 128-bit + * AEAD tag), explicit role-to-context binding, and a clear separation between + * static configuration and per-operation parameters. *

* *

Components

diff --git a/lib/src/main/java/zeroecho/core/alg/common/agreement/GenericJcaAgreementContext.java b/lib/src/main/java/zeroecho/core/alg/common/agreement/GenericJcaAgreementContext.java index 333759e..73264c1 100644 --- a/lib/src/main/java/zeroecho/core/alg/common/agreement/GenericJcaAgreementContext.java +++ b/lib/src/main/java/zeroecho/core/alg/common/agreement/GenericJcaAgreementContext.java @@ -44,9 +44,9 @@ import zeroecho.core.context.AgreementContext; *

Generic JCA-based Key Agreement Context

* * An {@link AgreementContext} backed by the standard JCA key-agreement API. - * This class supports elliptic-curve and modern Diffie-Hellman variants - * such as ECDH, XDH (X25519, X448), and others provided by the runtime or - * configured provider. + * This class supports elliptic-curve and modern Diffie-Hellman variants such as + * ECDH, XDH (X25519, X448), and others provided by the runtime or configured + * provider. * *

* Instances of this context are created with a local {@link PrivateKey}, and diff --git a/lib/src/main/java/zeroecho/core/alg/common/eddsa/package-info.java b/lib/src/main/java/zeroecho/core/alg/common/eddsa/package-info.java index 08621ad..ad8bb3b 100644 --- a/lib/src/main/java/zeroecho/core/alg/common/eddsa/package-info.java +++ b/lib/src/main/java/zeroecho/core/alg/common/eddsa/package-info.java @@ -99,9 +99,9 @@ * reconstructs public keys from X.509 encodings via * {@link java.security.KeyFactory}. *

  • Signature contexts: - * {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext} - * delegates all operations to a generic JCA-backed signature adapter, enforcing - * a fixed tag length for the selected EdDSA variant.
  • + * {@link zeroecho.core.alg.common.eddsa.CommonEdDSASignatureContext} delegates + * all operations to a generic JCA-backed signature adapter, enforcing a fixed + * tag length for the selected EdDSA variant. * * *

    Design notes

    diff --git a/lib/src/main/java/zeroecho/core/alg/common/sig/SignatureInteropProfile.java b/lib/src/main/java/zeroecho/core/alg/common/sig/SignatureInteropProfile.java index f2ebb2b..eaef5aa 100644 --- a/lib/src/main/java/zeroecho/core/alg/common/sig/SignatureInteropProfile.java +++ b/lib/src/main/java/zeroecho/core/alg/common/sig/SignatureInteropProfile.java @@ -116,7 +116,8 @@ public final class SignatureInteropProfile { // NOPMD * @param keyAlgorithmId ZeroEcho key algorithm identifier used for key * import and matching, such as {@code RSA} * @param contextAlgorithmId ZeroEcho context algorithm identifier used - * with {@code ZeroEchoSession.createContext(...)} + * with + * {@code ZeroEchoSession.createContext(...)} * @param contextSpec explicit ZeroEcho context specification * @param signatureRepresentation signature representation bridge between * external bytes and internal ZeroEcho bytes diff --git a/lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java b/lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java index 9a618c9..5531503 100644 --- a/lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java +++ b/lib/src/main/java/zeroecho/core/alg/common/sig/package-info.java @@ -60,9 +60,9 @@ * configured {@link java.security.Signature}, resolves a fixed tag length (via * resolvers), and exposes a one-shot {@code wrap(InputStream)} API. * Verification behavior is controlled by a pluggable comparison approach. - *
  • SignatureStream - internal passthrough input stream that feeds chunks to - * the signature engine, emits the trailer in SIGN mode, and performs final - * verification in VERIFY mode.
  • + *
  • SignatureStream - internal passthrough input stream that feeds + * chunks to the signature engine, emits the trailer in SIGN mode, and performs + * final verification in VERIFY mode.
  • * * *

    Length resolution

    diff --git a/lib/src/main/java/zeroecho/core/alg/digest/Sha2Sha3Algorithm.java b/lib/src/main/java/zeroecho/core/alg/digest/Sha2Sha3Algorithm.java index 3677b26..fb0871c 100644 --- a/lib/src/main/java/zeroecho/core/alg/digest/Sha2Sha3Algorithm.java +++ b/lib/src/main/java/zeroecho/core/alg/digest/Sha2Sha3Algorithm.java @@ -117,8 +117,8 @@ public final class Sha2Sha3Algorithm extends AbstractCryptoAlgorithm { MessageDigest md = MessageDigest.getInstance(s.algorithm().jca()); return new JcaDigestContext(this, md, s); } catch (GeneralSecurityException e) { - throw new ProviderFailureException( - "Failed to initialize MessageDigest " + s.algorithm().jca(), e); + throw new ProviderFailureException("Failed to initialize MessageDigest " + s.algorithm().jca(), + e); } }, DigestSpec::sha256 // default for catalog/tests ); diff --git a/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java b/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java index 087e4ac..79f48ae 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhAlgorithm.java @@ -160,8 +160,7 @@ public final class EcdhAlgorithm extends AbstractCryptoAlgorithm { () -> EcdsaCurveSpec.P256); // Reuse EC builders/importers - registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), - () -> EcdhCurveSpec.P256); + registerAsymmetricKeyPairGenerator(EcdhCurveSpec.class, new EcdhKeyGenBuilder(), () -> EcdhCurveSpec.P256); registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder()); registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder()); } diff --git a/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java b/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java index 451ac69..6943452 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdh/EcdhKeyGenBuilder.java @@ -45,8 +45,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator; /** *

    ECDH Key Pair Generator

    * - * Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for elliptic curve - * Diffie-Hellman (ECDH) key pairs. + * Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for + * elliptic curve Diffie-Hellman (ECDH) key pairs. * *

    * This builder generates fresh EC key pairs suitable for ECDH key agreement. It diff --git a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java index 797630f..8985b11 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaAlgorithm.java @@ -103,8 +103,8 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm { *

    * On construction, the algorithm declares its supported roles and registers * builders with the {@link CryptoAlgorithm} infrastructure so they can be - * discovered by the {@link CryptoCatalog} or invoked through the - * session-bound {@link zeroecho.sdk.KeyBuilders} entry point. + * discovered by the {@link CryptoCatalog} or invoked through the session-bound + * {@link zeroecho.sdk.KeyBuilders} entry point. *

    */ public EcdsaAlgorithm() { @@ -134,8 +134,7 @@ public final class EcdsaAlgorithm extends AbstractCryptoAlgorithm { } }, () -> EcdsaCurveSpec.P256); - registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), - () -> EcdsaCurveSpec.P256); + registerAsymmetricKeyPairGenerator(EcdsaCurveSpec.class, new EcdsaKeyGenBuilder(), () -> EcdsaCurveSpec.P256); registerPublicKeyImporter(EcdsaPublicKeySpec.class, new EcdsaPublicKeyBuilder()); registerPrivateKeyImporter(EcdsaPrivateKeySpec.class, new EcdsaPrivateKeyBuilder()); } diff --git a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaKeyGenBuilder.java b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaKeyGenBuilder.java index 7f628b5..06732b8 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaKeyGenBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaKeyGenBuilder.java @@ -45,14 +45,14 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator; *

    ECDSA Key Pair Generator

    * * Implementation of {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} for - * {@link EcdsaCurveSpec}. - * This builder is responsible for generating new elliptic curve key pairs for - * use with the {@link EcdsaAlgorithm}. + * {@link EcdsaCurveSpec}. This builder is responsible for generating new + * elliptic curve key pairs for use with the {@link EcdsaAlgorithm}. * - *

    The exact supported operation is - * {@link #generateKeyPair(EcdsaCurveSpec)}. Public and private import are - * registered separately through {@link EcdsaPublicKeyBuilder} and - * {@link EcdsaPrivateKeyBuilder}.

    + *

    + * The exact supported operation is {@link #generateKeyPair(EcdsaCurveSpec)}. + * Public and private import are registered separately through + * {@link EcdsaPublicKeyBuilder} and {@link EcdsaPrivateKeyBuilder}. + *

    * *

    Usage

    Typically accessed through the session key-operation API or * {@link CryptoAlgorithm#asymmetricKeyPairGenerator(Class)}. diff --git a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPrivateKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPrivateKeyBuilder.java index ac05027..4289416 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPrivateKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPrivateKeyBuilder.java @@ -49,9 +49,11 @@ import zeroecho.core.spi.PrivateKeyImporter; * {@link EcdsaPrivateKeySpec}. This builder is responsible for importing ECDSA * private keys from encoded representations. * - *

    The exact supported operation is - * {@link #importPrivate(EcdsaPrivateKeySpec)}. Generation and public import are - * registered through their own operation-specific implementations.

    + *

    + * The exact supported operation is {@link #importPrivate(EcdsaPrivateKeySpec)}. + * Generation and public import are registered through their own + * operation-specific implementations. + *

    * *

    Encoding

    The {@link EcdsaPrivateKeySpec} stores the private key in * PKCS#8 DER format. This builder delegates to a JCA {@link KeyFactory} for the diff --git a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPublicKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPublicKeyBuilder.java index c67dc3f..52ec0b2 100644 --- a/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPublicKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ecdsa/EcdsaPublicKeyBuilder.java @@ -48,9 +48,11 @@ import zeroecho.core.spi.PublicKeyImporter; * {@link EcdsaPublicKeySpec}. This builder is responsible for importing ECDSA * public keys from X.509 SubjectPublicKeyInfo encodings. * - *

    The exact supported operation is - * {@link #importPublic(EcdsaPublicKeySpec)}. Generation and private import are - * registered through their own operation-specific implementations.

    + *

    + * The exact supported operation is {@link #importPublic(EcdsaPublicKeySpec)}. + * Generation and private import are registered through their own + * operation-specific implementations. + *

    * *

    Encoding

    The {@link EcdsaPublicKeySpec} stores the public key in * standard X.509 DER format. This builder delegates to a JCA {@link KeyFactory} diff --git a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519KeyGenBuilder.java b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519KeyGenBuilder.java index 50a11bc..77590fa 100644 --- a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519KeyGenBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519KeyGenBuilder.java @@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEdDSAKeyGenBuilder; /** *

    Key-pair builder for Ed25519

    * - * Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation for - * generating Ed25519 key pairs. + * Concrete {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} implementation + * for generating Ed25519 key pairs. * *

    * This builder delegates to the JCA provider under the canonical algorithm name diff --git a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PrivateKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PrivateKeyBuilder.java index 8e324f0..f3fec88 100644 --- a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PrivateKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PrivateKeyBuilder.java @@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPrivateKeyBuilder; /** *

    Private key builder for Ed25519

    * - * Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing - * wrapping Ed25519 private keys. + * Concrete {@link zeroecho.core.spi.PrivateKeyImporter} for importing wrapping + * Ed25519 private keys. * *

    * This builder integrates with the JCA under the canonical key factory diff --git a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PublicKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PublicKeyBuilder.java index 886117e..579e55d 100644 --- a/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PublicKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ed25519/Ed25519PublicKeyBuilder.java @@ -38,8 +38,8 @@ import zeroecho.core.alg.common.eddsa.AbstractEncodedPublicKeyBuilder; /** *

    Public key builder for Ed25519

    * - * Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing - * wrapping Ed25519 public keys. + * Concrete {@link zeroecho.core.spi.PublicKeyImporter} for importing wrapping + * Ed25519 public keys. * *

    * This builder integrates with the JCA under the canonical key factory diff --git a/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PrivateKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PrivateKeyBuilder.java index 883339f..dba5047 100644 --- a/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PrivateKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PrivateKeyBuilder.java @@ -66,8 +66,7 @@ import zeroecho.core.spec.AlgorithmKeySpec; * *

    Thread-safety

    Stateless and safe for concurrent use. Each call to * {@link zeroecho.core.spi.PrivateKeyImporter#importPrivate(AlgorithmKeySpec)} - * creates a new - * {@link java.security.KeyFactory}. + * creates a new {@link java.security.KeyFactory}. * * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PublicKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PublicKeyBuilder.java index bf6566f..7ff1f3b 100644 --- a/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PublicKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/ed448/Ed448PublicKeyBuilder.java @@ -65,8 +65,7 @@ import zeroecho.core.spec.AlgorithmKeySpec; * *

    Thread-safety

    Stateless and safe for concurrent use. Each call to * {@link zeroecho.core.spi.PublicKeyImporter#importPublic(AlgorithmKeySpec)} - * creates a new - * {@link java.security.KeyFactory}. + * creates a new {@link java.security.KeyFactory}. * * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/core/alg/kyber/KyberKeyGenSpec.java b/lib/src/main/java/zeroecho/core/alg/kyber/KyberKeyGenSpec.java index a56a929..66df0e2 100644 --- a/lib/src/main/java/zeroecho/core/alg/kyber/KyberKeyGenSpec.java +++ b/lib/src/main/java/zeroecho/core/alg/kyber/KyberKeyGenSpec.java @@ -63,7 +63,8 @@ import zeroecho.core.spec.AlgorithmKeySpec; *

    * * @see KyberAlgorithm - * @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String, AlgorithmKeySpec) + * @see zeroecho.sdk.KeyBuilders.Asymmetric#generateKeyPair(String, + * AlgorithmKeySpec) */ public final class KyberKeyGenSpec implements AlgorithmKeySpec, Describable { /** diff --git a/lib/src/main/java/zeroecho/core/alg/kyber/KyberPrivateKeySpec.java b/lib/src/main/java/zeroecho/core/alg/kyber/KyberPrivateKeySpec.java index 5e088be..785c3ff 100644 --- a/lib/src/main/java/zeroecho/core/alg/kyber/KyberPrivateKeySpec.java +++ b/lib/src/main/java/zeroecho/core/alg/kyber/KyberPrivateKeySpec.java @@ -47,9 +47,9 @@ import zeroecho.core.spec.AlgorithmKeySpec; * Specification wrapper for a Kyber (ML-KEM) private key encoded in PKCS#8. * *

    - * Instances of this class carry an owned copy of the PKCS#8-encoded private - * key bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key - * builders to import keys into the provider’s native representation. + * Instances of this class carry an owned copy of the PKCS#8-encoded private key + * bytes. They are used with {@link zeroecho.core.CryptoAlgorithm} key builders + * to import keys into the provider’s native representation. *

    * *

    Encoding

    diff --git a/lib/src/main/java/zeroecho/core/alg/rsa/BlockGeometry.java b/lib/src/main/java/zeroecho/core/alg/rsa/BlockGeometry.java index 81b6e55..214ceee 100644 --- a/lib/src/main/java/zeroecho/core/alg/rsa/BlockGeometry.java +++ b/lib/src/main/java/zeroecho/core/alg/rsa/BlockGeometry.java @@ -102,8 +102,7 @@ public record BlockGeometry(int inChunkSize, int outChunkSize, int finalizationO "inChunkSize must not exceed outChunkSize: " + inChunkSize + " > " + outChunkSize); } if (finalizationOutputChunks != 0) { - throw new IllegalArgumentException( - "finalizationOutputChunks must be zero: " + finalizationOutputChunks); + throw new IllegalArgumentException("finalizationOutputChunks must be zero: " + finalizationOutputChunks); } } diff --git a/lib/src/main/java/zeroecho/core/alg/saber/SaberKeyGenSpec.java b/lib/src/main/java/zeroecho/core/alg/saber/SaberKeyGenSpec.java index ae2b067..6c5de5d 100644 --- a/lib/src/main/java/zeroecho/core/alg/saber/SaberKeyGenSpec.java +++ b/lib/src/main/java/zeroecho/core/alg/saber/SaberKeyGenSpec.java @@ -43,7 +43,8 @@ import zeroecho.core.spec.AlgorithmKeySpec; * A {@code SaberKeyGenSpec} selects one of the SABER parameter variants * standardized in round-3 submissions. Each variant balances performance, * bandwidth, and security level. This spec is passed to a registered - * {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key pair. + * {@link zeroecho.core.spi.AsymmetricKeyPairGenerator} to generate a SABER key + * pair. *

    * *

    Variants

    The {@link Variant} enumeration identifies supported SABER diff --git a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusKeyGenBuilder.java b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusKeyGenBuilder.java index 8078506..94dade0 100644 --- a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusKeyGenBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusKeyGenBuilder.java @@ -54,10 +54,12 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator; * Reflection is used to avoid a hard dependency on all parameter variants. *

    * - *

    The exact supported operation is - * {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import are - * registered separately for {@link SphincsPlusPublicKeySpec} and - * {@link SphincsPlusPrivateKeySpec}.

    + *

    + * The exact supported operation is + * {@link #generateKeyPair(SphincsPlusKeyGenSpec)}. Public and private import + * are registered separately for {@link SphincsPlusPublicKeySpec} and + * {@link SphincsPlusPrivateKeySpec}. + *

    * *

    Example

    {@code
      * SphincsPlusKeyGenSpec spec =
    diff --git a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeyBuilder.java
    index d93cb1d..49338e1 100644
    --- a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeyBuilder.java
    +++ b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeyBuilder.java
    @@ -51,9 +51,11 @@ import zeroecho.core.spi.PrivateKeyImporter;
      * pairs but focuses solely on importing private key material.
      * 

    * - *

    The exact supported operation is + *

    + * The exact supported operation is * {@link #importPrivate(SphincsPlusPrivateKeySpec)}. Other key operations are - * registered through their own exact interfaces.

    + * registered through their own exact interfaces. + *

    * *

    Example

    {@code
      * // Assuming bytes contain a PKCS#8-encoded SPHINCS+ private key:
    diff --git a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeySpec.java b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeySpec.java
    index d45f4a5..582901c 100644
    --- a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeySpec.java
    +++ b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPrivateKeySpec.java
    @@ -48,7 +48,8 @@ import zeroecho.core.spec.AlgorithmKeySpec;
      * 

    * {@code SphincsPlusPrivateKeySpec} wraps a PKCS#8-encoded SPHINCS+ private key * along with the provider name that should be used for import. It is a simple - * destroyable holder designed for use with {@link SphincsPlusPrivateKeyBuilder}. + * destroyable holder designed for use with + * {@link SphincsPlusPrivateKeyBuilder}. *

    * *

    Encoding

    diff --git a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPublicKeyBuilder.java b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPublicKeyBuilder.java index 5eef249..b657d80 100644 --- a/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPublicKeyBuilder.java +++ b/lib/src/main/java/zeroecho/core/alg/sphincsplus/SphincsPlusPublicKeyBuilder.java @@ -50,9 +50,11 @@ import zeroecho.core.spi.PublicKeyImporter; * pairs, but focuses solely on importing public key material. *

    * - *

    The exact supported operation is + *

    + * The exact supported operation is * {@link #importPublic(SphincsPlusPublicKeySpec)}. Other key operations are - * registered through their own exact interfaces.

    + * registered through their own exact interfaces. + *

    * *

    Example

    {@code
      * // Assuming bytes contain an X.509-encoded SPHINCS+ public key:
    diff --git a/lib/src/main/java/zeroecho/core/alg/xdh/XdhKeyGenBuilder.java b/lib/src/main/java/zeroecho/core/alg/xdh/XdhKeyGenBuilder.java
    index 227bfb4..e5df7d9 100644
    --- a/lib/src/main/java/zeroecho/core/alg/xdh/XdhKeyGenBuilder.java
    +++ b/lib/src/main/java/zeroecho/core/alg/xdh/XdhKeyGenBuilder.java
    @@ -51,7 +51,8 @@ import zeroecho.core.spi.AsymmetricKeyPairGenerator;
      * 

    Design and scope

    *
      *
    • Generation only: This implementation exposes only the exact - * key-pair generation capability; import operations are registered separately.
    • + * key-pair generation capability; import operations are registered + * separately. *
    • Provider resolution: The default JCA provider selection is used. * If a specific provider is required, supply or register one that exposes the * requested XDH algorithm name.
    • diff --git a/lib/src/main/java/zeroecho/core/audit/AuditListeners.java b/lib/src/main/java/zeroecho/core/audit/AuditListeners.java index 7edf79c..1376522 100644 --- a/lib/src/main/java/zeroecho/core/audit/AuditListeners.java +++ b/lib/src/main/java/zeroecho/core/audit/AuditListeners.java @@ -13,10 +13,12 @@ import java.util.Objects; /** * Utilities for enforcing the best-effort audit-listener contract. * - *

      The returned listener suppresses listener failures without logging callback + *

      + * The returned listener suppresses listener failures without logging callback * arguments, because those arguments may refer to sensitive cryptographic * objects. Cryptographic operation outcomes therefore never depend on an audit - * sink's availability.

      + * sink's availability. + *

      * * @since 1.0 */ @@ -36,8 +38,8 @@ public final class AuditListeners { AuditListener target = Objects.requireNonNull(listener, "listener"); ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); ClassLoader loader = contextLoader == null ? ClassLoader.getSystemClassLoader() : contextLoader; - return (AuditListener) Proxy.newProxyInstance(loader, - new Class[] { AuditListener.class }, (proxy, method, arguments) -> { + return (AuditListener) Proxy.newProxyInstance(loader, new Class[] { AuditListener.class }, + (proxy, method, arguments) -> { if (method.getDeclaringClass() == Object.class) { return method.invoke(target, arguments); } diff --git a/lib/src/main/java/zeroecho/core/audit/AuditMode.java b/lib/src/main/java/zeroecho/core/audit/AuditMode.java index fc374a7..299bf58 100644 --- a/lib/src/main/java/zeroecho/core/audit/AuditMode.java +++ b/lib/src/main/java/zeroecho/core/audit/AuditMode.java @@ -10,8 +10,10 @@ package zeroecho.core.audit; /** * Defines the session-owned automatic auditing strategy. * - *

      Audit listener failures are best-effort diagnostics and never change the - * outcome of a cryptographic operation.

      + *

      + * Audit listener failures are best-effort diagnostics and never change the + * outcome of a cryptographic operation. + *

      * * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/core/audit/AuditedContexts.java b/lib/src/main/java/zeroecho/core/audit/AuditedContexts.java index 14b2ae5..6ab2a66 100644 --- a/lib/src/main/java/zeroecho/core/audit/AuditedContexts.java +++ b/lib/src/main/java/zeroecho/core/audit/AuditedContexts.java @@ -105,8 +105,8 @@ import zeroecho.core.spec.ContextSpec; *
    • Counting is performed by decorating the returned {@code InputStream}s; no * buffering beyond normal {@code FilterInputStream} forwarding is * introduced.
    • - *
    • Idempotent wrapping: contexts already wrapped by this utility are returned - * unchanged. Unrelated JDK proxies are wrapped normally.
    • + *
    • Idempotent wrapping: contexts already wrapped by this utility are + * returned unchanged. Unrelated JDK proxies are wrapped normally.
    • *
    * *

    Usage example

    {@code
    @@ -190,8 +190,7 @@ public final class AuditedContexts {
         }
     
         private static boolean isAuditedProxy(CryptoContext context) {
    -        return Proxy.isProxyClass(context.getClass())
    -                && Proxy.getInvocationHandler(context) instanceof AuditingHandler;
    +        return Proxy.isProxyClass(context.getClass()) && Proxy.getInvocationHandler(context) instanceof AuditingHandler;
         }
     
         @SuppressWarnings("unchecked")
    @@ -242,8 +241,7 @@ public final class AuditedContexts {
                 }
     
                 safeAudit.onContextCreatedMeta(ctxId, algoId == null ? UNKNOWN : algoId,
    -                    provider == null ? UNKNOWN : provider,
    -                    role, keyFp, specMeta);
    +                    provider == null ? UNKNOWN : provider, role, keyFp, specMeta);
             }
     
             ClassLoader cl = ctx.getClass().getClassLoader(); // NOPMD
    @@ -720,8 +718,7 @@ public final class AuditedContexts {
                 StringBuilder fingerprint = new StringBuilder(16);
                 for (int index = 0; index < Math.min(8, digest.length); index++) {
                     int value = digest[index] & 0xff;
    -                fingerprint.append(Character.forDigit(value >>> 4, 16))
    -                        .append(Character.forDigit(value & 0x0f, 16));
    +                fingerprint.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16));
                 }
                 return key.getAlgorithm() + ":" + fingerprint;
             } catch (NoSuchAlgorithmException exception) {
    diff --git a/lib/src/main/java/zeroecho/core/audit/JulAuditListenerStd.java b/lib/src/main/java/zeroecho/core/audit/JulAuditListenerStd.java
    index b586911..e661a76 100644
    --- a/lib/src/main/java/zeroecho/core/audit/JulAuditListenerStd.java
    +++ b/lib/src/main/java/zeroecho/core/audit/JulAuditListenerStd.java
    @@ -204,9 +204,9 @@ public final class JulAuditListenerStd implements AuditListener {
              * appends a stack trace in addition to the structured summary.
              *
              * 

    - * Stack traces may contain provider exception messages or application - * values. Enabling them is an explicit diagnostic opt-in and requires a - * suitably protected log destination. + * Stack traces may contain provider exception messages or application values. + * Enabling them is an explicit diagnostic opt-in and requires a suitably + * protected log destination. *

    * * @param include true to include stack traces, false to omit them @@ -600,8 +600,7 @@ public final class JulAuditListenerStd implements AuditListener { StringBuilder sb = new StringBuilder(key.getAlgorithm()).append(':'); for (int i = 0; i < Math.min(8, digest.length); i++) { int value = digest[i] & 0xff; - sb.append(Character.forDigit(value >>> 4, 16)) - .append(Character.forDigit(value & 0x0f, 16)); + sb.append(Character.forDigit(value >>> 4, 16)).append(Character.forDigit(value & 0x0f, 16)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { diff --git a/lib/src/main/java/zeroecho/core/err/UnsupportedRoleException.java b/lib/src/main/java/zeroecho/core/err/UnsupportedRoleException.java index 3711286..2172948 100644 --- a/lib/src/main/java/zeroecho/core/err/UnsupportedRoleException.java +++ b/lib/src/main/java/zeroecho/core/err/UnsupportedRoleException.java @@ -46,13 +46,11 @@ package zeroecho.core.err; *

    When it is thrown

    *
      *
    • During - * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, - * zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)} + * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)} * after policy validation, if the resolved algorithm exposes no bindings for * the given role.
    • *
    • Directly from - * {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage, - * java.security.Key, zeroecho.core.spec.ContextSpec)} + * {@link zeroecho.core.CryptoAlgorithm#createContext(zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)} * when no binding exists for the role.
    • *
    * diff --git a/lib/src/main/java/zeroecho/core/io/AbstractChunkTransformInputStream.java b/lib/src/main/java/zeroecho/core/io/AbstractChunkTransformInputStream.java index bf3088b..0816e1b 100644 --- a/lib/src/main/java/zeroecho/core/io/AbstractChunkTransformInputStream.java +++ b/lib/src/main/java/zeroecho/core/io/AbstractChunkTransformInputStream.java @@ -157,8 +157,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea * @param outChunkSize size of output chunks produced by the transform (must be * > 0) * @param chunks number of chunks buffered at once (must be > 0) - * @throws IllegalArgumentException if a size is outside its documented range - * or a buffer size overflows + * @throws IllegalArgumentException if a size is outside its documented range or + * a buffer size overflows */ protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks) { super(upstream); @@ -190,8 +190,8 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea * steady-state (must be > 0) * @param finalizationOutputChunks number of extra output chunks reserved for * finalization (must be >= 0) - * @throws IllegalArgumentException if a size is outside its documented range - * or a buffer size overflows + * @throws IllegalArgumentException if a size is outside its documented range or + * a buffer size overflows */ protected AbstractChunkTransformInputStream(InputStream upstream, int inChunkSize, int outChunkSize, int chunks, int finalizationOutputChunks) { @@ -261,8 +261,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea // EOF: run finalization exactly once, even if there's no remainder, // and surface any produced bytes (e.g., padding block, GCM tag). if (!eofSeen) { - int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length, - "finalization"); + int finalOut = validateOutputCount(doFinal(inBuf, 0, 0, outBuf, 0), outBuf.length, "finalization"); outPtr = 0; outLen = finalOut; eofSeen = true; @@ -273,8 +272,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea // all chunks are aligned to the specified boundary (inChunkSize) -> transform // can be simply invoked - outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length, - "transformation"); + outLen = validateOutputCount(transform(inBuf, 0, inLen / inChunkSize, outBuf), outBuf.length, "transformation"); outPtr = 0; int left = inLen % inChunkSize; @@ -291,8 +289,7 @@ public abstract class AbstractChunkTransformInputStream extends FilterInputStrea return true; } - private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks, - int finalizationOutputChunks) { + private static void validateGeometry(int inChunkSize, int outChunkSize, int chunks, int finalizationOutputChunks) { if (inChunkSize < MIN_INPUT_CHUNK_SIZE) { throw new IllegalArgumentException("inChunkSize must be greater than 1"); } diff --git a/lib/src/main/java/zeroecho/core/io/CipherTransformInputStreamBuilder.java b/lib/src/main/java/zeroecho/core/io/CipherTransformInputStreamBuilder.java index c1ca971..1c92cd0 100644 --- a/lib/src/main/java/zeroecho/core/io/CipherTransformInputStreamBuilder.java +++ b/lib/src/main/java/zeroecho/core/io/CipherTransformInputStreamBuilder.java @@ -53,13 +53,12 @@ import javax.crypto.Cipher; * block; a final partial block (if any) is processed by a single * {@code doFinal}. This mode is restricted to RSA and ElGamal. *
  • Left-padded independent block stream - like the independent block - * stream, but - * left-pads each transformed output block with zeros up to + * stream, but left-pads each transformed output block with zeros up to * {@code outChunkSize}. Final blocks must be complete; otherwise an * {@link IllegalStateException} is thrown.
  • - *
  • Continuous stream - uses - * {@code Cipher.update(...)} for bulk bytes and a single {@code doFinal()} at - * end of stream. This is suitable for CTR/CFB/OFB/GCM and padding modes.
  • + *
  • Continuous stream - uses {@code Cipher.update(...)} for bulk bytes + * and a single {@code doFinal()} at end of stream. This is suitable for + * CTR/CFB/OFB/GCM and padding modes.
  • * * *

    Block sizing

    @@ -344,7 +343,8 @@ public final class CipherTransformInputStreamBuilder { *

    * * @return a new InputStream that transforms bytes on the fly - * @throws NullPointerException if {@code upstream} or {@code cipher} is null + * @throws NullPointerException if {@code upstream} or {@code cipher} is + * null * @throws IllegalArgumentException if independent-block processing is selected * for an unsupported algorithm or buffer * geometry is invalid diff --git a/lib/src/main/java/zeroecho/core/io/SmartBlockStream.java b/lib/src/main/java/zeroecho/core/io/SmartBlockStream.java index 876c195..cb3fa10 100644 --- a/lib/src/main/java/zeroecho/core/io/SmartBlockStream.java +++ b/lib/src/main/java/zeroecho/core/io/SmartBlockStream.java @@ -58,6 +58,7 @@ final class SmartBlockStream extends AbstractChunkTransformInputStream { private static final Logger LOG = Logger.getLogger(SmartBlockStream.class.getName()); private final Cipher cipher; + /* package */ SmartBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize, int bufferedBlocks) { super(upstream, inChunkSize, outChunkSize, bufferedBlocks); diff --git a/lib/src/main/java/zeroecho/core/io/SmartPaddedBlockStream.java b/lib/src/main/java/zeroecho/core/io/SmartPaddedBlockStream.java index 0087cbe..f48fce2 100644 --- a/lib/src/main/java/zeroecho/core/io/SmartPaddedBlockStream.java +++ b/lib/src/main/java/zeroecho/core/io/SmartPaddedBlockStream.java @@ -59,6 +59,7 @@ final class SmartPaddedBlockStream extends AbstractChunkTransformInputStream { private static final Logger LOG = Logger.getLogger(SmartPaddedBlockStream.class.getName()); private final Cipher cipher; + /* package */ SmartPaddedBlockStream(InputStream upstream, Cipher cipher, int inChunkSize, int outChunkSize, int bufferedBlocks) { super(upstream, inChunkSize, outChunkSize, bufferedBlocks); diff --git a/lib/src/main/java/zeroecho/core/io/package-info.java b/lib/src/main/java/zeroecho/core/io/package-info.java index 09ed79a..2b088f3 100644 --- a/lib/src/main/java/zeroecho/core/io/package-info.java +++ b/lib/src/main/java/zeroecho/core/io/package-info.java @@ -54,12 +54,12 @@ * then calls {@code onCompleted()} exactly once at EOF. *
  • {@link CipherTransformInputStreamBuilder} - fluent builder that creates * cipher-backed streams for RSA/ElGamal independent-block processing, - * left-zero-padded independent blocks, or - * continuous {@code update}+{@code doFinal} streaming.
  • + * left-zero-padded independent blocks, or continuous + * {@code update}+{@code doFinal} streaming. *
  • {@link SmartBlockStream}, {@link SmartPaddedBlockStream}, * {@link SmartContinuousBlockStream} - internal cipher-backed stream variants; - * the first two are restricted to independent RSA or ElGamal blocks - * used by the builder.
  • + * the first two are restricted to independent RSA or ElGamal blocks used by the + * builder. *
  • {@link TailStrippingInputStream} - withholds the last N bytes from the * payload and delivers them to a callback at EOF (useful for tags, checksums, * or footers).
  • diff --git a/lib/src/main/java/zeroecho/core/marshal/PairSeq.java b/lib/src/main/java/zeroecho/core/marshal/PairSeq.java index b0a72fd..72a5385 100644 --- a/lib/src/main/java/zeroecho/core/marshal/PairSeq.java +++ b/lib/src/main/java/zeroecho/core/marshal/PairSeq.java @@ -61,8 +61,8 @@ import java.util.List; * *

    Serialization

    *
      - *
    • {@link #writeTo(Appendable)} outputs each pair as {@code k=v\n} - * lines without escaping and reports checked I/O failures.
    • + *
    • {@link #writeTo(Appendable)} outputs each pair as {@code k=v\n} lines + * without escaping and reports checked I/O failures.
    • *
    • {@link #readFrom(java.io.Reader)} parses lines in the same format, * ignoring blank lines and comments starting with {@code #}.
    • *
    @@ -99,8 +99,7 @@ public final class PairSeq { if (kv[elementIndex] == null) { int pairIndex = elementIndex >>> 1; String role = (elementIndex & 1) == 0 ? "key" : "value"; - throw new IllegalArgumentException( - "pair " + pairIndex + " " + role + " must not be null"); + throw new IllegalArgumentException("pair " + pairIndex + " " + role + " must not be null"); } } return new PairSeq(kv.clone()); @@ -201,8 +200,8 @@ public final class PairSeq { } /** - * Appends all pairs to the target as {@code key=value} lines, reporting - * checked I/O failures directly. + * Appends all pairs to the target as {@code key=value} lines, reporting checked + * I/O failures directly. * *

    * No escaping is performed; callers must ensure keys and values do not contain diff --git a/lib/src/main/java/zeroecho/core/marshal/PairSeqCodec.java b/lib/src/main/java/zeroecho/core/marshal/PairSeqCodec.java index c6e280f..1628b8c 100644 --- a/lib/src/main/java/zeroecho/core/marshal/PairSeqCodec.java +++ b/lib/src/main/java/zeroecho/core/marshal/PairSeqCodec.java @@ -101,9 +101,9 @@ import java.util.function.Supplier; * }

    * *

    Thread-safety

    Instances are immutable and thread-safe. Public - * accessors are resolved once per runtime class and operation type, then invoked - * through cached method handles. The unload-safe {@link ClassValue} caches do - * not retain otherwise unreachable class loaders. + * accessors are resolved once per runtime class and operation type, then + * invoked through cached method handles. The unload-safe {@link ClassValue} + * caches do not retain otherwise unreachable class loaders. * * @param domain type that follows the marshalling and unmarshalling * conventions @@ -274,8 +274,7 @@ public final class PairSeqCodec implements Codec { try { Method method = runtimeType.getMethod("marshal"); if (!PairSeq.class.isAssignableFrom(method.getReturnType())) { - return new MarshalPlan(null, - "marshal() must return PairSeq in " + runtimeType.getName(), null); + return new MarshalPlan(null, "marshal() must return PairSeq in " + runtimeType.getName(), null); } MethodHandle handle = MethodHandles.lookup().unreflect(method); return new MarshalPlan(handle, null, null); @@ -327,14 +326,14 @@ public final class PairSeqCodec implements Codec { "static unmarshal(PairSeq) must return " + runtimeType.getName(), null); } MethodHandle handle = MethodHandles.lookup().unreflect(method); - return new UnmarshalPlan(handle, - "static unmarshal(PairSeq) failed for " + runtimeType.getName(), null, null); + return new UnmarshalPlan(handle, "static unmarshal(PairSeq) failed for " + runtimeType.getName(), + null, null); } } catch (NoSuchMethodException ignored) { // Resolve the constructor fallback below. } catch (IllegalAccessException exception) { - return new UnmarshalPlan(null, null, - "static unmarshal(PairSeq) failed for " + runtimeType.getName(), exception); + return new UnmarshalPlan(null, null, "static unmarshal(PairSeq) failed for " + runtimeType.getName(), + exception); } try { diff --git a/lib/src/main/java/zeroecho/core/spec/AlgorithmKeySpec.java b/lib/src/main/java/zeroecho/core/spec/AlgorithmKeySpec.java index c7eaf76..f59bcaf 100644 --- a/lib/src/main/java/zeroecho/core/spec/AlgorithmKeySpec.java +++ b/lib/src/main/java/zeroecho/core/spec/AlgorithmKeySpec.java @@ -33,7 +33,6 @@ ******************************************************************************/ package zeroecho.core.spec; - /** * Marker interface for algorithm-specific key specifications. *

    diff --git a/lib/src/main/java/zeroecho/core/spi/AsymmetricKeyPairGenerator.java b/lib/src/main/java/zeroecho/core/spi/AsymmetricKeyPairGenerator.java index 804d81d..232cca5 100644 --- a/lib/src/main/java/zeroecho/core/spi/AsymmetricKeyPairGenerator.java +++ b/lib/src/main/java/zeroecho/core/spi/AsymmetricKeyPairGenerator.java @@ -11,7 +11,8 @@ import zeroecho.core.spec.AlgorithmKeySpec; /** * Generates asymmetric key pairs for one exact specification type. - * Implementations must be stateless or otherwise safe for concurrent invocation. + * Implementations must be stateless or otherwise safe for concurrent + * invocation. * * @param specification type * @since 1.0 diff --git a/lib/src/main/java/zeroecho/core/spi/ContextFactoryKS.java b/lib/src/main/java/zeroecho/core/spi/ContextFactoryKS.java index 2af71e3..81db075 100644 --- a/lib/src/main/java/zeroecho/core/spi/ContextFactoryKS.java +++ b/lib/src/main/java/zeroecho/core/spi/ContextFactoryKS.java @@ -15,11 +15,13 @@ import zeroecho.core.spec.ContextSpec; /** * Creates a cryptographic context from a key and a context specification. * - *

    Implementations report provider and parameter failures with unchecked + *

    + * Implementations report provider and parameter failures with unchecked * exceptions; context construction is a pure in-memory operation and does not * expose an I/O failure contract. Factories must be stateless or otherwise safe * for concurrent invocation; returned contexts retain their own documented - * thread-safety contracts.

    + * thread-safety contracts. + *

    * * @param context type produced * @param key type accepted @@ -31,7 +33,7 @@ public interface ContextFactoryKSOwnership of the returned object transfers to the receiver, which must + *

    + * Ownership of the returned object transfers to the receiver, which must * destroy it in a {@code finally} block immediately after the keyring has been * opened. Implementations must not source passwords from immutable strings, * process arguments, system properties, environment fallbacks, persistent - * files, or global mutable state.

    + * files, or global mutable state. + *

    */ @FunctionalInterface public interface KeyringUnlockProvider { diff --git a/lib/src/main/java/zeroecho/core/spi/PrivateKeyImporter.java b/lib/src/main/java/zeroecho/core/spi/PrivateKeyImporter.java index 6cb7c44..f05ffd0 100644 --- a/lib/src/main/java/zeroecho/core/spi/PrivateKeyImporter.java +++ b/lib/src/main/java/zeroecho/core/spi/PrivateKeyImporter.java @@ -10,8 +10,8 @@ import java.security.PrivateKey; import zeroecho.core.spec.AlgorithmKeySpec; /** - * Imports private keys for one exact specification type. Implementations must be - * stateless or otherwise safe for concurrent invocation. + * Imports private keys for one exact specification type. Implementations must + * be stateless or otherwise safe for concurrent invocation. * * @param specification type * @since 1.0 diff --git a/lib/src/main/java/zeroecho/core/spi/package-info.java b/lib/src/main/java/zeroecho/core/spi/package-info.java index 896d4bf..f4071e0 100644 --- a/lib/src/main/java/zeroecho/core/spi/package-info.java +++ b/lib/src/main/java/zeroecho/core/spi/package-info.java @@ -34,20 +34,26 @@ /** * Provider contracts for context construction and exact key operations. * - *

    Algorithms bind each supported role to a {@link ContextFactoryKS}. Context + *

    + * Algorithms bind each supported role to a {@link ContextFactoryKS}. Context * construction is an in-memory operation; stream attachment and processing are - * responsible for reporting {@link java.io.IOException}.

    + * responsible for reporting {@link java.io.IOException}. + *

    * - *

    Key capabilities are registered independently through + *

    + * Key capabilities are registered independently through * {@link SymmetricKeyGenerator}, {@link SymmetricKeyImporter}, * {@link AsymmetricKeyPairGenerator}, {@link PublicKeyImporter}, and * {@link PrivateKeyImporter}. A provider registers only the operations it * implements, so capability lookup fails before invocation instead of returning - * an object with unsupported methods.

    + * an object with unsupported methods. + *

    * - *

    SPI implementations should be stateless or otherwise safe for concurrent + *

    + * SPI implementations should be stateless or otherwise safe for concurrent * lookup and invocation. Created cryptographic contexts remain operation-local - * and are not necessarily thread-safe.

    + * and are not necessarily thread-safe. + *

    * * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringException.java b/lib/src/main/java/zeroecho/core/storage/KeyringException.java index a8c4133..6fb1d8d 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringException.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringException.java @@ -10,9 +10,11 @@ import java.util.Objects; /** * Redacted checked failure raised by encrypted keyring operations. * - *

    The public message contains only the stable error code. Filesystem paths, + *

    + * The public message contains only the stable error code. Filesystem paths, * aliases, key material, ciphertext, and provider-controlled messages are - * deliberately excluded.

    + * deliberately excluded. + *

    */ public final class KeyringException extends IOException { private static final long serialVersionUID = 1L; @@ -21,17 +23,9 @@ public final class KeyringException extends IOException { * Stable keyring failure categories. */ public enum Code { - KEYRING_ALREADY_OPEN, - KEYRING_FILESYSTEM_UNSUPPORTED, - KEYRING_FORMAT_INVALID, - KEYRING_LIMIT_EXCEEDED, - KEYRING_UNLOCK_FAILED, - KEYRING_IO_FAILED, - KEYRING_DURABILITY_UNCONFIRMED, - KEYRING_CLOSED, - KEYRING_NON_EXPORTABLE_KEY, - KEYRING_IMPORT_MAPPING_INVALID, - KEYRING_IMPORT_METADATA_INVALID, + KEYRING_ALREADY_OPEN, KEYRING_FILESYSTEM_UNSUPPORTED, KEYRING_FORMAT_INVALID, KEYRING_LIMIT_EXCEEDED, + KEYRING_UNLOCK_FAILED, KEYRING_IO_FAILED, KEYRING_DURABILITY_UNCONFIRMED, KEYRING_CLOSED, + KEYRING_NON_EXPORTABLE_KEY, KEYRING_IMPORT_MAPPING_INVALID, KEYRING_IMPORT_METADATA_INVALID, KEYRING_KEY_NOT_CANONICALIZABLE } diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringFileOperations.java b/lib/src/main/java/zeroecho/core/storage/KeyringFileOperations.java index 957185e..1d9567a 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringFileOperations.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringFileOperations.java @@ -24,8 +24,7 @@ interface KeyringFileOperations { /** Atomic persistence destination. */ enum Target { - MAIN_IMAGE, - NONCE_RESERVATION + MAIN_IMAGE, NONCE_RESERVATION } /** Creates one owner-only temporary file beside its destination. */ @@ -57,10 +56,8 @@ final class NioKeyringFileOperations implements KeyringFileOperations { } @Override - public void writeTemporary(Target target, Path temporary, byte[] image) - throws IOException { - try (FileChannel channel = FileChannel.open(temporary, - StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) { + public void writeTemporary(Target target, Path temporary, byte[] image) throws IOException { + try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) { ByteBuffer buffer = ByteBuffer.wrap(image); while (buffer.hasRemaining()) { channel.write(buffer); @@ -70,17 +67,14 @@ final class NioKeyringFileOperations implements KeyringFileOperations { @Override public void forceTemporary(Target target, Path temporary) throws IOException { - try (FileChannel channel = FileChannel.open(temporary, - StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) { + try (FileChannel channel = FileChannel.open(temporary, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS)) { channel.force(true); } } @Override - public void atomicReplace(Target target, Path temporary, Path destination) - throws IOException { - Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE, - StandardCopyOption.REPLACE_EXISTING); + public void atomicReplace(Target target, Path temporary, Path destination) throws IOException { + Files.move(temporary, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); } @Override diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringImportRegistry.java b/lib/src/main/java/zeroecho/core/storage/KeyringImportRegistry.java index 552a313..dfdf37b 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringImportRegistry.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringImportRegistry.java @@ -71,18 +71,18 @@ import zeroecho.core.spi.SymmetricKeyImporter; * Closed trusted mapping from persistent key identities to canonical registered * import operations. * - *

    Provider identity is deliberately absent. Standard encoded key material - * is reconstructed by the current runtime's canonical ZeroEcho importer. The - * original JCA provider is neither persisted nor reproduced.

    + *

    + * Provider identity is deliberately absent. Standard encoded key material is + * reconstructed by the current runtime's canonical ZeroEcho importer. The + * original JCA provider is neither persisted nor reproduced. + *

    */ final class KeyringImportRegistry { private static final String ALGORITHM_AES = "AES"; private static final String ALGORITHM_HMAC = "HMAC"; private static final String ALGORITHM_CHACHA20 = "CHACHA20"; private static final String ALGORITHM_CHACHA20_POLY1305 = "CHACHA20-POLY1305"; - private static final Map, - Function> SPEC_FACTORIES = - createSpecFactories(); + private static final Map, Function> SPEC_FACTORIES = createSpecFactories(); private static final Map MAPPINGS = createMappings(); private KeyringImportRegistry() { @@ -93,10 +93,7 @@ final class KeyringImportRegistry { */ /* default */ enum HmacVariant { - NONE(0, null), - SHA256(1, "HmacSHA256"), - SHA384(2, "HmacSHA384"), - SHA512(3, "HmacSHA512"); + NONE(0, null), SHA256(1, "HmacSHA256"), SHA384(2, "HmacSHA384"), SHA512(3, "HmacSHA512"); private final int code; private final String jcaName; @@ -123,8 +120,7 @@ final class KeyringImportRegistry { throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID); } - /* default */ static HmacVariant forStoredKey(String algorithmId, Key key) - throws KeyringException { + /* default */ static HmacVariant forStoredKey(String algorithmId, Key key) throws KeyringException { if (!ALGORITHM_HMAC.equals(algorithmId)) { return NONE; } @@ -142,21 +138,19 @@ final class KeyringImportRegistry { * Immutable description used by the finite importer-matrix test. * * @param algorithmId canonical ZeroEcho algorithm identifier - * @param kind key kind - * @param encoding standard encoding + * @param kind key kind + * @param encoding standard encoding * @param hmacVariant closed HMAC variant, or {@link HmacVariant#NONE} - * @param specType exact registered importer specification type + * @param specType exact registered importer specification type */ /* default */ - record PersistentMapping(String algorithmId, KeyringStore.Kind kind, - KeyringStore.Encoding encoding, HmacVariant hmacVariant, - Class specType) { + record PersistentMapping(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding, + HmacVariant hmacVariant, Class specType) { } @SuppressWarnings("PMD.AvoidCatchingGenericException") - /* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind, - KeyringStore.Encoding encoding, HmacVariant hmacVariant, byte[] encoded) - throws GeneralSecurityException, KeyringException { + /* default */ static Key importKey(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding, + HmacVariant hmacVariant, byte[] encoded) throws GeneralSecurityException, KeyringException { PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant); CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId); AlgorithmKeySpec spec = createSpec(mapping, encoded); @@ -172,8 +166,7 @@ final class KeyringImportRegistry { } /* default */ static void validateMapping(String algorithmId, KeyringStore.Kind kind, - KeyringStore.Encoding encoding, HmacVariant hmacVariant) - throws KeyringException { + KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException { PersistentMapping mapping = requireMapping(algorithmId, kind, encoding, hmacVariant); CryptoAlgorithm algorithm = requireAlgorithm(mapping.algorithmId); List operations = matchingOperations(algorithm, kind); @@ -184,27 +177,23 @@ final class KeyringImportRegistry { @SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.AvoidCatchingGenericException" }) /* default */ static void validateCanonical(String algorithmId, KeyringStore.Kind kind, - KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source, - byte[] encoded) + KeyringStore.Encoding encoding, HmacVariant hmacVariant, Key source, byte[] encoded) throws KeyringException { Key imported = null; byte[] canonical = null; try { if (!matchesSourceAlgorithm(source, algorithmId, hmacVariant)) { - throw new KeyringException( - KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); + throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); } imported = importKey(algorithmId, kind, encoding, hmacVariant, encoded); canonical = imported.getEncoded(); if (canonical == null || !matchesFormat(imported.getFormat(), encoding) || !MessageDigest.isEqual(encoded, canonical) || !matchesAlgorithm(imported, algorithmId, hmacVariant)) { - throw new KeyringException( - KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); + throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); } } catch (GeneralSecurityException | RuntimeException exception) { - throw new KeyringException( - KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); + throw new KeyringException(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE); } finally { if (canonical != null) { Arrays.fill(canonical, (byte) 0); @@ -217,11 +206,9 @@ final class KeyringImportRegistry { return List.copyOf(MAPPINGS.values()); } - private static PersistentMapping requireMapping(String algorithmId, - KeyringStore.Kind kind, KeyringStore.Encoding encoding, - HmacVariant hmacVariant) throws KeyringException { - PersistentMapping mapping = MAPPINGS.get( - new Tuple(algorithmId, kind, encoding, hmacVariant)); + private static PersistentMapping requireMapping(String algorithmId, KeyringStore.Kind kind, + KeyringStore.Encoding encoding, HmacVariant hmacVariant) throws KeyringException { + PersistentMapping mapping = MAPPINGS.get(new Tuple(algorithmId, kind, encoding, hmacVariant)); if (mapping == null) { throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID); } @@ -229,8 +216,7 @@ final class KeyringImportRegistry { } @SuppressWarnings("PMD.PreserveStackTrace") - private static CryptoAlgorithm requireAlgorithm(String algorithmId) - throws KeyringException { + private static CryptoAlgorithm requireAlgorithm(String algorithmId) throws KeyringException { try { return CryptoAlgorithms.require(algorithmId); } catch (IllegalArgumentException exception) { @@ -238,11 +224,8 @@ final class KeyringImportRegistry { } } - private static List matchingOperations(CryptoAlgorithm algorithm, - KeyringStore.Kind kind) { - return algorithm.keyOperations().stream() - .filter(info -> info.operation() == operation(kind)) - .toList(); + private static List matchingOperations(CryptoAlgorithm algorithm, KeyringStore.Kind kind) { + return algorithm.keyOperations().stream().filter(info -> info.operation() == operation(kind)).toList(); } private static KeyOperation operation(KeyringStore.Kind kind) { @@ -254,25 +237,22 @@ final class KeyringImportRegistry { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind, - AlgorithmKeySpec spec) throws GeneralSecurityException { + private static Key invokeImporter(CryptoAlgorithm algorithm, KeyringStore.Kind kind, AlgorithmKeySpec spec) + throws GeneralSecurityException { return switch (kind) { - case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass())) - .importPublic(spec); - case PRIVATE_KEY -> ((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass())) - .importPrivate(spec); - case SECRET_KEY -> ((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass())) - .importSecret(spec); + case PUBLIC_KEY -> ((PublicKeyImporter) algorithm.publicKeyImporter(spec.getClass())).importPublic(spec); + case PRIVATE_KEY -> + ((PrivateKeyImporter) algorithm.privateKeyImporter(spec.getClass())).importPrivate(spec); + case SECRET_KEY -> + ((SymmetricKeyImporter) algorithm.symmetricKeyImporter(spec.getClass())).importSecret(spec); }; } - private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded) - throws KeyringException { + private static AlgorithmKeySpec createSpec(PersistentMapping mapping, byte[] encoded) throws KeyringException { if (mapping.specType == HmacKeyImportSpec.class) { return new HmacKeyImportSpec(mapping.hmacVariant.jcaName, encoded); } - Function factory = - SPEC_FACTORIES.get(mapping.specType); + Function factory = SPEC_FACTORIES.get(mapping.specType); AlgorithmKeySpec result = factory == null ? null : factory.apply(encoded); if (result == null) { throw new KeyringException(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID); @@ -288,31 +268,27 @@ final class KeyringImportRegistry { }; } - private static boolean matchesAlgorithm(Key imported, String algorithmId, - HmacVariant hmacVariant) { + private static boolean matchesAlgorithm(Key imported, String algorithmId, HmacVariant hmacVariant) { if (ALGORITHM_HMAC.equals(algorithmId)) { return hmacVariant.jcaName.equals(imported.getAlgorithm()); } if (ALGORITHM_AES.equals(algorithmId)) { return ALGORITHM_AES.equals(imported.getAlgorithm()); } - if (ALGORITHM_CHACHA20.equals(algorithmId) - || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) { + if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) { return "ChaCha20".equals(imported.getAlgorithm()); } return true; } - private static boolean matchesSourceAlgorithm(Key source, String algorithmId, - HmacVariant hmacVariant) { + private static boolean matchesSourceAlgorithm(Key source, String algorithmId, HmacVariant hmacVariant) { if (ALGORITHM_HMAC.equals(algorithmId)) { return hmacVariant.jcaName.equals(source.getAlgorithm()); } if (ALGORITHM_AES.equals(algorithmId)) { return ALGORITHM_AES.equals(source.getAlgorithm()); } - if (ALGORITHM_CHACHA20.equals(algorithmId) - || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) { + if (ALGORITHM_CHACHA20.equals(algorithmId) || ALGORITHM_CHACHA20_POLY1305.equals(algorithmId)) { return "ChaCha20".equals(source.getAlgorithm()); } return true; @@ -329,10 +305,8 @@ final class KeyringImportRegistry { } } - private static Map, - Function> createSpecFactories() { - return Map.ofEntries( - Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw), + private static Map, Function> createSpecFactories() { + return Map.ofEntries(Map.entry(AesKeyImportSpec.class, AesKeyImportSpec::fromRaw), Map.entry(ChaChaKeyImportSpec.class, ChaChaKeyImportSpec::fromRaw), Map.entry(BikePublicKeySpec.class, BikePublicKeySpec::new), Map.entry(BikePrivateKeySpec.class, BikePrivateKeySpec::new), @@ -380,73 +354,54 @@ final class KeyringImportRegistry { addAsymmetric(mappings, "CMCE", CmcePublicKeySpec.class, CmcePrivateKeySpec.class); addAsymmetric(mappings, "DH", DhPublicKeySpec.class, DhPrivateKeySpec.class); addAsymmetric(mappings, "ECDSA", EcdsaPublicKeySpec.class, EcdsaPrivateKeySpec.class); - addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class, - Ed25519PrivateKeySpec.class); - addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class, - Ed448PrivateKeySpec.class); - addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class, - ElgamalPrivateKeySpec.class); - addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class, - FrodoPrivateKeySpec.class); + addAsymmetric(mappings, "Ed25519", Ed25519PublicKeySpec.class, Ed25519PrivateKeySpec.class); + addAsymmetric(mappings, "Ed448", Ed448PublicKeySpec.class, Ed448PrivateKeySpec.class); + addAsymmetric(mappings, "ElGamal", ElgamalPublicKeySpec.class, ElgamalPrivateKeySpec.class); + addAsymmetric(mappings, "Frodo", FrodoPublicKeySpec.class, FrodoPrivateKeySpec.class); addAsymmetric(mappings, "HQC", HqcPublicKeySpec.class, HqcPrivateKeySpec.class); - addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class, - KyberPrivateKeySpec.class); - addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class, - MldsaPrivateKeySpec.class); + addAsymmetric(mappings, "ML-KEM", KyberPublicKeySpec.class, KyberPrivateKeySpec.class); + addAsymmetric(mappings, "ML-DSA", MldsaPublicKeySpec.class, MldsaPrivateKeySpec.class); addAsymmetric(mappings, "NTRU", NtruPublicKeySpec.class, NtruPrivateKeySpec.class); - addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class, - NtrulPrimePrivateKeySpec.class); - addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class, - SntruPrimePrivateKeySpec.class); + addAsymmetric(mappings, "NTRULPRime", NtrulPrimePublicKeySpec.class, NtrulPrimePrivateKeySpec.class); + addAsymmetric(mappings, "SNTRUPrime", SntruPrimePublicKeySpec.class, SntruPrimePrivateKeySpec.class); addAsymmetric(mappings, "RSA", RsaPublicKeySpec.class, RsaPrivateKeySpec.class); - addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class, - SaberPrivateKeySpec.class); - addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class, - SlhDsaPrivateKeySpec.class); - addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class, - SphincsPlusPrivateKeySpec.class); + addAsymmetric(mappings, "SABER", SaberPublicKeySpec.class, SaberPrivateKeySpec.class); + addAsymmetric(mappings, "SLH-DSA", SlhDsaPublicKeySpec.class, SlhDsaPrivateKeySpec.class); + addAsymmetric(mappings, "SPHINCS+", SphincsPlusPublicKeySpec.class, SphincsPlusPrivateKeySpec.class); addAsymmetric(mappings, "Xdh", XdhPublicKeySpec.class, XdhPrivateKeySpec.class); - add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, - HmacVariant.NONE, AesKeyImportSpec.class); - add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, + add(mappings, ALGORITHM_AES, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE, + AesKeyImportSpec.class); + add(mappings, ALGORITHM_CHACHA20, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE, + ChaChaKeyImportSpec.class); + add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.NONE, ChaChaKeyImportSpec.class); - add(mappings, ALGORITHM_CHACHA20_POLY1305, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, HmacVariant.NONE, ChaChaKeyImportSpec.class); - add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, - HmacVariant.SHA256, HmacKeyImportSpec.class); - add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, - HmacVariant.SHA384, HmacKeyImportSpec.class); - add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, - KeyringStore.Encoding.RAW, - HmacVariant.SHA512, HmacKeyImportSpec.class); + add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA256, + HmacKeyImportSpec.class); + add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA384, + HmacKeyImportSpec.class); + add(mappings, ALGORITHM_HMAC, KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, HmacVariant.SHA512, + HmacKeyImportSpec.class); return Collections.unmodifiableMap(mappings); } - private static void addAsymmetric(Map mappings, - String algorithmId, Class publicSpec, - Class privateSpec) { - add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY, - KeyringStore.Encoding.X509, HmacVariant.NONE, publicSpec); - add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY, - KeyringStore.Encoding.PKCS8, HmacVariant.NONE, privateSpec); + private static void addAsymmetric(Map mappings, String algorithmId, + Class publicSpec, Class privateSpec) { + add(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, HmacVariant.NONE, + publicSpec); + add(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY, KeyringStore.Encoding.PKCS8, HmacVariant.NONE, + privateSpec); } - private static void add(Map mappings, String algorithmId, - KeyringStore.Kind kind, KeyringStore.Encoding encoding, - HmacVariant hmacVariant, Class specType) { + private static void add(Map mappings, String algorithmId, KeyringStore.Kind kind, + KeyringStore.Encoding encoding, HmacVariant hmacVariant, Class specType) { Tuple tuple = new Tuple(algorithmId, kind, encoding, hmacVariant); - PersistentMapping mapping = new PersistentMapping(algorithmId, kind, - encoding, hmacVariant, specType); + PersistentMapping mapping = new PersistentMapping(algorithmId, kind, encoding, hmacVariant, specType); if (mappings.put(tuple, mapping) != null) { throw new IllegalStateException("Duplicate persistent key importer tuple"); } } - private record Tuple(String algorithmId, KeyringStore.Kind kind, - KeyringStore.Encoding encoding, HmacVariant hmacVariant) { + private record Tuple(String algorithmId, KeyringStore.Kind kind, KeyringStore.Encoding encoding, + HmacVariant hmacVariant) { } } diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringNonceReservationKdf.java b/lib/src/main/java/zeroecho/core/storage/KeyringNonceReservationKdf.java index ff0872b..959a35e 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringNonceReservationKdf.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringNonceReservationKdf.java @@ -19,8 +19,7 @@ final class KeyringNonceReservationKdf { private static final int STORE_ID_BYTES = 16; private static final int OUTPUT_BYTES = 32; private static final String HMAC_SHA256 = "HmacSHA256"; - private static final String DOMAIN_LABEL = - "zeroecho:keyring:nonce-reservation-mac:v1"; + private static final String DOMAIN_LABEL = "zeroecho:keyring:nonce-reservation-mac:v1"; private KeyringNonceReservationKdf() { } @@ -29,14 +28,13 @@ final class KeyringNonceReservationKdf { * Derives the store-specific nonce-reservation MAC key. * * @param masterKey borrowed 256-bit store master key - * @param storeId borrowed canonical 128-bit binary store UUID + * @param storeId borrowed canonical 128-bit binary store UUID * @return newly owned 256-bit derived key * @throws GeneralSecurityException if HMAC-SHA-256 is unavailable */ - /* default */ static byte[] derive(byte[] masterKey, byte[] storeId) - throws GeneralSecurityException { - if (masterKey == null || masterKey.length != MASTER_KEY_BYTES - || storeId == null || storeId.length != STORE_ID_BYTES) { + /* default */ static byte[] derive(byte[] masterKey, byte[] storeId) throws GeneralSecurityException { + if (masterKey == null || masterKey.length != MASTER_KEY_BYTES || storeId == null + || storeId.length != STORE_ID_BYTES) { throw new IllegalArgumentException("Invalid keyring derivation input"); } byte[] salt = storeId.clone(); @@ -61,8 +59,7 @@ final class KeyringNonceReservationKdf { } } - private static byte[] hmac(byte[] key, byte[] input) - throws GeneralSecurityException { + private static byte[] hmac(byte[] key, byte[] input) throws GeneralSecurityException { Mac mac = Mac.getInstance(HMAC_SHA256); mac.init(new SecretKeySpec(key, HMAC_SHA256)); return mac.doFinal(input); diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringPassword.java b/lib/src/main/java/zeroecho/core/storage/KeyringPassword.java index 2eb7049..437164c 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringPassword.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringPassword.java @@ -14,14 +14,18 @@ import javax.security.auth.Destroyable; /** * Destroyable owner of a keyring password. * - *

    The constructor and {@link #copy()} use defensive copies. Callers retain + *

    + * The constructor and {@link #copy()} use defensive copies. Callers retain * ownership of the array supplied to the constructor and must clear it. The * returned copy belongs to the receiver and must be cleared immediately after * key derivation. This object never creates an immutable password - * {@link String}.

    + * {@link String}. + *

    * - *

    Instances are thread-safe. Destruction is idempotent and makes subsequent - * access fail deterministically.

    + *

    + * Instances are thread-safe. Destruction is idempotent and makes subsequent + * access fail deterministically. + *

    */ public final class KeyringPassword implements Destroyable, AutoCloseable { private final ReentrantLock lifecycleLock = new ReentrantLock(); @@ -32,7 +36,7 @@ public final class KeyringPassword implements Destroyable, AutoCloseable { * Creates a password owner. * * @param password password characters, which are defensively copied - * @throws NullPointerException if {@code password} is {@code null} + * @throws NullPointerException if {@code password} is {@code null} * @throws IllegalArgumentException if {@code password} is empty */ @SuppressWarnings("PMD.UseVarargs") diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringProtection.java b/lib/src/main/java/zeroecho/core/storage/KeyringProtection.java index bfea301..26362f8 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringProtection.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringProtection.java @@ -8,7 +8,8 @@ package zeroecho.core.storage; * Operational limits applied while opening an encrypted software keyring. * * @param operationalIterationMaximum maximum accepted PBKDF2 iteration count; - * it may restrict but never exceed the absolute decoded maximum + * it may restrict but never exceed the + * absolute decoded maximum */ public record KeyringProtection(int operationalIterationMaximum) { /** Iterations used when a new keyring is created. */ @@ -21,8 +22,8 @@ public record KeyringProtection(int operationalIterationMaximum) { /** * Validates the operational limit. * - * @throws IllegalArgumentException if the limit is below the creation - * setting or above the absolute operational maximum + * @throws IllegalArgumentException if the limit is below the creation setting + * or above the absolute operational maximum */ public KeyringProtection { if (operationalIterationMaximum < CREATION_ITERATIONS diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringRandomBytes.java b/lib/src/main/java/zeroecho/core/storage/KeyringRandomBytes.java index 7223bc3..2eb7b68 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringRandomBytes.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringRandomBytes.java @@ -7,8 +7,10 @@ package zeroecho.core.storage; /** * Fills keyring randomness buffers. * - *

    This package-private seam supports deterministic format tests; production - * creation uses the authoritative shared secure random source.

    + *

    + * This package-private seam supports deterministic format tests; production + * creation uses the authoritative shared secure random source. + *

    */ @FunctionalInterface interface KeyringRandomBytes { diff --git a/lib/src/main/java/zeroecho/core/storage/KeyringStore.java b/lib/src/main/java/zeroecho/core/storage/KeyringStore.java index 310fa6e..dae4c4d 100644 --- a/lib/src/main/java/zeroecho/core/storage/KeyringStore.java +++ b/lib/src/main/java/zeroecho/core/storage/KeyringStore.java @@ -65,35 +65,44 @@ import zeroecho.core.util.RandomSupport; /** * Path-bound encrypted local software keystore. * - *

    A keyring uses PBKDF2-HMAC-SHA-256 to unwrap one random 256-bit store - * master key. Every entry and the ordered manifest are independently protected - * by AES-256-GCM. Java class names are neither persisted nor resolved. Import + *

    + * A keyring uses PBKDF2-HMAC-SHA-256 to unwrap one random 256-bit store master + * key. Every entry and the ordered manifest are independently protected by + * AES-256-GCM. Java class names are neither persisted nor resolved. Import * mappings come exclusively from a closed mapping to the immutable algorithm * registry. Provider names are not persisted: standard key encodings are * reconstructed by the current runtime's canonical importer. HMAC persistence - * admits only HmacSHA256, HmacSHA384, and HmacSHA512.

    + * admits only HmacSHA256, HmacSHA384, and HmacSHA512. + *

    * - *

    The keyring exclusively owns its filesystem path from successful + *

    + * The keyring exclusively owns its filesystem path from successful * {@link #create(Path, KeyringPassword)} or * {@link #open(Path, KeyringPassword)} until {@link #close()}. Only POSIX * filesystems on which owner-only permissions and ownership can be verified are - * supported. Mutations replace a complete encrypted image atomically.

    + * supported. Mutations replace a complete encrypted image atomically. + *

    * - *

    The nonce-reservation sidecar is authenticated independently from AES - * entry and manifest encryption. Its 256-bit MAC key is derived from the store - * master key and binary store UUID with HKDF-HMAC-SHA-256 and the fixed domain - * label {@code zeroecho:keyring:nonce-reservation-mac:v1}. The derived key is + *

    + * The nonce-reservation sidecar is authenticated independently from AES entry + * and manifest encryption. Its 256-bit MAC key is derived from the store master + * key and binary store UUID with HKDF-HMAC-SHA-256 and the fixed domain label + * {@code zeroecho:keyring:nonce-reservation-mac:v1}. The derived key is * retained only while the store is open and is cleared on close. Previous - * sidecar versions are rejected.

    + * sidecar versions are rejected. + *

    * - *

    Instances are thread-safe. Reads may proceed concurrently; mutations and + *

    + * Instances are thread-safe. Reads may proceed concurrently; mutations and * close are exclusive. Closing clears the master and sidecar MAC keys and makes * all subsequent operations fail. The pre-release plaintext format is rejected - * and is not migrated.

    + * and is not migrated. + *

    */ // The store intentionally centralizes its closed format, crypto, and lifecycle types. @SuppressWarnings("PMD.CouplingBetweenObjects") -public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD +public final class KeyringStore implements AutoCloseable, Destroyable { + // NOPMD /* default */ static final byte[] MAGIC = { 'Z', 'E', 'K', 'R', 'I', 'N', 'G', '2' }; /* default */ static final int FORMAT_VERSION = 2; /* default */ static final int ENTRY_FORMAT_VERSION = 1; @@ -113,8 +122,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /* default */ static final int SHA256_BYTES = 32; /* default */ static final byte KDF_PBKDF2_SHA256 = 1; /* default */ static final byte AEAD_AES_256_GCM = 1; - private static final byte[] NONCE_RESERVATION_MAGIC = - { 'Z', 'E', 'K', 'N', 'O', 'N', 'C', '2' }; + private static final byte[] NONCE_RESERVATION_MAGIC = { 'Z', 'E', 'K', 'N', 'O', 'N', 'C', '2' }; private static final int NONCE_RESERVATION_VERSION = 2; private static final int NONCE_RESERVATION_TAG_BYTES = 32; private static final byte MASTER_WRAP_NONCE_DOMAIN = 1; @@ -123,17 +131,16 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD private static final String SUFFIX_PUBLIC = ".pub"; private static final String SUFFIX_PRIVATE = ".priv"; private static final long MIN_NONCE_HIGH_WATER = 1L; - private static final Set DIRECTORY_PERMISSIONS = - EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, - PosixFilePermission.OWNER_EXECUTE); - private static final Set FILE_PERMISSIONS = - EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); - private static final FileAttribute> DIRECTORY_ATTRIBUTE = - PosixFilePermissions.asFileAttribute(DIRECTORY_PERMISSIONS); - private static final FileAttribute> FILE_ATTRIBUTE = - PosixFilePermissions.asFileAttribute(FILE_PERMISSIONS); - private static final KeyringRandomBytes SYSTEM_RANDOM = destination -> - RandomSupport.getRandom().nextBytes(destination); + private static final Set DIRECTORY_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE); + private static final Set FILE_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE); + private static final FileAttribute> DIRECTORY_ATTRIBUTE = PosixFilePermissions + .asFileAttribute(DIRECTORY_PERMISSIONS); + private static final FileAttribute> FILE_ATTRIBUTE = PosixFilePermissions + .asFileAttribute(FILE_PERMISSIONS); + private static final KeyringRandomBytes SYSTEM_RANDOM = destination -> RandomSupport.getRandom() + .nextBytes(destination); private final Path path; private final Path nonceReservationPath; private final Ownership ownership; @@ -157,9 +164,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Standard encoded key forms supported by the encrypted store. */ public enum Encoding { - X509(1), - PKCS8(2), - RAW(3); + X509(1), PKCS8(2), RAW(3); private final int code; @@ -181,9 +186,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Key classifications supported by the encrypted store. */ public enum Kind { - PUBLIC_KEY(1), - PRIVATE_KEY(2), - SECRET_KEY(3); + PUBLIC_KEY(1), PRIVATE_KEY(2), SECRET_KEY(3); private final int code; @@ -201,11 +204,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private KeyringStore(Path path, Ownership ownership, - KeyringRandomBytes random, KeyringFileOperations fileOperations, - Header header, byte[] masterKey, - byte[] nonceReservationMacKey, Manifest manifest, - Map entries) { + private KeyringStore(Path path, Ownership ownership, KeyringRandomBytes random, + KeyringFileOperations fileOperations, Header header, byte[] masterKey, byte[] nonceReservationMacKey, + Manifest manifest, Map entries) { this.path = path; this.nonceReservationPath = nonceReservationPath(path); this.ownership = ownership; @@ -226,11 +227,12 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Creates a new encrypted keyring with the standard protection policy. * - * @param path new keyring file - * @param password borrowed unlock password; the caller remains responsible - * for destroying it + * @param path new keyring file + * @param password borrowed unlock password; the caller remains responsible for + * destroying it * @return open encrypted keyring - * @throws IOException if secure creation or durable persistence fails + * @throws IOException if secure creation or durable persistence + * fails * @throws GeneralSecurityException if cryptographic initialization fails */ public static KeyringStore create(Path path, KeyringPassword password) @@ -241,28 +243,27 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Creates a new encrypted keyring. * - * @param path new keyring file - * @param password borrowed unlock password + * @param path new keyring file + * @param password borrowed unlock password * @param protection operational protection policy * @return open encrypted keyring - * @throws IOException if secure creation or durable persistence fails + * @throws IOException if secure creation or durable persistence + * fails * @throws GeneralSecurityException if cryptographic initialization fails */ - public static KeyringStore create(Path path, KeyringPassword password, - KeyringProtection protection) throws IOException, GeneralSecurityException { + public static KeyringStore create(Path path, KeyringPassword password, KeyringProtection protection) + throws IOException, GeneralSecurityException { return create(path, password, protection, SYSTEM_RANDOM); } - /* default */ static KeyringStore create(Path path, KeyringPassword password, - KeyringProtection protection, KeyringRandomBytes random) - throws IOException, GeneralSecurityException { + /* default */ static KeyringStore create(Path path, KeyringPassword password, KeyringProtection protection, + KeyringRandomBytes random) throws IOException, GeneralSecurityException { return create(path, password, protection, random, KeyringFileOperations.NIO); } @SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" }) - /* default */ static KeyringStore create(Path path, KeyringPassword password, - KeyringProtection protection, KeyringRandomBytes random, - KeyringFileOperations fileOperations) + /* default */ static KeyringStore create(Path path, KeyringPassword password, KeyringProtection protection, + KeyringRandomBytes random, KeyringFileOperations fileOperations) throws IOException, GeneralSecurityException { Objects.requireNonNull(password, "password must not be null"); Objects.requireNonNull(protection, "protection must not be null"); @@ -287,19 +288,18 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD random.nextBytes(masterKey); random.nextBytes(salt); random.nextBytes(storeId); - nonceReservationMacKey = - KeyringNonceReservationKdf.derive(masterKey, storeId); + nonceReservationMacKey = KeyringNonceReservationKdf.derive(masterKey, storeId); fillMasterWrapNonce(random, wrapNonce); int prefix = randomPrefix(random); - Header provisional = new Header(storeId, KeyringProtection.CREATION_ITERATIONS, - salt, wrapNonce, new byte[0]); + Header provisional = new Header(storeId, KeyringProtection.CREATION_ITERATIONS, salt, wrapNonce, + new byte[0]); aad = masterWrapAad(provisional); kek = deriveKek(password, salt, provisional.iterations); wrapped = crypt(Cipher.ENCRYPT_MODE, kek, wrapNonce, aad, masterKey); Header header = new Header(storeId, provisional.iterations, salt, wrapNonce, wrapped); Manifest initial = new Manifest(prefix, 0, List.of()); - store = new KeyringStore(normalized, ownership, random, fileOperations, header, - masterKey, nonceReservationMacKey, initial, new LinkedHashMap<>()); + store = new KeyringStore(normalized, ownership, random, fileOperations, header, masterKey, + nonceReservationMacKey, initial, new LinkedHashMap<>()); store.persistSnapshot(new LinkedHashMap<>()); success = true; return store; @@ -325,44 +325,41 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Opens an encrypted keyring with the standard protection policy. * - * @param path keyring file + * @param path keyring file * @param password borrowed unlock password * @return open encrypted keyring - * @throws IOException if the format, filesystem, or authentication check - * fails + * @throws IOException if the format, filesystem, or authentication + * check fails * @throws GeneralSecurityException if cryptographic initialization fails */ - public static KeyringStore open(Path path, KeyringPassword password) - throws IOException, GeneralSecurityException { + public static KeyringStore open(Path path, KeyringPassword password) throws IOException, GeneralSecurityException { return open(path, password, KeyringProtection.standard()); } /** * Opens an encrypted keyring. * - * @param path keyring file - * @param password borrowed unlock password + * @param path keyring file + * @param password borrowed unlock password * @param protection operational protection policy * @return open encrypted keyring - * @throws IOException if the format, filesystem, or authentication check - * fails + * @throws IOException if the format, filesystem, or authentication + * check fails * @throws GeneralSecurityException if cryptographic initialization fails */ - public static KeyringStore open(Path path, KeyringPassword password, - KeyringProtection protection) throws IOException, GeneralSecurityException { + public static KeyringStore open(Path path, KeyringPassword password, KeyringProtection protection) + throws IOException, GeneralSecurityException { return open(path, password, protection, SYSTEM_RANDOM); } - /* default */ static KeyringStore open(Path path, KeyringPassword password, - KeyringProtection protection, KeyringRandomBytes random) - throws IOException, GeneralSecurityException { + /* default */ static KeyringStore open(Path path, KeyringPassword password, KeyringProtection protection, + KeyringRandomBytes random) throws IOException, GeneralSecurityException { return open(path, password, protection, random, KeyringFileOperations.NIO); } @SuppressWarnings({ "PMD.CloseResource", "PMD.UseTryWithResources" }) - /* default */ static KeyringStore open(Path path, KeyringPassword password, - KeyringProtection protection, KeyringRandomBytes random, - KeyringFileOperations fileOperations) + /* default */ static KeyringStore open(Path path, KeyringPassword password, KeyringProtection protection, + KeyringRandomBytes random, KeyringFileOperations fileOperations) throws IOException, GeneralSecurityException { Objects.requireNonNull(password, "password must not be null"); Objects.requireNonNull(protection, "protection must not be null"); @@ -379,19 +376,16 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD image = readStoreImage(normalized); Decoded decoded = decodeImage(image, password, protection); masterKey = decoded.masterKey; - nonceReservationMacKey = KeyringNonceReservationKdf.derive( - masterKey, decoded.header.storeId); - long reservedHighWater = readNonceReservation(normalized, ownership.owner(), - decoded.header.storeId, decoded.manifest.noncePrefix, - nonceReservationMacKey); + nonceReservationMacKey = KeyringNonceReservationKdf.derive(masterKey, decoded.header.storeId); + long reservedHighWater = readNonceReservation(normalized, ownership.owner(), decoded.header.storeId, + decoded.manifest.noncePrefix, nonceReservationMacKey); if (reservedHighWater < decoded.manifest.highWater) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } - Manifest effectiveManifest = new Manifest(decoded.manifest.noncePrefix, - reservedHighWater, decoded.manifest.entries); - KeyringStore store = new KeyringStore(normalized, ownership, - random, fileOperations, decoded.header, masterKey, nonceReservationMacKey, - effectiveManifest, decoded.entries); + Manifest effectiveManifest = new Manifest(decoded.manifest.noncePrefix, reservedHighWater, + decoded.manifest.entries); + KeyringStore store = new KeyringStore(normalized, ownership, random, fileOperations, decoded.header, + masterKey, nonceReservationMacKey, effectiveManifest, decoded.entries); success = true; return store; } finally { @@ -407,10 +401,10 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Adds or replaces an encoded public key and persists the mutation. * - * @param alias logical alias + * @param alias logical alias * @param algorithmId canonical registered algorithm identifier - * @param key exportable public key - * @throws IOException if validation or durable persistence fails + * @param key exportable public key + * @throws IOException if validation or durable persistence fails * @throws GeneralSecurityException if entry encryption fails */ public void putPublic(String alias, String algorithmId, PublicKey key) @@ -422,10 +416,10 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Adds or replaces an encoded private key and persists the mutation. * - * @param alias logical alias + * @param alias logical alias * @param algorithmId canonical registered algorithm identifier - * @param key exportable private key - * @throws IOException if validation or durable persistence fails + * @param key exportable private key + * @throws IOException if validation or durable persistence fails * @throws GeneralSecurityException if entry encryption fails */ public void putPrivate(String alias, String algorithmId, PrivateKey key) @@ -437,17 +431,17 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /** * Adds or replaces an encoded secret key and persists the mutation. * - * @param alias logical alias + * @param alias logical alias * @param algorithmId canonical registered algorithm identifier - * @param key exportable secret key - * @throws IOException if validation or durable persistence fails + * @param key exportable secret key + * @throws IOException if validation or durable persistence fails * @throws GeneralSecurityException if entry encryption fails */ public void putSecret(String alias, String algorithmId, SecretKey key) throws IOException, GeneralSecurityException { Objects.requireNonNull(key, "key must not be null"); - KeyringImportRegistry.HmacVariant hmacVariant = - KeyringImportRegistry.HmacVariant.forStoredKey(algorithmId, key); + KeyringImportRegistry.HmacVariant hmacVariant = KeyringImportRegistry.HmacVariant.forStoredKey(algorithmId, + key); put(alias, algorithmId, Kind.SECRET_KEY, Encoding.RAW, key, hmacVariant); } @@ -497,7 +491,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported key and algorithm - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public PublicWithId getPublicWithId(String alias) throws IOException, GeneralSecurityException { @@ -510,7 +504,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported key and algorithm - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public PrivateWithId getPrivateWithId(String alias) throws IOException, GeneralSecurityException { @@ -523,7 +517,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported key and algorithm - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public SecretWithId getSecretWithId(String alias) throws IOException, GeneralSecurityException { @@ -536,7 +530,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported public key - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public PublicKey getPublic(String alias) throws IOException, GeneralSecurityException { @@ -548,7 +542,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported private key - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public PrivateKey getPrivate(String alias) throws IOException, GeneralSecurityException { @@ -560,7 +554,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * * @param alias logical alias * @return imported secret key - * @throws IOException if the entry cannot be authenticated + * @throws IOException if the entry cannot be authenticated * @throws GeneralSecurityException if decryption or import fails */ public SecretKey getSecret(String alias) throws IOException, GeneralSecurityException { @@ -571,7 +565,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Algorithm identifier paired with an imported public key. * * @param algorithm canonical algorithm identifier - * @param key imported key + * @param key imported key */ public record PublicWithId(String algorithm, PublicKey key) { } @@ -580,7 +574,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Algorithm identifier paired with an imported private key. * * @param algorithm canonical algorithm identifier - * @param key imported key + * @param key imported key */ public record PrivateWithId(String algorithm, PrivateKey key) { } @@ -589,7 +583,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Algorithm identifier paired with an imported secret key. * * @param algorithm canonical algorithm identifier - * @param key imported key + * @param key imported key */ public record SecretWithId(String algorithm, SecretKey key) { } @@ -598,7 +592,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD * Clears the master and nonce-reservation MAC keys and releases filesystem * ownership. * - *

    Concurrent calls are safe and cleanup occurs exactly once.

    + *

    + * Concurrent calls are safe and cleanup occurs exactly once. + *

    * * @throws DestroyFailedException if resource release fails */ @@ -647,9 +643,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private void put(String alias, String algorithmId, Kind kind, Encoding encoding, - Key key, KeyringImportRegistry.HmacVariant hmacVariant) - throws IOException, GeneralSecurityException { + private void put(String alias, String algorithmId, Kind kind, Encoding encoding, Key key, + KeyringImportRegistry.HmacVariant hmacVariant) throws IOException, GeneralSecurityException { Objects.requireNonNull(key, "key must not be null"); validateString(alias, MAX_ALIAS_BYTES); validateString(algorithmId, MAX_METADATA_BYTES); @@ -663,8 +658,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD throw new KeyringException(Code.KEYRING_NON_EXPORTABLE_KEY); } try { - KeyringImportRegistry.validateCanonical(algorithmId, kind, encoding, - hmacVariant, key, encoded); + KeyringImportRegistry.validateCanonical(algorithmId, kind, encoding, hmacVariant, key, encoded); lifecycleLock.writeLock().lock(); try { ensureOpen(); @@ -680,16 +674,14 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD byte[] aad = null; byte[] ciphertext = null; try { - plaintext = encodeEntryPlaintext(alias, algorithmId, kind, encoding, - hmacVariant, encoded); + plaintext = encodeEntryPlaintext(alias, algorithmId, kind, encoding, hmacVariant, encoded); aad = entryAad(entryId, position); ciphertext = crypt(Cipher.ENCRYPT_MODE, masterKey, nonce, aad, plaintext); if (ciphertext.length > MAX_ENTRY_CIPHERTEXT_BYTES) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } - EncryptedEntry entry = new EncryptedEntry(entryId, position, alias, - algorithmId, kind, encoding, hmacVariant, nonce, ciphertext, - digest(ciphertext)); + EncryptedEntry entry = new EncryptedEntry(entryId, position, alias, algorithmId, kind, encoding, + hmacVariant, nonce, ciphertext, digest(ciphertext)); candidate.put(alias, entry); persistSnapshot(candidate); entries = candidate; @@ -709,8 +701,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.PreserveStackTrace") - private Imported importEntry(String alias, Kind expected) - throws IOException, GeneralSecurityException { + private Imported importEntry(String alias, Kind expected) throws IOException, GeneralSecurityException { lifecycleLock.readLock().lock(); try { ensureOpen(); @@ -726,8 +717,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD plaintext = crypt(Cipher.DECRYPT_MODE, masterKey, entry.nonce, aad, entry.ciphertext); decoded = decodeEntryPlaintext(plaintext); validateEntryBinding(entry, decoded); - Key key = KeyringImportRegistry.importKey(decoded.algorithm, decoded.kind, - decoded.encoding, decoded.hmacVariant, decoded.encoded); + Key key = KeyringImportRegistry.importKey(decoded.algorithm, decoded.kind, decoded.encoding, + decoded.hmacVariant, decoded.encoded); return new Imported(decoded.algorithm, key); } catch (AEADBadTagException exception) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); @@ -743,8 +734,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private void persistSnapshot(Map candidate) - throws IOException, GeneralSecurityException { + private void persistSnapshot(Map candidate) throws IOException, GeneralSecurityException { byte[] manifestNonce = null; byte[] manifestPlain = null; byte[] manifestAad = null; @@ -754,8 +744,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD manifestNonce = nextNonce(); manifestPlain = encodeManifest(candidate); manifestAad = manifestAad(candidate.size()); - manifestCipher = crypt(Cipher.ENCRYPT_MODE, masterKey, manifestNonce, - manifestAad, manifestPlain); + manifestCipher = crypt(Cipher.ENCRYPT_MODE, masterKey, manifestNonce, manifestAad, manifestPlain); image = encodeImage(candidate, manifestNonce, manifestCipher); writeAtomically(image); } catch (IOException | GeneralSecurityException exception) { @@ -780,15 +769,14 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings({ "PMD.PreserveStackTrace", "PMD.ExceptionAsFlowControl" }) - private void writeAtomically(Target target, Path destination, byte[] image) - throws IOException { + private void writeAtomically(Target target, Path destination, byte[] image) throws IOException { Path parent = destination.getParent(); Path temporary = null; boolean moved = false; IOException primary = null; try { - temporary = fileOperations.createTemporary(target, parent, - "." + destination.getFileName() + ".", ".tmp", FILE_ATTRIBUTE); + temporary = fileOperations.createTemporary(target, parent, "." + destination.getFileName() + ".", ".tmp", + FILE_ATTRIBUTE); validateOwnerOnly(temporary, false); fileOperations.writeTemporary(target, temporary, image); fileOperations.forceTemporary(target, temporary); @@ -844,15 +832,13 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private void writeNonceReservation(long highWater) - throws IOException, GeneralSecurityException { + private void writeNonceReservation(long highWater) throws IOException, GeneralSecurityException { byte[] authenticated = encodeNonceReservation(storeId, noncePrefix, highWater); byte[] tag = null; byte[] image = null; try { tag = hmac(nonceReservationMacKey, authenticated); - image = ByteBuffer.allocate(authenticated.length + tag.length) - .put(authenticated).put(tag).array(); + image = ByteBuffer.allocate(authenticated.length + tag.length).put(authenticated).put(tag).array(); writeSidecarAtomically(nonceReservationPath, image); } finally { wipe(authenticated); @@ -861,19 +847,14 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static long readNonceReservation(Path keyring, UserPrincipal owner, - byte[] expectedStoreId, int expectedPrefix, - byte[] nonceReservationMacKey) - throws IOException, GeneralSecurityException { + private static long readNonceReservation(Path keyring, UserPrincipal owner, byte[] expectedStoreId, + int expectedPrefix, byte[] nonceReservationMacKey) throws IOException, GeneralSecurityException { Path reservation = nonceReservationPath(keyring); validateExistingFile(reservation, owner); - byte[] image = readFixedImage(reservation, - NONCE_RESERVATION_MAGIC.length + Integer.BYTES + UUID_BYTES - + Integer.BYTES + Long.BYTES + NONCE_RESERVATION_TAG_BYTES); - byte[] authenticated = Arrays.copyOf(image, - image.length - NONCE_RESERVATION_TAG_BYTES); - byte[] actualTag = Arrays.copyOfRange(image, - authenticated.length, image.length); + byte[] image = readFixedImage(reservation, NONCE_RESERVATION_MAGIC.length + Integer.BYTES + UUID_BYTES + + Integer.BYTES + Long.BYTES + NONCE_RESERVATION_TAG_BYTES); + byte[] authenticated = Arrays.copyOf(image, image.length - NONCE_RESERVATION_TAG_BYTES); + byte[] actualTag = Arrays.copyOfRange(image, authenticated.length, image.length); byte[] expectedTag = null; try { expectedTag = hmac(nonceReservationMacKey, authenticated); @@ -889,11 +870,10 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD int prefix = buffer.getInt(); long highWater = buffer.getLong(); if (!MessageDigest.isEqual(magic, NONCE_RESERVATION_MAGIC) - || ByteBuffer.wrap(authenticated, - NONCE_RESERVATION_MAGIC.length, Integer.BYTES).getInt() - != NONCE_RESERVATION_VERSION - || !MessageDigest.isEqual(storeId, expectedStoreId) - || prefix != expectedPrefix || highWater < MIN_NONCE_HIGH_WATER) { + || ByteBuffer.wrap(authenticated, NONCE_RESERVATION_MAGIC.length, Integer.BYTES) + .getInt() != NONCE_RESERVATION_VERSION + || !MessageDigest.isEqual(storeId, expectedStoreId) || prefix != expectedPrefix + || highWater < MIN_NONCE_HIGH_WATER) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } return highWater; @@ -905,20 +885,14 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static byte[] encodeNonceReservation(byte[] storeId, int prefix, - long highWater) { - return ByteBuffer.allocate(NONCE_RESERVATION_MAGIC.length + Integer.BYTES - + UUID_BYTES + Integer.BYTES + Long.BYTES) - .put(NONCE_RESERVATION_MAGIC) - .putInt(NONCE_RESERVATION_VERSION) - .put(storeId) - .putInt(prefix) - .putLong(highWater) - .array(); + private static byte[] encodeNonceReservation(byte[] storeId, int prefix, long highWater) { + return ByteBuffer + .allocate(NONCE_RESERVATION_MAGIC.length + Integer.BYTES + UUID_BYTES + Integer.BYTES + Long.BYTES) + .put(NONCE_RESERVATION_MAGIC).putInt(NONCE_RESERVATION_VERSION).put(storeId).putInt(prefix) + .putLong(highWater).array(); } - private static byte[] hmac(byte[] key, byte[] input) - throws GeneralSecurityException { + private static byte[] hmac(byte[] key, byte[] input) throws GeneralSecurityException { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return mac.doFinal(input); @@ -928,8 +902,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD writeAtomically(Target.NONCE_RESERVATION, target, image); } - private byte[] encodeImage(Map candidate, - byte[] manifestNonce, byte[] manifestCipher) throws IOException, KeyringException { + private byte[] encodeImage(Map candidate, byte[] manifestNonce, byte[] manifestCipher) + throws IOException, KeyringException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (DataOutputStream out = new DataOutputStream(bytes)) { out.write(MAGIC); @@ -963,8 +937,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD return result; } - private byte[] encodeManifest(Map candidate) - throws IOException { + private byte[] encodeManifest(Map candidate) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (DataOutputStream out = new DataOutputStream(bytes)) { out.writeInt(MANIFEST_FORMAT_VERSION); @@ -987,11 +960,10 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD return bytes.toByteArray(); } - @SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", - "PMD.PreserveStackTrace", "PMD.ExceptionAsFlowControl", - "PMD.AvoidCatchingGenericException" }) - private static Decoded decodeImage(byte[] image, KeyringPassword password, - KeyringProtection protection) throws IOException, GeneralSecurityException { + @SuppressWarnings({ "PMD.NcssCount", "PMD.CyclomaticComplexity", "PMD.PreserveStackTrace", + "PMD.ExceptionAsFlowControl", "PMD.AvoidCatchingGenericException" }) + private static Decoded decodeImage(byte[] image, KeyringPassword password, KeyringProtection protection) + throws IOException, GeneralSecurityException { try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(image))) { byte[] magic = readFixed(in, MAGIC.length); if (!MessageDigest.isEqual(MAGIC, magic)) { @@ -1014,8 +986,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD if (wrapNonce[0] != MASTER_WRAP_NONCE_DOMAIN) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } - byte[] wrapped = readBoundedBytes(in, MASTER_KEY_BYTES + GCM_TAG_BYTES, - MASTER_KEY_BYTES + GCM_TAG_BYTES); + byte[] wrapped = readBoundedBytes(in, MASTER_KEY_BYTES + GCM_TAG_BYTES, MASTER_KEY_BYTES + GCM_TAG_BYTES); if (wrapped.length != MASTER_KEY_BYTES + GCM_TAG_BYTES) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } @@ -1047,8 +1018,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD for (int index = 0; index < count; index++) { byte[] entryId = readFixed(in, UUID_BYTES); byte[] nonce = readFixed(in, NONCE_BYTES); - byte[] ciphertext = readBoundedBytes(in, MAX_ENTRY_CIPHERTEXT_BYTES, - MAX_ENTRY_CIPHERTEXT_BYTES); + byte[] ciphertext = readBoundedBytes(in, MAX_ENTRY_CIPHERTEXT_BYTES, MAX_ENTRY_CIPHERTEXT_BYTES); wireEntries.add(new EncryptedWireEntry(entryId, nonce, ciphertext)); } byte[] manifestNonce = readFixed(in, NONCE_BYTES); @@ -1061,14 +1031,12 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD try { aad = manifestAad(storeId, count); try { - manifestPlain = crypt(Cipher.DECRYPT_MODE, masterKey, - manifestNonce, aad, manifestCipher); + manifestPlain = crypt(Cipher.DECRYPT_MODE, masterKey, manifestNonce, aad, manifestCipher); } catch (AEADBadTagException exception) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } Manifest manifest = decodeManifest(manifestPlain, count); - Map entries = - bindManifest(manifest, wireEntries, manifestNonce); + Map entries = bindManifest(manifest, wireEntries, manifestNonce); return new Decoded(header, masterKey, manifest, entries); } finally { wipe(aad); @@ -1086,8 +1054,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.PreserveStackTrace") - private static Manifest decodeManifest(byte[] plaintext, int expectedCount) - throws IOException { + private static Manifest decodeManifest(byte[] plaintext, int expectedCount) throws IOException { try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(plaintext))) { if (in.readInt() != MANIFEST_FORMAT_VERSION) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); @@ -1109,16 +1076,16 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD String algorithm = readString(in, MAX_METADATA_BYTES); Kind kind = Kind.fromCode(in.readUnsignedByte()); Encoding encoding = Encoding.fromCode(in.readUnsignedByte()); - KeyringImportRegistry.HmacVariant hmacVariant = - KeyringImportRegistry.HmacVariant.fromCode(in.readUnsignedByte()); + KeyringImportRegistry.HmacVariant hmacVariant = KeyringImportRegistry.HmacVariant + .fromCode(in.readUnsignedByte()); byte[] nonce = readFixed(in, NONCE_BYTES); int length = in.readInt(); if (length < GCM_TAG_BYTES || length > MAX_ENTRY_CIPHERTEXT_BYTES) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } byte[] digest = readFixed(in, SHA256_BYTES); - descriptors.add(new ManifestEntry(entryId, position, alias, algorithm, - kind, encoding, hmacVariant, nonce, length, digest)); + descriptors.add(new ManifestEntry(entryId, position, alias, algorithm, kind, encoding, hmacVariant, + nonce, length, digest)); } if (in.read() != -1) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); @@ -1130,9 +1097,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - private static Map bindManifest(Manifest manifest, - List wireEntries, byte[] manifestNonce) - throws KeyringException { + private static Map bindManifest(Manifest manifest, List wireEntries, + byte[] manifestNonce) throws KeyringException { if (manifest.entries.size() != wireEntries.size()) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } @@ -1145,44 +1111,38 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD for (int index = 0; index < wireEntries.size(); index++) { ManifestEntry descriptor = manifest.entries.get(index); EncryptedWireEntry wire = wireEntries.get(index); - if (descriptor.position != index - || !MessageDigest.isEqual(descriptor.entryId, wire.entryId) + if (descriptor.position != index || !MessageDigest.isEqual(descriptor.entryId, wire.entryId) || !MessageDigest.isEqual(descriptor.nonce, wire.nonce) || descriptor.ciphertextLength != wire.ciphertext.length || !MessageDigest.isEqual(descriptor.digest, digest(wire.ciphertext))) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } validateNonce(wire.nonce, manifest.noncePrefix, manifest.highWater); - if (!aliases.add(descriptor.alias) - || !ids.add(uuid(descriptor.entryId)) + if (!aliases.add(descriptor.alias) || !ids.add(uuid(descriptor.entryId)) || !nonces.add(new Nonce(wire.nonce))) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } - KeyringImportRegistry.validateMapping(descriptor.algorithm, - descriptor.kind, descriptor.encoding, descriptor.hmacVariant); - result.put(descriptor.alias, new EncryptedEntry(wire.entryId, index, - descriptor.alias, descriptor.algorithm, descriptor.kind, - descriptor.encoding, descriptor.hmacVariant, wire.nonce, - wire.ciphertext, descriptor.digest)); + KeyringImportRegistry.validateMapping(descriptor.algorithm, descriptor.kind, descriptor.encoding, + descriptor.hmacVariant); + result.put(descriptor.alias, + new EncryptedEntry(wire.entryId, index, descriptor.alias, descriptor.algorithm, descriptor.kind, + descriptor.encoding, descriptor.hmacVariant, wire.nonce, wire.ciphertext, + descriptor.digest)); } return result; } @SuppressWarnings("PMD.PreserveStackTrace") - private static byte[] encodeEntryPlaintext(String alias, String algorithm, - Kind kind, Encoding encoding, KeyringImportRegistry.HmacVariant hmacVariant, - byte[] encoded) - throws KeyringException { + private static byte[] encodeEntryPlaintext(String alias, String algorithm, Kind kind, Encoding encoding, + KeyringImportRegistry.HmacVariant hmacVariant, byte[] encoded) throws KeyringException { if (encoded.length <= 0 || encoded.length > MAX_ENTRY_CIPHERTEXT_BYTES - GCM_TAG_BYTES) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } byte[] aliasBytes = alias.getBytes(StandardCharsets.UTF_8); byte[] algorithmBytes = algorithm.getBytes(StandardCharsets.UTF_8); try { - int size = Math.addExact(Integer.BYTES + 3, - Math.addExact(lengthPrefixedSize(aliasBytes), - Math.addExact(lengthPrefixedSize(algorithmBytes), - lengthPrefixedSize(encoded)))); + int size = Math.addExact(Integer.BYTES + 3, Math.addExact(lengthPrefixedSize(aliasBytes), + Math.addExact(lengthPrefixedSize(algorithmBytes), lengthPrefixedSize(encoded)))); if (size > MAX_ENTRY_CIPHERTEXT_BYTES - GCM_TAG_BYTES) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } @@ -1222,10 +1182,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD String algorithm = readString(in, MAX_METADATA_BYTES); Kind kind = Kind.fromCode(in.readUnsignedByte()); Encoding encoding = Encoding.fromCode(in.readUnsignedByte()); - KeyringImportRegistry.HmacVariant hmacVariant = - KeyringImportRegistry.HmacVariant.fromCode(in.readUnsignedByte()); - byte[] encoded = readBoundedBytes(in, - MAX_ENTRY_CIPHERTEXT_BYTES - GCM_TAG_BYTES, + KeyringImportRegistry.HmacVariant hmacVariant = KeyringImportRegistry.HmacVariant + .fromCode(in.readUnsignedByte()); + byte[] encoded = readBoundedBytes(in, MAX_ENTRY_CIPHERTEXT_BYTES - GCM_TAG_BYTES, MAX_ENTRY_CIPHERTEXT_BYTES - GCM_TAG_BYTES); if (in.read() != -1) { wipe(encoded); @@ -1237,8 +1196,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static void validateEntryBinding(EncryptedEntry entry, EntryPlain decoded) - throws KeyringException { + private static void validateEntryBinding(EncryptedEntry entry, EntryPlain decoded) throws KeyringException { if (!entry.alias.equals(decoded.alias) || !entry.algorithm.equals(decoded.algorithm) || entry.kind != decoded.kind || entry.encoding != decoded.encoding || entry.hmacVariant != decoded.hmacVariant) { @@ -1305,8 +1263,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD return buffer.array(); } - private static void validateNonce(byte[] nonce, int prefix, long highWater) - throws KeyringException { + private static void validateNonce(byte[] nonce, int prefix, long highWater) throws KeyringException { if (nonce.length != NONCE_BYTES) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } @@ -1325,8 +1282,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD byte[] prefix = new byte[3]; try { random.nextBytes(prefix); - return (Byte.toUnsignedInt(prefix[0]) << 16) - | (Byte.toUnsignedInt(prefix[1]) << 8) + return (Byte.toUnsignedInt(prefix[0]) << 16) | (Byte.toUnsignedInt(prefix[1]) << 8) | Byte.toUnsignedInt(prefix[2]); } finally { wipe(prefix); @@ -1351,8 +1307,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } private static int readThreeBytePrefix(ByteBuffer buffer) { - return (Byte.toUnsignedInt(buffer.get()) << 16) - | (Byte.toUnsignedInt(buffer.get()) << 8) + return (Byte.toUnsignedInt(buffer.get()) << 16) | (Byte.toUnsignedInt(buffer.get()) << 8) | Byte.toUnsignedInt(buffer.get()); } @@ -1362,18 +1317,16 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, KEK_BYTES * Byte.SIZE); Arrays.fill(chars, '\0'); try { - return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") - .generateSecret(spec).getEncoded(); + return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded(); } finally { spec.clearPassword(); } } - private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad, - byte[] input) throws GeneralSecurityException { + private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad, byte[] input) + throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(mode, new SecretKeySpec(key, "AES"), - new GCMParameterSpec(GCM_TAG_BITS, nonce)); + cipher.init(mode, new SecretKeySpec(key, "AES"), new GCMParameterSpec(GCM_TAG_BITS, nonce)); cipher.updateAAD(aad); return cipher.doFinal(input); } @@ -1387,10 +1340,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static void validateIterations(int iterations, KeyringProtection protection) - throws KeyringException { - if (iterations < KeyringProtection.CREATION_ITERATIONS - || iterations > KeyringProtection.MAX_DECODED_ITERATIONS + private static void validateIterations(int iterations, KeyringProtection protection) throws KeyringException { + if (iterations < KeyringProtection.CREATION_ITERATIONS || iterations > KeyringProtection.MAX_DECODED_ITERATIONS || iterations > protection.operationalIterationMaximum()) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } @@ -1404,8 +1355,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD }; } - private static int positionFor(Map values, - String alias) { + private static int positionFor(Map values, String alias) { int index = 0; for (String current : values.keySet()) { if (current.equals(alias)) { @@ -1442,10 +1392,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static void validateString(String value, int maximumBytes) - throws KeyringException { - if (value == null || value.isBlank() - || value.getBytes(StandardCharsets.UTF_8).length > maximumBytes) { + private static void validateString(String value, int maximumBytes) throws KeyringException { + if (value == null || value.isBlank() || value.getBytes(StandardCharsets.UTF_8).length > maximumBytes) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } } @@ -1485,11 +1433,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD return value; } - private static byte[] readBoundedBytes(DataInputStream in, int maximum, - int exactOrMaximum) throws IOException { + private static byte[] readBoundedBytes(DataInputStream in, int maximum, int exactOrMaximum) throws IOException { int length = in.readInt(); - if (length < 0 || length > maximum - || exactOrMaximum < maximum && length != exactOrMaximum) { + if (length < 0 || length > maximum || exactOrMaximum < maximum && length != exactOrMaximum) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); } byte[] value = new byte[length]; @@ -1512,10 +1458,8 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD byte[] encoded = readBoundedBytes(in, maximum, maximum); try { try { - return StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(encoded)).toString(); + return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT).decode(ByteBuffer.wrap(encoded)).toString(); } catch (CharacterCodingException exception) { throw new KeyringException(Code.KEYRING_FORMAT_INVALID); } @@ -1541,8 +1485,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD @SuppressWarnings("PMD.PreserveStackTrace") private static byte[] readStoreImage(Path source) throws IOException { - try (FileChannel channel = FileChannel.open(source, - StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) { + try (FileChannel channel = FileChannel.open(source, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) { long size = channel.size(); if (size <= 0 || size > MAX_FILE_BYTES) { throw new KeyringException(Code.KEYRING_LIMIT_EXCEEDED); @@ -1576,8 +1519,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.PreserveStackTrace") - private static Path securePath(Path requested, boolean createParent) - throws IOException { + private static Path securePath(Path requested, boolean createParent) throws IOException { Objects.requireNonNull(requested, "path must not be null"); Path absolute = requested.toAbsolutePath().normalize(); Path parent = absolute.getParent(); @@ -1609,15 +1551,13 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.PreserveStackTrace") - private static void validateExistingFile(Path file, UserPrincipal expectedOwner) - throws IOException { - if (!Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS) - || Files.isSymbolicLink(file)) { + private static void validateExistingFile(Path file, UserPrincipal expectedOwner) throws IOException { + if (!Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS) || Files.isSymbolicLink(file)) { throw new KeyringException(Code.KEYRING_FILESYSTEM_UNSUPPORTED); } validateOwnerOnly(file, false); - PosixFileAttributes attributes = Files.readAttributes(file, - PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + PosixFileAttributes attributes = Files.readAttributes(file, PosixFileAttributes.class, + LinkOption.NOFOLLOW_LINKS); if (!attributes.owner().equals(expectedOwner)) { throw new KeyringException(Code.KEYRING_FILESYSTEM_UNSUPPORTED); } @@ -1631,10 +1571,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private static void validateOwnerOnly(Path path, boolean directory) - throws IOException { - PosixFileAttributes attributes = Files.readAttributes(path, - PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + private static void validateOwnerOnly(Path path, boolean directory) throws IOException { + PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class, + LinkOption.NOFOLLOW_LINKS); Set expected = directory ? DIRECTORY_PERMISSIONS : FILE_PERMISSIONS; if (!attributes.permissions().equals(expected)) { throw new KeyringException(Code.KEYRING_FILESYSTEM_UNSUPPORTED); @@ -1642,8 +1581,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } @SuppressWarnings("PMD.PreserveStackTrace") - /* default */ static void destroyTemporarySpec( - zeroecho.core.spec.AlgorithmKeySpec spec, Throwable primary) + /* default */ static void destroyTemporarySpec(zeroecho.core.spec.AlgorithmKeySpec spec, Throwable primary) throws GeneralSecurityException { if (!(spec instanceof Destroyable destroyable)) { return; @@ -1667,22 +1605,19 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private record Header(byte[] storeId, int iterations, byte[] salt, - byte[] masterWrapNonce, byte[] wrappedMasterKey) { + private record Header(byte[] storeId, int iterations, byte[] salt, byte[] masterWrapNonce, + byte[] wrappedMasterKey) { } - private record Manifest(int noncePrefix, long highWater, - List entries) { + private record Manifest(int noncePrefix, long highWater, List entries) { } - private record ManifestEntry(byte[] entryId, int position, String alias, - String algorithm, Kind kind, Encoding encoding, - KeyringImportRegistry.HmacVariant hmacVariant, - byte[] nonce, int ciphertextLength, byte[] digest) { + private record ManifestEntry(byte[] entryId, int position, String alias, String algorithm, Kind kind, + Encoding encoding, KeyringImportRegistry.HmacVariant hmacVariant, byte[] nonce, int ciphertextLength, + byte[] digest) { } - private record EncryptedWireEntry(byte[] entryId, byte[] nonce, - byte[] ciphertext) { + private record EncryptedWireEntry(byte[] entryId, byte[] nonce, byte[] ciphertext) { } /** Immutable encrypted entry retained while the store is open. */ @@ -1698,10 +1633,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD private final byte[] ciphertext; private final byte[] digest; - private EncryptedEntry(byte[] entryId, int position, String alias, - String algorithm, Kind kind, Encoding encoding, - KeyringImportRegistry.HmacVariant hmacVariant, - byte[] nonce, byte[] ciphertext, byte[] digest) { + private EncryptedEntry(byte[] entryId, int position, String alias, String algorithm, Kind kind, + Encoding encoding, KeyringImportRegistry.HmacVariant hmacVariant, byte[] nonce, byte[] ciphertext, + byte[] digest) { this.entryId = entryId.clone(); this.position = position; this.alias = alias; @@ -1715,16 +1649,14 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } - private record EntryPlain(String alias, String algorithm, Kind kind, - Encoding encoding, KeyringImportRegistry.HmacVariant hmacVariant, - byte[] encoded) { + private record EntryPlain(String alias, String algorithm, Kind kind, Encoding encoding, + KeyringImportRegistry.HmacVariant hmacVariant, byte[] encoded) { } private record Imported(String algorithm, Key key) { } - private record Decoded(Header header, byte[] masterKey, Manifest manifest, - Map entries) { + private record Decoded(Header header, byte[] masterKey, Manifest manifest, Map entries) { } /** Value-semantic nonce used only for duplicate detection. */ @@ -1764,8 +1696,9 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD /* default */ static Ownership acquire(Path keyring) throws IOException { Path lockPath = keyring.resolveSibling(keyring.getFileName() + ".lock"); try { - UserPrincipal expectedOwner = Files.readAttributes(lockPath.getParent(), - PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS).owner(); + UserPrincipal expectedOwner = Files + .readAttributes(lockPath.getParent(), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS) + .owner(); if (!Files.exists(lockPath, LinkOption.NOFOLLOW_LINKS)) { try { Files.createFile(lockPath, FILE_ATTRIBUTE); @@ -1774,8 +1707,7 @@ public final class KeyringStore implements AutoCloseable, Destroyable { // NOPMD } } validateExistingFile(lockPath, expectedOwner); - FileChannel channel = FileChannel.open(lockPath, - StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS); + FileChannel channel = FileChannel.open(lockPath, StandardOpenOption.WRITE, LinkOption.NOFOLLOW_LINKS); try { FileLock lock; try { diff --git a/lib/src/main/java/zeroecho/core/storage/package-info.java b/lib/src/main/java/zeroecho/core/storage/package-info.java index dc0c316..28e5d65 100644 --- a/lib/src/main/java/zeroecho/core/storage/package-info.java +++ b/lib/src/main/java/zeroecho/core/storage/package-info.java @@ -53,15 +53,15 @@ *

    * *

    - * Unlock passwords are destroyable, transfer ownership to the receiver, and - * are destroyed immediately after the master key is unwrapped. The unlocked - * store retains the master key, its domain-separated nonce-reservation MAC - * key, and encrypted entry records; closing the store clears this material. - * The store requires a POSIX filesystem on which owner-only permissions can be - * verified. A directory-force failure after atomic replacement makes the open - * instance unusable until close and authenticated reopen resolves which - * complete image is current. Non-exportable keys must remain behind an - * external provider reference. + * Unlock passwords are destroyable, transfer ownership to the receiver, and are + * destroyed immediately after the master key is unwrapped. The unlocked store + * retains the master key, its domain-separated nonce-reservation MAC key, and + * encrypted entry records; closing the store clears this material. The store + * requires a POSIX filesystem on which owner-only permissions can be verified. + * A directory-force failure after atomic replacement makes the open instance + * unusable until close and authenticated reopen resolves which complete image + * is current. Non-exportable keys must remain behind an external provider + * reference. *

    * *

    diff --git a/lib/src/main/java/zeroecho/core/tag/TagEngineBuilder.java b/lib/src/main/java/zeroecho/core/tag/TagEngineBuilder.java index 8541749..cd579b6 100644 --- a/lib/src/main/java/zeroecho/core/tag/TagEngineBuilder.java +++ b/lib/src/main/java/zeroecho/core/tag/TagEngineBuilder.java @@ -124,8 +124,7 @@ public final class TagEngineBuilder implements Supplier> { public static TagEngineBuilder digest(final ZeroEchoSession session, final DigestSpec spec) { Objects.requireNonNull(session, "session"); final DigestSpec s = spec == null ? DigestSpec.sha256() : spec; - return new TagEngineBuilder<>( - () -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s)); + return new TagEngineBuilder<>(() -> session.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE, s)); } /** @@ -193,8 +192,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces Ed25519 signature engines in SIGN mode * @throws NullPointerException if {@code privateKey} is {@code null} */ - public static TagEngineBuilder ed25519Sign(final ZeroEchoSession session, - final PrivateKey privateKey) { + public static TagEngineBuilder ed25519Sign(final ZeroEchoSession session, final PrivateKey privateKey) { Objects.requireNonNull(privateKey, PRIVATE_KEY); return signature(session, "Ed25519", privateKey, VoidSpec.INSTANCE); } @@ -206,8 +204,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces Ed25519 signature engines in VERIFY mode * @throws NullPointerException if {@code publicKey} is {@code null} */ - public static TagEngineBuilder ed25519Verify(final ZeroEchoSession session, - final PublicKey publicKey) { + public static TagEngineBuilder ed25519Verify(final ZeroEchoSession session, final PublicKey publicKey) { Objects.requireNonNull(publicKey, PUBLIC_KEY); return signature(session, "Ed25519", publicKey, VoidSpec.INSTANCE); } @@ -229,8 +226,7 @@ public final class TagEngineBuilder implements Supplier> { public static TagEngineBuilder rsaSign(final ZeroEchoSession session, final PrivateKey privateKey, final RsaSigSpec spec) { Objects.requireNonNull(privateKey, PRIVATE_KEY); - return signature(session, "RSA", privateKey, - spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec); + return signature(session, "RSA", privateKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec); } /** @@ -250,8 +246,7 @@ public final class TagEngineBuilder implements Supplier> { public static TagEngineBuilder rsaVerify(final ZeroEchoSession session, final PublicKey publicKey, final RsaSigSpec spec) { Objects.requireNonNull(publicKey, PUBLIC_KEY); - return signature(session, "RSA", publicKey, - spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec); + return signature(session, "RSA", publicKey, spec == null ? RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32) : spec); } /** @@ -369,8 +364,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces SLH-DSA signature engines in SIGN mode * @throws NullPointerException if {@code privateKey} is {@code null} */ - public static TagEngineBuilder slhDsaSign(final ZeroEchoSession session, - final PrivateKey privateKey) { + public static TagEngineBuilder slhDsaSign(final ZeroEchoSession session, final PrivateKey privateKey) { Objects.requireNonNull(privateKey, PRIVATE_KEY); return signature(session, "SLH-DSA", privateKey, VoidSpec.INSTANCE); } @@ -388,8 +382,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces SLH-DSA signature engines in VERIFY mode * @throws NullPointerException if {@code publicKey} is {@code null} */ - public static TagEngineBuilder slhDsaVerify(final ZeroEchoSession session, - final PublicKey publicKey) { + public static TagEngineBuilder slhDsaVerify(final ZeroEchoSession session, final PublicKey publicKey) { Objects.requireNonNull(publicKey, PUBLIC_KEY); return signature(session, "SLH-DSA", publicKey, VoidSpec.INSTANCE); } @@ -408,8 +401,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces ML-DSA signature engines in SIGN mode * @throws NullPointerException if {@code privateKey} is {@code null} */ - public static TagEngineBuilder mldsaSign(final ZeroEchoSession session, - final PrivateKey privateKey) { + public static TagEngineBuilder mldsaSign(final ZeroEchoSession session, final PrivateKey privateKey) { Objects.requireNonNull(privateKey, PRIVATE_KEY); return signature(session, "ML-DSA", privateKey, VoidSpec.INSTANCE); } @@ -428,8 +420,7 @@ public final class TagEngineBuilder implements Supplier> { * @return a builder that produces ML-DSA signature engines in VERIFY mode * @throws NullPointerException if {@code publicKey} is {@code null} */ - public static TagEngineBuilder mldsaVerify(final ZeroEchoSession session, - final PublicKey publicKey) { + public static TagEngineBuilder mldsaVerify(final ZeroEchoSession session, final PublicKey publicKey) { Objects.requireNonNull(publicKey, PUBLIC_KEY); return signature(session, "ML-DSA", publicKey, VoidSpec.INSTANCE); } diff --git a/lib/src/main/java/zeroecho/sdk/KeyBuilders.java b/lib/src/main/java/zeroecho/sdk/KeyBuilders.java index 978d91e..305fa67 100644 --- a/lib/src/main/java/zeroecho/sdk/KeyBuilders.java +++ b/lib/src/main/java/zeroecho/sdk/KeyBuilders.java @@ -25,9 +25,11 @@ import zeroecho.core.spi.SymmetricKeyImporter; /** * Session-bound entry point for exact key-material operations. * - *

    Capability lookup fails before an operation object is returned. Returned + *

    + * Capability lookup fails before an operation object is returned. Returned * objects guarantee the requested operation and report successful execution to - * the owning session's audit listener on a best-effort basis.

    + * the owning session's audit listener on a best-effort basis. + *

    * * @since 1.0 */ @@ -69,13 +71,12 @@ public final class KeyBuilders { * Resolves an exact symmetric generator. * * @param algorithmId canonical algorithm identifier - * @param specType exact specification class - * @param specification type + * @param specType exact specification class + * @param specification type * @return guaranteed generator * @throws IllegalArgumentException if the capability is absent */ - public SymmetricKeyGenerator generator(String algorithmId, - Class specType) { + public SymmetricKeyGenerator generator(String algorithmId, Class specType) { CryptoAlgorithm algorithm = session.require(algorithmId); SymmetricKeyGenerator delegate = algorithm.symmetricKeyGenerator(specType); return spec -> { @@ -89,13 +90,12 @@ public final class KeyBuilders { * Resolves an exact symmetric importer. * * @param algorithmId canonical algorithm identifier - * @param specType exact specification class - * @param specification type + * @param specType exact specification class + * @param specification type * @return guaranteed importer * @throws IllegalArgumentException if the capability is absent */ - public SymmetricKeyImporter importer(String algorithmId, - Class specType) { + public SymmetricKeyImporter importer(String algorithmId, Class specType) { CryptoAlgorithm algorithm = session.require(algorithmId); SymmetricKeyImporter delegate = algorithm.symmetricKeyImporter(specType); return spec -> { @@ -109,12 +109,13 @@ public final class KeyBuilders { * Generates a symmetric key using the exact runtime specification type. * * @param algorithmId canonical algorithm identifier - * @param spec generation specification - * @param specification type + * @param spec generation specification + * @param specification type * @return generated secret key * @throws java.security.GeneralSecurityException if generation fails - * @throws IllegalArgumentException if the capability is absent - * @throws NullPointerException if {@code spec} is {@code null} + * @throws IllegalArgumentException if the capability is absent + * @throws NullPointerException if {@code spec} is + * {@code null} */ public SecretKey generate(String algorithmId, S spec) throws java.security.GeneralSecurityException { @@ -128,12 +129,13 @@ public final class KeyBuilders { * Imports a symmetric key using the exact runtime specification type. * * @param algorithmId canonical algorithm identifier - * @param spec import specification - * @param specification type + * @param spec import specification + * @param specification type * @return imported secret key * @throws java.security.GeneralSecurityException if import fails - * @throws IllegalArgumentException if the capability is absent - * @throws NullPointerException if {@code spec} is {@code null} + * @throws IllegalArgumentException if the capability is absent + * @throws NullPointerException if {@code spec} is + * {@code null} */ public SecretKey importKey(String algorithmId, S spec) throws java.security.GeneralSecurityException { @@ -155,8 +157,8 @@ public final class KeyBuilders { * Resolves an exact key-pair generator. * * @param algorithmId canonical algorithm identifier - * @param specType exact specification class - * @param specification type + * @param specType exact specification class + * @param specification type * @return guaranteed generator * @throws IllegalArgumentException if the capability is absent */ @@ -175,13 +177,12 @@ public final class KeyBuilders { * Resolves an exact public-key importer. * * @param algorithmId canonical algorithm identifier - * @param specType exact specification class - * @param specification type + * @param specType exact specification class + * @param specification type * @return guaranteed importer * @throws IllegalArgumentException if the capability is absent */ - public PublicKeyImporter publicImporter(String algorithmId, - Class specType) { + public PublicKeyImporter publicImporter(String algorithmId, Class specType) { CryptoAlgorithm algorithm = session.require(algorithmId); PublicKeyImporter delegate = algorithm.publicKeyImporter(specType); return spec -> { @@ -195,8 +196,8 @@ public final class KeyBuilders { * Resolves an exact private-key importer. * * @param algorithmId canonical algorithm identifier - * @param specType exact specification class - * @param specification type + * @param specType exact specification class + * @param specification type * @return guaranteed importer * @throws IllegalArgumentException if the capability is absent */ @@ -215,12 +216,13 @@ public final class KeyBuilders { * Generates a key pair using the exact runtime specification type. * * @param algorithmId canonical algorithm identifier - * @param spec generation specification - * @param specification type + * @param spec generation specification + * @param specification type * @return generated key pair * @throws java.security.GeneralSecurityException if generation fails - * @throws IllegalArgumentException if the capability is absent - * @throws NullPointerException if {@code spec} is {@code null} + * @throws IllegalArgumentException if the capability is absent + * @throws NullPointerException if {@code spec} is + * {@code null} */ public KeyPair generateKeyPair(String algorithmId, S spec) throws java.security.GeneralSecurityException { @@ -234,12 +236,13 @@ public final class KeyBuilders { * Imports a public key using the exact runtime specification type. * * @param algorithmId canonical algorithm identifier - * @param spec public-key import specification - * @param specification type + * @param spec public-key import specification + * @param specification type * @return imported public key * @throws java.security.GeneralSecurityException if import fails - * @throws IllegalArgumentException if the capability is absent - * @throws NullPointerException if {@code spec} is {@code null} + * @throws IllegalArgumentException if the capability is absent + * @throws NullPointerException if {@code spec} is + * {@code null} */ public PublicKey importPublic(String algorithmId, S spec) throws java.security.GeneralSecurityException { @@ -253,12 +256,13 @@ public final class KeyBuilders { * Imports a private key using the exact runtime specification type. * * @param algorithmId canonical algorithm identifier - * @param spec private-key import specification - * @param specification type + * @param spec private-key import specification + * @param specification type * @return imported private key * @throws java.security.GeneralSecurityException if import fails - * @throws IllegalArgumentException if the capability is absent - * @throws NullPointerException if {@code spec} is {@code null} + * @throws IllegalArgumentException if the capability is absent + * @throws NullPointerException if {@code spec} is + * {@code null} */ public PrivateKey importPrivate(String algorithmId, S spec) throws java.security.GeneralSecurityException { diff --git a/lib/src/main/java/zeroecho/sdk/Pbkdf2Limits.java b/lib/src/main/java/zeroecho/sdk/Pbkdf2Limits.java index 3cb708d..cc3681c 100644 --- a/lib/src/main/java/zeroecho/sdk/Pbkdf2Limits.java +++ b/lib/src/main/java/zeroecho/sdk/Pbkdf2Limits.java @@ -8,10 +8,11 @@ package zeroecho.sdk; /** - * Explicit PBKDF2 work-factor limits for trusted configuration and decoded data. + * Explicit PBKDF2 work-factor limits for trusted configuration and decoded + * data. * - * @param operationalMaximum largest iteration count accepted from trusted local - * configuration + * @param operationalMaximum largest iteration count accepted from trusted + * local configuration * @param absoluteDecodedMaximum hard safety ceiling for untrusted decoded data * @since 1.0 */ @@ -41,8 +42,8 @@ public record Pbkdf2Limits(int operationalMaximum, int absoluteDecodedMaximum) { */ public void validateTrusted(int iterations) { if (iterations < MINIMUM || iterations > operationalMaximum) { - throw new IllegalArgumentException("PBKDF2 iterations must be in range " + MINIMUM + ".." - + operationalMaximum + ": " + iterations); + throw new IllegalArgumentException( + "PBKDF2 iterations must be in range " + MINIMUM + ".." + operationalMaximum + ": " + iterations); } } diff --git a/lib/src/main/java/zeroecho/sdk/ZeroEchoSession.java b/lib/src/main/java/zeroecho/sdk/ZeroEchoSession.java index f359c83..1237d4f 100644 --- a/lib/src/main/java/zeroecho/sdk/ZeroEchoSession.java +++ b/lib/src/main/java/zeroecho/sdk/ZeroEchoSession.java @@ -108,8 +108,8 @@ public final class ZeroEchoSession { this(CryptoPolicy.permissive(), AuditListener.noop(), AuditMode.OFF, null); } - private ZeroEchoSession(CryptoPolicy policy, AuditListener auditListener, - AuditMode auditMode, Pbkdf2Limits pbkdf2Limits) { + private ZeroEchoSession(CryptoPolicy policy, AuditListener auditListener, AuditMode auditMode, + Pbkdf2Limits pbkdf2Limits) { this.policy = Objects.requireNonNull(policy, "policy must not be null"); this.auditListener = Objects.requireNonNull(auditListener, "auditListener must not be null"); this.auditSink = AuditListeners.bestEffort(auditListener); @@ -143,8 +143,7 @@ public final class ZeroEchoSession { */ public ZeroEchoSession withAuditListener(AuditListener newAuditListener) { return new ZeroEchoSession(policy, - Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode, - pbkdf2Limits); + Objects.requireNonNull(newAuditListener, "newAuditListener must not be null"), auditMode, pbkdf2Limits); } /** @@ -198,8 +197,8 @@ public final class ZeroEchoSession { * Returns the audit listener owned by this session. * *

    - * The returned listener is the configured strategy, not mutable session - * state. It is exposed to support manual audit mode. + * The returned listener is the configured strategy, not mutable session state. + * It is exposed to support manual audit mode. *

    * * @return the non-null audit listener @@ -254,8 +253,8 @@ public final class ZeroEchoSession { * @param id canonical algorithm identifier * @param role intended key usage * @param key key compatible with the selected algorithm and role - * @param spec optional context specification, or {@code null} for the - * algorithm default + * @param spec optional context specification, or {@code null} for the algorithm + * default * @param context type * @param key type * @param context specification type @@ -275,8 +274,8 @@ public final class ZeroEchoSession { return finishContext(algorithm, context, role, spec); } - private C finishContext(CryptoAlgorithm algorithm, - C context, KeyUsage role, S spec) { + private C finishContext(CryptoAlgorithm algorithm, C context, + KeyUsage role, S spec) { if (auditMode == AuditMode.OFF) { notifyContextCreated(algorithm, role, spec); return context; @@ -321,14 +320,15 @@ public final class ZeroEchoSession { * Destroys a key and verifies that it entered the destroyed state. * * @param algorithmId algorithm identifier used as audit metadata - * @param provider provider name used as audit metadata - * @param key key to destroy; must not be {@code null} + * @param provider provider name used as audit metadata + * @param key key to destroy; must not be {@code null} * @return {@code true} only when this call transitions the key to destroyed; * {@code false} for a non-destroyable or already destroyed key - * @throws NullPointerException if {@code key} is {@code null} + * @throws NullPointerException if {@code key} is {@code null} * @throws DestroyFailedException if destruction fails or the key does not * report itself destroyed afterward - * @throws RuntimeException if the key's lifecycle implementation throws one + * @throws RuntimeException if the key's lifecycle implementation throws + * one */ public boolean destroyKey(String algorithmId, String provider, Key key) throws DestroyFailedException { Objects.requireNonNull(key, "key must not be null"); @@ -352,12 +352,10 @@ public final class ZeroEchoSession { return true; } - private void notifyContextCreated(CryptoAlgorithm algorithm, - KeyUsage role, S spec) { - Map metadata = spec == null ? Map.of() - : Map.of("specType", spec.getClass().getName()); - auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(), - role, "n/a", metadata); + private void notifyContextCreated(CryptoAlgorithm algorithm, KeyUsage role, S spec) { + Map metadata = spec == null ? Map.of() : Map.of("specType", spec.getClass().getName()); + auditSink.onContextCreatedMeta(UUID.randomUUID().toString(), algorithm.id(), algorithm.providerName(), role, + "n/a", metadata); } /* default */ void notifyKeyPairGenerated(CryptoAlgorithm algorithm, AlgorithmKeySpec spec, KeyPair keyPair) { diff --git a/lib/src/main/java/zeroecho/sdk/builders/HybridKexBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/HybridKexBuilder.java index 817fcff..f224d01 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/HybridKexBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/HybridKexBuilder.java @@ -342,10 +342,8 @@ public final class HybridKexBuilder { if (pqcAlgId == null) { throw new IllegalStateException("pqc algorithm id must be set"); } - if (classicMode == ClassicMode.CLASSIC_AGREEMENT - && (classicPrivate == null || classicPeerPublic == null)) { - throw new IllegalStateException( - "classic private key and peer public must be set for CLASSIC_AGREEMENT"); + if (classicMode == ClassicMode.CLASSIC_AGREEMENT && (classicPrivate == null || classicPeerPublic == null)) { + throw new IllegalStateException("classic private key and peer public must be set for CLASSIC_AGREEMENT"); } if (classicMode == ClassicMode.PAIR_MESSAGE && classicKeyPair == null) { throw new IllegalStateException("classic key pair must be set for PAIR_MESSAGE"); diff --git a/lib/src/main/java/zeroecho/sdk/builders/SignatureTrailerDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/SignatureTrailerDataContentBuilder.java index f6c32f0..2996a72 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/SignatureTrailerDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/SignatureTrailerDataContentBuilder.java @@ -77,12 +77,13 @@ import zeroecho.sdk.hybrid.signature.HybridSignatureProfile; *
  • {@link #single(ZeroEchoSession)}: constructs a non-hybrid * {@code SignatureContext}.
  • *
  • {@link #hybrid(ZeroEchoSession)}: constructs a hybrid - * {@code SignatureContext} via - * {@link HybridSignatureContexts}.
  • + * {@code SignatureContext} via {@link HybridSignatureContexts}. * * - *

    Context construction is in-memory. Checked I/O failures arise only when a - * built stream is attached or processed.

    + *

    + * Context construction is in-memory. Checked I/O failures arise only when a + * built stream is attached or processed. + *

    * * @since 1.0 */ @@ -253,8 +254,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil Objects.requireNonNull(algorithmId, "algorithmId"); Objects.requireNonNull(privateKey, "privateKey"); - Supplier> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN, - privateKey, spec); + Supplier> factory = () -> session.createContext(algorithmId, KeyUsage.SIGN, privateKey, + spec); return core(factory); } @@ -293,8 +294,8 @@ public final class SignatureTrailerDataContentBuilder implements DataContentBuil Objects.requireNonNull(algorithmId, "algorithmId"); Objects.requireNonNull(publicKey, "publicKey"); - Supplier> factory = () -> session.createContext(algorithmId, - KeyUsage.VERIFY, publicKey, spec); + Supplier> factory = () -> session.createContext(algorithmId, KeyUsage.VERIFY, + publicKey, spec); return core(factory); } diff --git a/lib/src/main/java/zeroecho/sdk/builders/alg/ChaChaDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/alg/ChaChaDataContentBuilder.java index c70fa84..f83a6ba 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/alg/ChaChaDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/alg/ChaChaDataContentBuilder.java @@ -567,8 +567,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder * The actual cipher work is delegated to an * {@link zeroecho.core.context.EncryptionContext} created through - * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, - * zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}. + * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}. * If the created context implements {@code ContextAware}, the configured * context is injected before the stream is attached. *

    @@ -638,8 +637,7 @@ public final class ChaChaDataContentBuilder implements DataContentBuilder * The actual cipher work is delegated to an * {@link zeroecho.core.context.EncryptionContext} created through - * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, - * zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}. + * {@link zeroecho.sdk.ZeroEchoSession#createContext(String, zeroecho.core.KeyUsage, java.security.Key, zeroecho.core.spec.ContextSpec)}. * If the created context implements {@code ContextAware}, the configured * context is injected before the stream is attached. *

    diff --git a/lib/src/main/java/zeroecho/sdk/builders/alg/DigestDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/alg/DigestDataContentBuilder.java index 3d30bf3..3d89508 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/alg/DigestDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/alg/DigestDataContentBuilder.java @@ -113,6 +113,7 @@ import zeroecho.sdk.content.api.PlainContent; */ public final class DigestDataContentBuilder implements DataContentBuilder { private final ZeroEchoSession session; + /** * OutputMode selects how the digest-computing pipeline presents its result to * callers. diff --git a/lib/src/main/java/zeroecho/sdk/builders/alg/ElgamalEncDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/alg/ElgamalEncDataContentBuilder.java index f329801..560c18d 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/alg/ElgamalEncDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/alg/ElgamalEncDataContentBuilder.java @@ -361,8 +361,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder @@ -426,8 +425,7 @@ public final class ElgamalEncDataContentBuilder implements DataContentBuilder diff --git a/lib/src/main/java/zeroecho/sdk/builders/alg/HmacDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/alg/HmacDataContentBuilder.java index a7413d2..613e560 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/alg/HmacDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/alg/HmacDataContentBuilder.java @@ -91,6 +91,7 @@ import zeroecho.sdk.content.api.PlainContent; public final class HmacDataContentBuilder implements DataContentBuilder { private static final String ALGORITHM_ID = "HMAC"; private final ZeroEchoSession session; + /** * Mode selects whether the pipeline computes an HMAC tag or verifies one. * @@ -622,8 +623,7 @@ public final class HmacDataContentBuilder implements DataContentBuilder @@ -407,8 +406,7 @@ public final class RsaEncDataContentBuilder implements DataContentBuilder diff --git a/lib/src/main/java/zeroecho/sdk/builders/alg/RsaSigDataContentBuilder.java b/lib/src/main/java/zeroecho/sdk/builders/alg/RsaSigDataContentBuilder.java index 227f7f0..55a0b75 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/alg/RsaSigDataContentBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/builders/alg/RsaSigDataContentBuilder.java @@ -109,6 +109,7 @@ import zeroecho.sdk.content.api.PlainContent; public final class RsaSigDataContentBuilder implements DataContentBuilder { private static final String ALGORITHM_ID = "RSA"; private final ZeroEchoSession session; + /** * Mode selects whether the builder signs or verifies. */ diff --git a/lib/src/main/java/zeroecho/sdk/builders/package-info.java b/lib/src/main/java/zeroecho/sdk/builders/package-info.java index f94557d..a500988 100644 --- a/lib/src/main/java/zeroecho/sdk/builders/package-info.java +++ b/lib/src/main/java/zeroecho/sdk/builders/package-info.java @@ -70,7 +70,8 @@ * {@link zeroecho.sdk.builders.alg.ElgamalEncDataContentBuilder}. *
  • RSA signatures: * {@link zeroecho.sdk.builders.alg.RsaSigDataContentBuilder}; generic signature - * trailers use {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}.
  • + * trailers use + * {@link zeroecho.sdk.builders.SignatureTrailerDataContentBuilder}. *
  • MAC and digest: {@link zeroecho.sdk.builders.alg.HmacDataContentBuilder}, * {@link zeroecho.sdk.builders.alg.DigestDataContentBuilder}.
  • *
  • KEM envelopes: {@link zeroecho.sdk.builders.alg.KemDataContentBuilder} diff --git a/lib/src/main/java/zeroecho/sdk/content/builtin/SecretPassword.java b/lib/src/main/java/zeroecho/sdk/content/builtin/SecretPassword.java index 7b67abe..a77cb3c 100644 --- a/lib/src/main/java/zeroecho/sdk/content/builtin/SecretPassword.java +++ b/lib/src/main/java/zeroecho/sdk/content/builtin/SecretPassword.java @@ -85,8 +85,8 @@ public final class SecretPassword implements SecretContent, Destroyable { } /** - * Constructs a password from a caller-owned character array. The supplied - * array is cloned and remains owned by the caller. + * Constructs a password from a caller-owned character array. The supplied array + * is cloned and remains owned by the caller. * * @param password password characters; must not be {@code null} * @throws NullPointerException if {@code password} is {@code null} diff --git a/lib/src/main/java/zeroecho/sdk/guard/Decryptor.java b/lib/src/main/java/zeroecho/sdk/guard/Decryptor.java index 8b29b09..ad3fb5a 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/Decryptor.java +++ b/lib/src/main/java/zeroecho/sdk/guard/Decryptor.java @@ -148,8 +148,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent { } catch (AEADBadTagException ex) { // wrong key/password for that entry, continue scanning if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "recipient authentication failed: {0}", - ex.getClass().getSimpleName()); + LOG.log(Level.FINE, "recipient authentication failed: {0}", ex.getClass().getSimpleName()); } } catch (GeneralSecurityException | IOException | IllegalArgumentException ex) { // entry not applicable to this opener/material; ignore and continue @@ -198,8 +197,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent { } } - private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary) - throws IOException { + private void closeAfterAttempt(InputStream input, boolean transferred, Throwable primary) throws IOException { IOException cleanupFailure = null; if (!transferred) { try { @@ -282,8 +280,7 @@ final class Decryptor implements PlainContent, MultiRecipientContent { closeOpeners(openers, primary); } - /* default */ static void closeOpeners(List ownedOpeners, Throwable primary) - throws IOException { + /* default */ static void closeOpeners(List ownedOpeners, Throwable primary) throws IOException { IOException cleanupFailure = null; for (RecipientOpener opener : ownedOpeners) { // NOPMD - each opener is closed in this loop try { @@ -309,14 +306,13 @@ final class Decryptor implements PlainContent, MultiRecipientContent { } } - private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId, - RecipientOpener opener) { + private void rejectOversizedCek(byte[] candidate, int fieldIndex, String recipientId, RecipientOpener opener) { try { if (LOG.isLoggable(Level.WARNING)) { LOG.log(Level.WARNING, "Suspicious material in field {0}: {1}/{2} returned length {3}, while {4} is the limit. Ignoring.", - new Object[] { fieldIndex, recipientId, opener.getClass().getName(), - candidate.length, keyBytes }); + new Object[] { fieldIndex, recipientId, opener.getClass().getName(), candidate.length, + keyBytes }); } } finally { Arrays.fill(candidate, (byte) 0); diff --git a/lib/src/main/java/zeroecho/sdk/guard/Encryptor.java b/lib/src/main/java/zeroecho/sdk/guard/Encryptor.java index ad19aff..69f9249 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/Encryptor.java +++ b/lib/src/main/java/zeroecho/sdk/guard/Encryptor.java @@ -84,8 +84,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent { this.keyBytes = keyBytes; this.maxRecipients = maxRecipients; this.maxEntryLen = maxEntryLen; - this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory, - "randomBytesFactory must not be null"); + this.randomBytesFactory = Objects.requireNonNull(randomBytesFactory, "randomBytesFactory must not be null"); } /** @@ -232,8 +231,7 @@ final class Encryptor implements EncryptedContent, MultiRecipientContent { return key; } - /* default */ static void closeRecipients(List ownedRecipients, Throwable primary) - throws IOException { + /* default */ static void closeRecipients(List ownedRecipients, Throwable primary) throws IOException { IOException cleanupFailure = null; for (Recipient recipient : ownedRecipients) { try { diff --git a/lib/src/main/java/zeroecho/sdk/guard/KemCtxRecipient.java b/lib/src/main/java/zeroecho/sdk/guard/KemCtxRecipient.java index 348c13e..336fc42 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/KemCtxRecipient.java +++ b/lib/src/main/java/zeroecho/sdk/guard/KemCtxRecipient.java @@ -82,8 +82,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable { * @param kekBytes KEK length; exactly 16 or 32 bytes * @param saltLen length of the random salt to apply during HKDF * @throws NullPointerException if {@code ctx} is {@code null} - * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or - * 32 + * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32 */ public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen) { this(ctx, kekBytes, saltLen, false); @@ -107,8 +106,7 @@ public final class KemCtxRecipient implements Recipient, AutoCloseable { * @param decoy {@code true} if this recipient is a decoy (fake entry that * cannot unwrap a CEK); {@code false} if it is a real recipient * @throws NullPointerException if {@code ctx} is {@code null} - * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or - * 32 + * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32 */ public KemCtxRecipient(KemContext ctx, int kekBytes, int saltLen, boolean decoy) { int validatedKekBytes = RecipientKekSizes.requireSupported(kekBytes); diff --git a/lib/src/main/java/zeroecho/sdk/guard/KemKeyDerivation.java b/lib/src/main/java/zeroecho/sdk/guard/KemKeyDerivation.java index 1d091bf..f10b215 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/KemKeyDerivation.java +++ b/lib/src/main/java/zeroecho/sdk/guard/KemKeyDerivation.java @@ -23,9 +23,9 @@ final class KemKeyDerivation { * Derives a KEK bound to the KEM algorithm identifier. * * @param sharedSecret KEM shared secret - * @param salt HKDF salt - * @param algorithmId canonical KEM algorithm identifier - * @param outputBytes requested KEK size + * @param salt HKDF salt + * @param algorithmId canonical KEM algorithm identifier + * @param outputBytes requested KEK size * @return newly allocated KEK bytes * @throws GeneralSecurityException if HKDF fails */ diff --git a/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientContent.java b/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientContent.java index 69a108f..782d99f 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientContent.java +++ b/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientContent.java @@ -46,17 +46,17 @@ import zeroecho.sdk.content.api.DataContent; * resources until processing or explicit cleanup. * *

    - * Callers must invoke {@link #close()} when a built instance is abandoned before - * {@link #getStream()} is called. Successful or failed stream construction also - * releases the owned recipient resources. Unlocking keys and password material - * supplied separately remain caller-owned and are never destroyed by this - * content. + * Callers must invoke {@link #close()} when a built instance is abandoned + * before {@link #getStream()} is called. Successful or failed stream + * construction also releases the owned recipient resources. Unlocking keys and + * password material supplied separately remain caller-owned and are never + * destroyed by this content. *

    * *

    - * Implementations are not thread-safe. Cleanup is idempotent, and content cannot - * be used after cleanup. Calling {@link #getStream()} is terminal even when stream - * construction fails. + * Implementations are not thread-safe. Cleanup is idempotent, and content + * cannot be used after cleanup. Calling {@link #getStream()} is terminal even + * when stream construction fails. *

    */ public interface MultiRecipientContent extends DataContent, Destroyable, AutoCloseable { diff --git a/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientDataSourceBuilder.java b/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientDataSourceBuilder.java index dcadd2d..d9fe14e 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientDataSourceBuilder.java +++ b/lib/src/main/java/zeroecho/sdk/guard/MultiRecipientDataSourceBuilder.java @@ -215,8 +215,8 @@ public final class MultiRecipientDataSourceBuilder ensureOpen(); RecipientKekSizes.requireSupported(kekBytes); session.pbkdf2Limits().validateTrusted(iterations); - this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false, - session.pbkdf2Limits())); + this.recipients + .add(new PasswordRecipient(password, iterations, saltLen, kekBytes, false, session.pbkdf2Limits())); return this; } @@ -236,8 +236,7 @@ public final class MultiRecipientDataSourceBuilder * @param saltLen HKDF salt length in bytes * @return this builder * @throws NullPointerException if {@code kem} is {@code null} - * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or - * 32 + * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32 */ public MultiRecipientDataSourceBuilder addRecipient(KemContext kem, int kekBytes, int saltLen) { ensureOpen(); @@ -292,8 +291,8 @@ public final class MultiRecipientDataSourceBuilder ensureOpen(); RecipientKekSizes.requireSupported(kekBytes); session.pbkdf2Limits().validateTrusted(iterations); - this.recipients.add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true, - session.pbkdf2Limits())); + this.recipients + .add(new PasswordRecipient(password, iterations, saltLen, kekBytes, true, session.pbkdf2Limits())); return this; } @@ -314,8 +313,7 @@ public final class MultiRecipientDataSourceBuilder * @param saltLen HKDF salt length in bytes * @return this builder * @throws NullPointerException if {@code kem} is {@code null} - * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or - * 32 + * @throws IllegalArgumentException if {@code kekBytes} is not exactly 16 or 32 */ public MultiRecipientDataSourceBuilder addRecipientDecoy(KemContext kem, int kekBytes, int saltLen) { ensureOpen(); @@ -384,9 +382,9 @@ public final class MultiRecipientDataSourceBuilder *

    * *

    - * The builder takes ownership of the opener. The opener must be reusable - * across all recipient entries and is closed after scanning or when the built - * content is abandoned. + * The builder takes ownership of the opener. The opener must be reusable across + * all recipient entries and is closed after scanning or when the built content + * is abandoned. *

    * * @param opener reusable opener to add @@ -479,8 +477,10 @@ public final class MultiRecipientDataSourceBuilder /** * Destroys recipient secrets still owned by this builder. * - *

    Recipients transferred to a successfully built encrypting content object - * are owned and destroyed by that object instead.

    + *

    + * Recipients transferred to a successfully built encrypting content object are + * owned and destroyed by that object instead. + *

    * * @throws DestroyFailedException if recipient cleanup fails */ diff --git a/lib/src/main/java/zeroecho/sdk/guard/PasswordOpener.java b/lib/src/main/java/zeroecho/sdk/guard/PasswordOpener.java index ffc0520..54a7f6a 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/PasswordOpener.java +++ b/lib/src/main/java/zeroecho/sdk/guard/PasswordOpener.java @@ -58,6 +58,7 @@ public final class PasswordOpener implements RecipientOpener { public PasswordOpener(Pbkdf2Limits limits) { this.limits = java.util.Objects.requireNonNull(limits, "limits must not be null"); } + /** * Attempts to open a password-based recipient entry using a password unlock * material. diff --git a/lib/src/main/java/zeroecho/sdk/guard/PasswordRecipient.java b/lib/src/main/java/zeroecho/sdk/guard/PasswordRecipient.java index 61fee1d..04d67c3 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/PasswordRecipient.java +++ b/lib/src/main/java/zeroecho/sdk/guard/PasswordRecipient.java @@ -76,8 +76,8 @@ public final class PasswordRecipient implements Recipient, Destroyable, AutoClos *
  • The caller should clear the {@code password} array after constructing the * recipient to minimize exposure in memory.
  • *
  • Choose an iteration count appropriate to the target platform to balance - * password-guessing resistance against recipient creation and opening - * latency. Counts below {@value Pbkdf2Limits#MINIMUM} are rejected.
  • + * password-guessing resistance against recipient creation and opening latency. + * Counts below {@value Pbkdf2Limits#MINIMUM} are rejected. *
  • Decoy recipients increase confidentiality by hiding the number of real * recipients but cannot successfully unwrap the CEK.
  • * diff --git a/lib/src/main/java/zeroecho/sdk/guard/RecipientKekSizes.java b/lib/src/main/java/zeroecho/sdk/guard/RecipientKekSizes.java index f4c671a..4ea24f9 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/RecipientKekSizes.java +++ b/lib/src/main/java/zeroecho/sdk/guard/RecipientKekSizes.java @@ -8,9 +8,11 @@ package zeroecho.sdk.guard; * Defines the KEK sizes supported by recipient entries without an encoded size * discriminator. * - *

    The current recipient format permits AES-128 and AES-256 wrapping only. + *

    + * The current recipient format permits AES-128 and AES-256 wrapping only. * Validation must occur before an entry is registered so every emitted entry - * remains openable by the corresponding recipient opener.

    + * remains openable by the corresponding recipient opener. + *

    * * @since 1.0 */ diff --git a/lib/src/main/java/zeroecho/sdk/guard/UnlockMaterial.java b/lib/src/main/java/zeroecho/sdk/guard/UnlockMaterial.java index cbb04fa..5d78e12 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/UnlockMaterial.java +++ b/lib/src/main/java/zeroecho/sdk/guard/UnlockMaterial.java @@ -18,9 +18,11 @@ import zeroecho.core.annotation.Describable; /** * Caller-owned session-operation input used to unlock a recipient entry. * - *

    Components accepting an {@code UnlockMaterial} borrow it and do not destroy + *

    + * Components accepting an {@code UnlockMaterial} borrow it and do not destroy * it. The caller must keep it usable until the operation completes and destroy - * password material afterwards.

    + * password material afterwards. + *

    */ public sealed interface UnlockMaterial extends Describable { /** @@ -44,8 +46,10 @@ public sealed interface UnlockMaterial extends Describable { /** * Destroyable password unlocking material backed by an owned character array. * - *

    Construction and access use defensive copies. Destruction is idempotent - * and prevents subsequent access.

    + *

    + * Construction and access use defensive copies. Destruction is idempotent and + * prevents subsequent access. + *

    */ final class Password implements UnlockMaterial, Destroyable { private final char[] characters; diff --git a/lib/src/main/java/zeroecho/sdk/guard/package-info.java b/lib/src/main/java/zeroecho/sdk/guard/package-info.java index 954d73a..ab6483f 100644 --- a/lib/src/main/java/zeroecho/sdk/guard/package-info.java +++ b/lib/src/main/java/zeroecho/sdk/guard/package-info.java @@ -108,8 +108,8 @@ * generation and recipient entries; the symmetric builder manages algorithm * parameters and payload framing. *
  • Reusable opener strategies: recipients encode entries; - * openers attempt every applicable entry and create fresh cryptographic contexts - * per attempt. Neither carries long-lived secret state.
  • + * openers attempt every applicable entry and create fresh cryptographic + * contexts per attempt. Neither carries long-lived secret state. *
  • Defensive parsing: the builder applies limits to the * number of recipients and the size of each entry blob; the symmetric stage * applies its own limits to its header and payload.
  • diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/derived/HybridDerived.java b/lib/src/main/java/zeroecho/sdk/hybrid/derived/HybridDerived.java index 8ecd03a..ac55771 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/derived/HybridDerived.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/derived/HybridDerived.java @@ -196,8 +196,8 @@ public final class HybridDerived { * construction. *

    * - * @param aes AES builder to configure (must not be null) - * @param keyBits AES key size in bits (128/192/256) + * @param aes AES builder to configure (must not be null) + * @param keyBits AES key size in bits (128/192/256) * @return the provided builder instance * @throws NullPointerException if aes is null * @throws IllegalArgumentException if keyBits is invalid @@ -225,16 +225,16 @@ public final class HybridDerived { } /** - * Derives a ChaCha key and applies it with optional AAD to the provided - * ChaCha builder. + * Derives a ChaCha key and applies it with optional AAD to the provided ChaCha + * builder. * *

    * The returned value is the same builder instance to preserve fluent pipeline * construction. *

    * - * @param chacha ChaCha builder to configure (must not be null) - * @param keyBits key size in bits (typically 256) + * @param chacha ChaCha builder to configure (must not be null) + * @param keyBits key size in bits (typically 256) * @return the provided builder instance * @throws NullPointerException if chacha is null * @throws IllegalArgumentException if keyBits is invalid diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContext.java b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContext.java index 38cfd8f..ad6042f 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContext.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContext.java @@ -150,8 +150,8 @@ public final class HybridKexContext implements MessageAgreementContext { this(profile, classic, pqc, Kdf::hkdfSha256); } - /* default */ HybridKexContext(HybridKexProfile profile, AgreementContext classic, - MessageAgreementContext pqc, SecretDeriver secretDeriver) { + /* default */ HybridKexContext(HybridKexProfile profile, AgreementContext classic, MessageAgreementContext pqc, + SecretDeriver secretDeriver) { this.profile = Objects.requireNonNull(profile, "profile"); this.classic = Objects.requireNonNull(classic, "classic"); this.pqc = Objects.requireNonNull(pqc, "pqc"); @@ -358,15 +358,14 @@ public final class HybridKexContext implements MessageAgreementContext { /** * Derives the final output from owned temporary hybrid input. * - * @param ikm combined component secrets - * @param salt HKDF salt - * @param info HKDF context information + * @param ikm combined component secrets + * @param salt HKDF salt + * @param info HKDF context information * @param outputLength requested output length * @return derived output transferred to the caller * @throws GeneralSecurityException if derivation fails */ - byte[] derive(byte[] ikm, byte[] salt, byte[] info, int outputLength) - throws GeneralSecurityException; + byte[] derive(byte[] ikm, byte[] salt, byte[] info, int outputLength) throws GeneralSecurityException; } /** diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContexts.java b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContexts.java index 2e0ad74..59b2853 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContexts.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexContexts.java @@ -83,9 +83,11 @@ import zeroecho.sdk.ZeroEchoSession; *

    * *

    Error handling

    - *

    Context construction is in-memory and reports configuration or provider - * failures through the security exception model of {@link ZeroEchoSession}. - * I/O failures remain associated with later stream processing.

    + *

    + * Context construction is in-memory and reports configuration or provider + * failures through the security exception model of {@link ZeroEchoSession}. I/O + * failures remain associated with later stream processing. + *

    * *

    Thread safety

    *

    @@ -158,8 +160,7 @@ public final class HybridKexContexts { AgreementContext classic = null; MessageAgreementContext pqc = null; try { - classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorPrivate, - classicSpec); + classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorPrivate, classicSpec); classic.setPeerPublic(classicPeerPublic); pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcPeerPublic, pqcSpec); return new HybridKexContext(profile, classic, pqc); @@ -222,8 +223,7 @@ public final class HybridKexContexts { AgreementContext classic = null; MessageAgreementContext pqc = null; try { - classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderPrivate, - classicSpec); + classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderPrivate, classicSpec); classic.setPeerPublic(classicPeerPublic); pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcResponderPrivate, pqcSpec); return new HybridKexContext(profile, classic, pqc); @@ -262,9 +262,8 @@ public final class HybridKexContexts { * @throws NullPointerException if any required argument is {@code null} */ public static HybridKexContext initiatorPairMessage(ZeroEchoSession session, HybridKexProfile profile, - String classicAlgId, - zeroecho.core.alg.common.agreement.KeyPairKey classicInitiatorKeyPair, ContextSpec classicSpec, - String pqcAlgId, PublicKey pqcPeerPublic, ContextSpec pqcSpec) { + String classicAlgId, zeroecho.core.alg.common.agreement.KeyPairKey classicInitiatorKeyPair, + ContextSpec classicSpec, String pqcAlgId, PublicKey pqcPeerPublic, ContextSpec pqcSpec) { Objects.requireNonNull(session, "session"); Objects.requireNonNull(profile, "profile"); @@ -276,8 +275,7 @@ public final class HybridKexContexts { MessageAgreementContext classic = null; MessageAgreementContext pqc = null; try { - classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorKeyPair, - classicSpec); + classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicInitiatorKeyPair, classicSpec); pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcPeerPublic, pqcSpec); return new HybridKexContext(profile, classic, pqc); } catch (RuntimeException | Error failure) { // NOPMD - close partial construction @@ -312,9 +310,8 @@ public final class HybridKexContexts { * @throws NullPointerException if any required argument is {@code null} */ public static HybridKexContext responderPairMessage(ZeroEchoSession session, HybridKexProfile profile, - String classicAlgId, - zeroecho.core.alg.common.agreement.KeyPairKey classicResponderKeyPair, ContextSpec classicSpec, - String pqcAlgId, PrivateKey pqcResponderPrivate, ContextSpec pqcSpec) { + String classicAlgId, zeroecho.core.alg.common.agreement.KeyPairKey classicResponderKeyPair, + ContextSpec classicSpec, String pqcAlgId, PrivateKey pqcResponderPrivate, ContextSpec pqcSpec) { Objects.requireNonNull(session, "session"); Objects.requireNonNull(profile, "profile"); @@ -326,8 +323,7 @@ public final class HybridKexContexts { MessageAgreementContext classic = null; MessageAgreementContext pqc = null; try { - classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderKeyPair, - classicSpec); + classic = session.createContext(classicAlgId, KeyUsage.AGREEMENT, classicResponderKeyPair, classicSpec); pqc = session.createContext(pqcAlgId, KeyUsage.AGREEMENT, pqcResponderPrivate, pqcSpec); return new HybridKexContext(profile, classic, pqc); } catch (RuntimeException | Error failure) { // NOPMD - close partial construction diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexExporter.java b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexExporter.java index 825bae7..48cacd3 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexExporter.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/kex/HybridKexExporter.java @@ -155,8 +155,10 @@ public final class HybridKexExporter implements Destroyable, AutoCloseable { /** * Overwrites the exporter root secret and salt. * - *

    Destruction is idempotent. All subsequent export or diagnostic access - * fails with {@link IllegalStateException}.

    + *

    + * Destruction is idempotent. All subsequent export or diagnostic access fails + * with {@link IllegalStateException}. + *

    */ @Override public void destroy() { diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContext.java b/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContext.java index f25a5d1..a006a0c 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContext.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContext.java @@ -118,8 +118,7 @@ final class HybridSignatureContext implements SignatureContext { * @throws IllegalArgumentException if {@code maxBufferedBytes <= 0} */ /* default */ HybridSignatureContext(ZeroEchoSession session, HybridSignatureProfile profile, - PrivateKey classicPrivate, - PrivateKey pqcPrivate, int maxBufferedBytes) { + PrivateKey classicPrivate, PrivateKey pqcPrivate, int maxBufferedBytes) { this.session = Objects.requireNonNull(session, "session"); this.profile = Objects.requireNonNull(profile, "profile"); this.classicPrivate = Objects.requireNonNull(classicPrivate, "classicPrivate"); @@ -151,8 +150,7 @@ final class HybridSignatureContext implements SignatureContext { * @throws IllegalArgumentException if {@code maxBufferedBytes <= 0} */ /* default */ HybridSignatureContext(ZeroEchoSession session, HybridSignatureProfile profile, - PublicKey classicPublic, - PublicKey pqcPublic, int maxBufferedBytes) { + PublicKey classicPublic, PublicKey pqcPublic, int maxBufferedBytes) { this.session = Objects.requireNonNull(session, "session"); this.profile = Objects.requireNonNull(profile, "profile"); this.classicPublic = Objects.requireNonNull(classicPublic, "classicPublic"); diff --git a/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContexts.java b/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContexts.java index 5930b17..73b71ab 100644 --- a/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContexts.java +++ b/lib/src/main/java/zeroecho/sdk/hybrid/signature/HybridSignatureContexts.java @@ -69,8 +69,7 @@ public final class HybridSignatureContexts { * @since 1.0 */ public static SignatureContext sign(ZeroEchoSession session, HybridSignatureProfile profile, - PrivateKey classicPrivate, - PrivateKey pqcPrivate, int maxBufferedBytes) { + PrivateKey classicPrivate, PrivateKey pqcPrivate, int maxBufferedBytes) { Objects.requireNonNull(session, "session"); Objects.requireNonNull(profile, "profile"); Objects.requireNonNull(classicPrivate, "classicPrivate"); diff --git a/lib/src/main/java/zeroecho/sdk/util/Password.java b/lib/src/main/java/zeroecho/sdk/util/Password.java index 8a397e7..469ff60 100644 --- a/lib/src/main/java/zeroecho/sdk/util/Password.java +++ b/lib/src/main/java/zeroecho/sdk/util/Password.java @@ -35,7 +35,6 @@ package zeroecho.sdk.util; import zeroecho.core.util.RandomSupport; - /** * Utility class for generating random passwords and secure random byte arrays. *

    diff --git a/lib/src/test/java/zeroecho/core/CapabilityValueSemanticsTest.java b/lib/src/test/java/zeroecho/core/CapabilityValueSemanticsTest.java index e5fc63f..85648af 100644 --- a/lib/src/test/java/zeroecho/core/CapabilityValueSemanticsTest.java +++ b/lib/src/test/java/zeroecho/core/CapabilityValueSemanticsTest.java @@ -74,9 +74,8 @@ class CapabilityValueSemanticsTest { void nullAndIncompatibleDefaultsAreRejectedAtConstruction() { System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction"); assertThrows(NullPointerException.class, () -> capability(() -> null)); - assertThrows(IllegalArgumentException.class, - () -> new Capability("DIGEST", AlgorithmFamily.DIGEST, KeyUsage.DIGEST, DigestContext.class, - NullKey.class, TestSpec.class, new OtherSpec())); + assertThrows(IllegalArgumentException.class, () -> new Capability("DIGEST", AlgorithmFamily.DIGEST, + KeyUsage.DIGEST, DigestContext.class, NullKey.class, TestSpec.class, new OtherSpec())); System.out.println("...invalidDefaultsRejected=true"); System.out.println("nullAndIncompatibleDefaultsAreRejectedAtConstruction...ok"); @@ -121,8 +120,7 @@ class CapabilityValueSemanticsTest { (key, spec) -> { runtimeSpecs.add(spec); return mock(DigestContext.class); - }, - () -> new TestSpec(Integer.toString(evaluations.incrementAndGet()))); + }, () -> new TestSpec(Integer.toString(evaluations.incrementAndGet()))); } } } diff --git a/lib/src/test/java/zeroecho/core/CatalogContractTest.java b/lib/src/test/java/zeroecho/core/CatalogContractTest.java index 38ceb5b..f75342e 100644 --- a/lib/src/test/java/zeroecho/core/CatalogContractTest.java +++ b/lib/src/test/java/zeroecho/core/CatalogContractTest.java @@ -105,9 +105,10 @@ public class CatalogContractTest { Logger jul = Logger.getLogger("zeroecho.audit"); jul.setLevel(Level.FINE); // see PROGRESS at FINE - session = new ZeroEchoSession().withAuditListener(JulAuditListenerStd.builder().logger(jul) - .infoLevel(Level.INFO).warnLevel(Level.WARNING).progressLevel(Level.FINE) - .includeStackTraces(true).build()).withAuditMode(AuditMode.WRAP); + session = new ZeroEchoSession() + .withAuditListener(JulAuditListenerStd.builder().logger(jul).infoLevel(Level.INFO) + .warnLevel(Level.WARNING).progressLevel(Level.FINE).includeStackTraces(true).build()) + .withAuditMode(AuditMode.WRAP); dump(""); dump("zeroecho.core.audit"); @@ -178,8 +179,7 @@ public class CatalogContractTest { .anyMatch(info -> info.operation() == KeyOperation.SYMMETRIC_GENERATE); // SIGN/VERIFY (asymmetric) - if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY) - && hasAsym) { + if (alg.roles().contains(KeyUsage.SIGN) && alg.roles().contains(KeyUsage.VERIFY) && hasAsym) { trySignVerify(id, msg); System.out.println(); } @@ -193,8 +193,7 @@ public class CatalogContractTest { } // KEM - if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE) - && hasAsym) { + if (alg.roles().contains(KeyUsage.ENCAPSULATE) && alg.roles().contains(KeyUsage.DECAPSULATE) && hasAsym) { tryKem(id, msg); System.out.println(); } diff --git a/lib/src/test/java/zeroecho/core/CryptoAlgorithmsAuditWrapTest.java b/lib/src/test/java/zeroecho/core/CryptoAlgorithmsAuditWrapTest.java index 04f515c..59811cf 100644 --- a/lib/src/test/java/zeroecho/core/CryptoAlgorithmsAuditWrapTest.java +++ b/lib/src/test/java/zeroecho/core/CryptoAlgorithmsAuditWrapTest.java @@ -74,10 +74,10 @@ import zeroecho.core.tag.TagEngine; * *

    * These tests focus on the internal audit wrapping path used by - * {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}. - * They verify that representative context types are wrapped as audited JDK - * proxies and that the resulting wrapper preserves the expected basic - * delegation behavior. + * {@link AuditedContexts#wrap(CryptoContext, AuditListener, KeyUsage)}. They + * verify that representative context types are wrapped as audited JDK proxies + * and that the resulting wrapper preserves the expected basic delegation + * behavior. *

    */ class CryptoAlgorithmsAuditWrapTest { diff --git a/lib/src/test/java/zeroecho/core/CryptoArchitectureTest.java b/lib/src/test/java/zeroecho/core/CryptoArchitectureTest.java index fb01a12..a511862 100644 --- a/lib/src/test/java/zeroecho/core/CryptoArchitectureTest.java +++ b/lib/src/test/java/zeroecho/core/CryptoArchitectureTest.java @@ -27,7 +27,8 @@ import zeroecho.core.context.DigestContext; import zeroecho.sdk.ZeroEchoSession; /** - * Verifies authoritative registry ownership and explicitly scoped runtime state. + * Verifies authoritative registry ownership and explicitly scoped runtime + * state. */ class CryptoArchitectureTest { @@ -51,8 +52,8 @@ class CryptoArchitectureTest { System.out.println("explicitPolicyOrder"); List events = new ArrayList<>(); AuditListener listener = policyOrderListener(events); - ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener).withPolicy( - (id, role, key, spec) -> events.add("policy")); + ZeroEchoSession allowed = new ZeroEchoSession().withAuditListener(listener) + .withPolicy((id, role, key, spec) -> events.add("policy")); try (DigestContext context = allowed.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) { assertSame(CryptoAlgorithms.require("DIGEST"), context.algorithm()); } @@ -60,19 +61,18 @@ class CryptoArchitectureTest { events.clear(); IllegalArgumentException denial = new IllegalArgumentException("controlled denial"); - ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy( - (id, role, key, spec) -> { - events.add("policy"); - throw denial; - }); + ZeroEchoSession denied = new ZeroEchoSession().withAuditListener(listener).withPolicy((id, role, key, spec) -> { + events.add("policy"); + throw denial; + }); assertSame(denial, assertThrows(IllegalArgumentException.class, () -> denied.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE))); assertEquals(List.of("policy"), events); events.clear(); IllegalStateException failure = new IllegalStateException("controlled policy failure"); - ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener).withPolicy( - (id, role, key, spec) -> { + ZeroEchoSession failing = new ZeroEchoSession().withAuditListener(listener) + .withPolicy((id, role, key, spec) -> { events.add("policy"); throw failure; }); @@ -91,8 +91,7 @@ class CryptoArchitectureTest { AtomicInteger secondEvents = new AtomicInteger(); AuditListener firstListener = contextListener(firstEvents); AuditListener secondListener = contextListener(secondEvents); - ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener) - .withAuditMode(AuditMode.WRAP); + ZeroEchoSession wrapped = new ZeroEchoSession().withAuditListener(firstListener).withAuditMode(AuditMode.WRAP); ZeroEchoSession direct = new ZeroEchoSession().withAuditListener(secondListener); try (DigestContext wrappedContext = wrapped.createContext("DIGEST", KeyUsage.DIGEST, NullKey.INSTANCE)) { diff --git a/lib/src/test/java/zeroecho/core/SecretSpecLifecycleTest.java b/lib/src/test/java/zeroecho/core/SecretSpecLifecycleTest.java index 40793ae..c67a7f8 100644 --- a/lib/src/test/java/zeroecho/core/SecretSpecLifecycleTest.java +++ b/lib/src/test/java/zeroecho/core/SecretSpecLifecycleTest.java @@ -58,15 +58,12 @@ class SecretSpecLifecycleTest { new SpecCase("zeroecho.core.alg.kyber.KyberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.mldsa.MldsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.ntru.NtruPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR), - new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8, - Factory.CONSTRUCTOR), - new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8, - Factory.CONSTRUCTOR), + new SpecCase("zeroecho.core.alg.ntruprime.NtrulPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR), + new SpecCase("zeroecho.core.alg.ntruprime.SntruPrimePrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.rsa.RsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.saber.SaberPrivateKeySpec", "pkcs8", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.slhdsa.SlhDsaPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR), - new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8, - Factory.CONSTRUCTOR), + new SpecCase("zeroecho.core.alg.sphincsplus.SphincsPlusPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR), new SpecCase("zeroecho.core.alg.xdh.XdhPrivateKeySpec", "encoded", 8, Factory.CONSTRUCTOR)); @Test @@ -131,14 +128,14 @@ class SecretSpecLifecycleTest { () -> AesKeyImportSpec.unmarshal(PairSeq.of("k.b64", aesKey, "k.b64", "%"))); assertThrows(IllegalArgumentException.class, () -> ChaChaKeyImportSpec.unmarshal(PairSeq.of("k.b64", chachaKey, "k.b64", "%"))); - assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec.unmarshal( - PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%"))); - assertThrows(IllegalArgumentException.class, () -> MldsaPrivateKeySpec.unmarshal( - PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); - assertThrows(IllegalArgumentException.class, () -> SlhDsaPrivateKeySpec.unmarshal( - PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); - assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec.unmarshal( - PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); + assertThrows(IllegalArgumentException.class, () -> HmacKeyImportSpec + .unmarshal(PairSeq.of("mac", "HmacSHA256", "k.b64", encodedPrivateKey, "k.b64", "%"))); + assertThrows(IllegalArgumentException.class, + () -> MldsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); + assertThrows(IllegalArgumentException.class, + () -> SlhDsaPrivateKeySpec.unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); + assertThrows(IllegalArgumentException.class, () -> SphincsPlusPrivateKeySpec + .unmarshal(PairSeq.of("pkcs8.b64", encodedPrivateKey, "pkcs8.b64", "%"))); System.out.println("...cases=6"); System.out.println("unmarshalCleansUpWhenMalformedDataFollowsValidSecretMaterial...ok"); } @@ -195,7 +192,8 @@ class SecretSpecLifecycleTest { generator.initialize(2048); KeyPair pair = generator.generateKeyPair(); RsaPrivateKeySpec rsaSpec = new RsaPrivateKeySpec(pair.getPrivate().getEncoded()); - PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA", rsaSpec); + PrivateKey imported = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().importPrivate("RSA", + rsaSpec); assertArrayEquals(pair.getPrivate().getEncoded(), imported.getEncoded()); assertFalse(rsaSpec.isDestroyed()); assertArrayEquals(pair.getPrivate().getEncoded(), rsaSpec.encoded()); @@ -221,9 +219,7 @@ class SecretSpecLifecycleTest { } private enum Factory { - CONSTRUCTOR, - STATIC_RAW, - HMAC + CONSTRUCTOR, STATIC_RAW, HMAC } private record SpecCase(String className, String accessor, int length, Factory factory) { diff --git a/lib/src/test/java/zeroecho/core/TargetArchitectureTest.java b/lib/src/test/java/zeroecho/core/TargetArchitectureTest.java index afd61d5..d7e3b7f 100644 --- a/lib/src/test/java/zeroecho/core/TargetArchitectureTest.java +++ b/lib/src/test/java/zeroecho/core/TargetArchitectureTest.java @@ -60,12 +60,9 @@ class TargetArchitectureTest { @Test void obsoleteArchitectureTypesAreAbsent() { String name = start("obsoleteArchitectureTypesAreAbsent"); - assertThrows(ClassNotFoundException.class, - () -> Class.forName("zeroecho.core.spi.ContextConstructorKS")); - assertThrows(ClassNotFoundException.class, - () -> Class.forName("zeroecho.core.spi.SymmetricKeyBuilder")); - assertThrows(ClassNotFoundException.class, - () -> Class.forName("zeroecho.core.spi.AsymmetricKeyBuilder")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.ContextConstructorKS")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.SymmetricKeyBuilder")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("zeroecho.core.spi.AsymmetricKeyBuilder")); for (Method method : CryptoAlgorithms.class.getDeclaredMethods()) { if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) { @@ -76,8 +73,8 @@ class TargetArchitectureTest { for (Field field : CryptoAlgorithms.class.getDeclaredFields()) { assertTrue(Modifier.isFinal(field.getModifiers()), field.toString()); } - Set removedConveniences = Set.of("generateSecret", "importSecret", "generateKeyPair", - "importPublic", "importPrivate"); + Set removedConveniences = Set.of("generateSecret", "importSecret", "generateKeyPair", "importPublic", + "importPrivate"); for (Method method : CryptoAlgorithm.class.getDeclaredMethods()) { assertFalse(removedConveniences.contains(method.getName()), method.toString()); } @@ -98,14 +95,13 @@ class TargetArchitectureTest { String name = start("auditListenerFailureCannotChangeOperationOutcome"); AuditListener failing = new AuditListener() { @Override - public void onContextCreatedMeta(String contextId, String algorithmId, String provider, - KeyUsage role, String keyFingerprint, Map metadata) { + public void onContextCreatedMeta(String contextId, String algorithmId, String provider, KeyUsage role, + String keyFingerprint, Map metadata) { throw new AssertionError("controlled listener failure"); } @Override - public void onContextClosed(String contextId, long bodyBytes, long trailerBytes, - long durationMillis) { + public void onContextClosed(String contextId, long bodyBytes, long trailerBytes, long durationMillis) { throw new AssertionError("controlled listener failure"); } }; @@ -127,14 +123,10 @@ class TargetArchitectureTest { switch (info.operation()) { case ASYMMETRIC_KEY_PAIR_GENERATE -> assertDoesNotThrow(() -> algorithm.asymmetricKeyPairGenerator(specType)); - case ASYMMETRIC_PUBLIC_IMPORT -> - assertDoesNotThrow(() -> algorithm.publicKeyImporter(specType)); - case ASYMMETRIC_PRIVATE_IMPORT -> - assertDoesNotThrow(() -> algorithm.privateKeyImporter(specType)); - case SYMMETRIC_GENERATE -> - assertDoesNotThrow(() -> algorithm.symmetricKeyGenerator(specType)); - case SYMMETRIC_IMPORT -> - assertDoesNotThrow(() -> algorithm.symmetricKeyImporter(specType)); + case ASYMMETRIC_PUBLIC_IMPORT -> assertDoesNotThrow(() -> algorithm.publicKeyImporter(specType)); + case ASYMMETRIC_PRIVATE_IMPORT -> assertDoesNotThrow(() -> algorithm.privateKeyImporter(specType)); + case SYMMETRIC_GENERATE -> assertDoesNotThrow(() -> algorithm.symmetricKeyGenerator(specType)); + case SYMMETRIC_IMPORT -> assertDoesNotThrow(() -> algorithm.symmetricKeyImporter(specType)); } } diff --git a/lib/src/test/java/zeroecho/core/ZeroEchoSessionWrapIntegrationTest.java b/lib/src/test/java/zeroecho/core/ZeroEchoSessionWrapIntegrationTest.java index 2141620..e06a133 100644 --- a/lib/src/test/java/zeroecho/core/ZeroEchoSessionWrapIntegrationTest.java +++ b/lib/src/test/java/zeroecho/core/ZeroEchoSessionWrapIntegrationTest.java @@ -67,16 +67,16 @@ class ZeroEchoSessionWrapIntegrationTest { SecretKey aesKey = new SecretKeySpec(new byte[16], "AES"); CtxInterface aesContext = Ctx.INSTANCE.getContext("wrap-integration-" + CONTEXT_IDS.incrementAndGet()); byte[] ciphertext; - try (EncryptionContext encryption = session.createContext( - "AES", KeyUsage.ENCRYPT, aesKey, AesSpec.gcm128(null))) { + try (EncryptionContext encryption = session.createContext("AES", KeyUsage.ENCRYPT, aesKey, + AesSpec.gcm128(null))) { assertProxy(encryption); ((ContextAware) encryption).setContext(aesContext); try (InputStream input = encryption.attach(new ByteArrayInputStream(message))) { ciphertext = input.readAllBytes(); } } - try (EncryptionContext decryption = session.createContext( - "AES", KeyUsage.DECRYPT, aesKey, AesSpec.gcm128(null))) { + try (EncryptionContext decryption = session.createContext("AES", KeyUsage.DECRYPT, aesKey, + AesSpec.gcm128(null))) { assertProxy(decryption); ((ContextAware) decryption).setContext(aesContext); try (InputStream input = decryption.attach(new ByteArrayInputStream(ciphertext))) { @@ -84,16 +84,14 @@ class ZeroEchoSessionWrapIntegrationTest { } } - KeyPair signingKeys = session.keyBuilders().asymmetric() - .generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); + KeyPair signingKeys = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", + Ed25519KeyGenSpec.defaultSpec()); TaggedBody signature; - try (SignatureContext signer = session.createContext( - "Ed25519", KeyUsage.SIGN, signingKeys.getPrivate())) { + try (SignatureContext signer = session.createContext("Ed25519", KeyUsage.SIGN, signingKeys.getPrivate())) { assertProxy(signer); signature = produceTag(signer, message); } - try (SignatureContext verifier = session.createContext( - "Ed25519", KeyUsage.VERIFY, signingKeys.getPublic())) { + try (SignatureContext verifier = session.createContext("Ed25519", KeyUsage.VERIFY, signingKeys.getPublic())) { assertProxy(verifier); verifier.setExpectedTag(signature.tag()); try (InputStream input = verifier.wrap(new ByteArrayInputStream(message))) { @@ -111,9 +109,7 @@ class ZeroEchoSessionWrapIntegrationTest { assertArrayEquals(message, produceTag(digest, message).body()); } - assertEquals(List.of( - "create:ENCRYPT", "create:DECRYPT", - "create:SIGN", "tag", "create:VERIFY", "verify:true", + assertEquals(List.of("create:ENCRYPT", "create:DECRYPT", "create:SIGN", "tag", "create:VERIFY", "verify:true", "create:MAC", "tag", "create:DIGEST", "tag"), listener.events); System.out.println("...contexts=6...events=" + listener.events.size()); System.out.println("registeredStreamContexts...ok"); @@ -127,10 +123,10 @@ class ZeroEchoSessionWrapIntegrationTest { KeyPair aliceKeys = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); KeyPair bobKeys = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - try (AgreementContext alice = session.createContext( - "Xdh", KeyUsage.AGREEMENT, aliceKeys.getPrivate(), XdhSpec.X25519); - AgreementContext bob = session.createContext( - "Xdh", KeyUsage.AGREEMENT, bobKeys.getPrivate(), XdhSpec.X25519)) { + try (AgreementContext alice = session.createContext("Xdh", KeyUsage.AGREEMENT, aliceKeys.getPrivate(), + XdhSpec.X25519); + AgreementContext bob = session.createContext("Xdh", KeyUsage.AGREEMENT, bobKeys.getPrivate(), + XdhSpec.X25519)) { assertProxy(alice); assertProxy(bob); alice.setPeerPublic(bobKeys.getPublic()); @@ -138,10 +134,10 @@ class ZeroEchoSessionWrapIntegrationTest { assertArrayEquals(alice.deriveSecret(), bob.deriveSecret()); } - try (MessageAgreementContext alice = session.createContext( - "Xdh", KeyUsage.AGREEMENT, new KeyPairKey(aliceKeys), XdhSpec.X25519); - MessageAgreementContext bob = session.createContext( - "Xdh", KeyUsage.AGREEMENT, new KeyPairKey(bobKeys), XdhSpec.X25519)) { + try (MessageAgreementContext alice = session.createContext("Xdh", KeyUsage.AGREEMENT, new KeyPairKey(aliceKeys), + XdhSpec.X25519); + MessageAgreementContext bob = session.createContext("Xdh", KeyUsage.AGREEMENT, new KeyPairKey(bobKeys), + XdhSpec.X25519)) { assertProxy(alice); assertProxy(bob); byte[] aliceMessage = alice.getPeerMessage(); @@ -151,10 +147,9 @@ class ZeroEchoSessionWrapIntegrationTest { assertArrayEquals(alice.deriveSecret(), bob.deriveSecret()); } - assertEquals(List.of( - "create:AGREEMENT", "create:AGREEMENT", "peer", "peer", "derived", "derived", - "create:AGREEMENT", "create:AGREEMENT", "message-get", "message-get", - "message-set", "message-set", "derived", "derived"), listener.events); + assertEquals(List.of("create:AGREEMENT", "create:AGREEMENT", "peer", "peer", "derived", "derived", + "create:AGREEMENT", "create:AGREEMENT", "message-get", "message-get", "message-set", "message-set", + "derived", "derived"), listener.events); System.out.println("...contexts=4...events=" + listener.events.size()); System.out.println("registeredAgreementContexts...ok"); } @@ -164,32 +159,30 @@ class ZeroEchoSessionWrapIntegrationTest { System.out.println("registeredKemContexts"); RecordingListener listener = new RecordingListener(); ZeroEchoSession session = wrappedSession(listener); - KeyPair recipient = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair recipient = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); KemContext.KemResult encapsulated; - try (KemContext encapsulator = session.createContext( - "ML-KEM", KeyUsage.ENCAPSULATE, recipient.getPublic(), VoidSpec.INSTANCE); - KemContext decapsulator = session.createContext( - "ML-KEM", KeyUsage.DECAPSULATE, recipient.getPrivate(), VoidSpec.INSTANCE)) { + try (KemContext encapsulator = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, recipient.getPublic(), + VoidSpec.INSTANCE); + KemContext decapsulator = session.createContext("ML-KEM", KeyUsage.DECAPSULATE, recipient.getPrivate(), + VoidSpec.INSTANCE)) { assertProxy(encapsulator); assertProxy(decapsulator); encapsulated = encapsulator.encapsulate(); assertArrayEquals(encapsulated.sharedSecret(), decapsulator.decapsulate(encapsulated.ciphertext())); } - try (MessageAgreementContext initiator = session.createContext( - "ML-KEM", KeyUsage.AGREEMENT, recipient.getPublic(), VoidSpec.INSTANCE); - MessageAgreementContext responder = session.createContext( - "ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(), VoidSpec.INSTANCE)) { + try (MessageAgreementContext initiator = session.createContext("ML-KEM", KeyUsage.AGREEMENT, + recipient.getPublic(), VoidSpec.INSTANCE); + MessageAgreementContext responder = session.createContext("ML-KEM", KeyUsage.AGREEMENT, + recipient.getPrivate(), VoidSpec.INSTANCE)) { assertProxy(initiator); assertProxy(responder); responder.setPeerMessage(initiator.getPeerMessage()); assertArrayEquals(initiator.deriveSecret(), responder.deriveSecret()); } - assertEquals(List.of( - "create:ENCAPSULATE", "create:DECAPSULATE", "encapsulated", "decapsulated", + assertEquals(List.of("create:ENCAPSULATE", "create:DECAPSULATE", "encapsulated", "decapsulated", "create:AGREEMENT", "create:AGREEMENT", "message-get", "message-set", "derived", "derived"), listener.events); System.out.println("...contexts=4...events=" + listener.events.size()); @@ -204,8 +197,8 @@ class ZeroEchoSessionWrapIntegrationTest { byte[][] tag = new byte[1][]; int tagLength = engine.tagLength(); byte[] emittedBody; - try (InputStream input = new TailStrippingInputStream( - engine.wrap(new ByteArrayInputStream(body)), tagLength, 128) { + try (InputStream input = new TailStrippingInputStream(engine.wrap(new ByteArrayInputStream(body)), tagLength, + 128) { @Override protected void processTail(byte[] tail) { tag[0] = tail.clone(); @@ -220,7 +213,8 @@ class ZeroEchoSessionWrapIntegrationTest { assertTrue(Proxy.isProxyClass(context.getClass())); } - private record TaggedBody(byte[] body, byte[] tag) {} + private record TaggedBody(byte[] body, byte[] tag) { + } private static final class RecordingListener implements AuditListener { private final List events = new ArrayList<>(); diff --git a/lib/src/test/java/zeroecho/core/alg/aes/AesDecryptionSecurityTest.java b/lib/src/test/java/zeroecho/core/alg/aes/AesDecryptionSecurityTest.java index 07c5c35..3bfc99c 100644 --- a/lib/src/test/java/zeroecho/core/alg/aes/AesDecryptionSecurityTest.java +++ b/lib/src/test/java/zeroecho/core/alg/aes/AesDecryptionSecurityTest.java @@ -41,8 +41,8 @@ class AesDecryptionSecurityTest { byte[] plaintext = repeated((byte) 0x5a, 96); Encrypted encrypted = encrypt(AesSpec.gcm128(null), plaintext); - FailureResult modifiedBody = failedDecryption( - encrypted.spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0)); + FailureResult modifiedBody = failedDecryption(encrypted.spec, KEY, encrypted.iv, + changed(encrypted.ciphertext, 0)); FailureResult modifiedTag = failedDecryption(encrypted.spec, KEY, encrypted.iv, changed(encrypted.ciphertext, encrypted.ciphertext.length - 1)); FailureResult truncatedBody = failedDecryption(encrypted.spec, KEY, encrypted.iv, @@ -52,19 +52,17 @@ class AesDecryptionSecurityTest { FailureResult trailing = failedDecryption(encrypted.spec, KEY, encrypted.iv, Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length + 1)); FailureResult wrongKey = failedDecryption(encrypted.spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext); - FailureResult wrongIv = failedDecryption( - encrypted.spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext); + FailureResult wrongIv = failedDecryption(encrypted.spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext); FailureResult empty = failedDecryption(encrypted.spec, KEY, encrypted.iv, new byte[0]); - for (FailureResult result : List.of( - modifiedBody, modifiedTag, truncatedBody, truncatedTag, trailing, wrongKey, wrongIv, empty)) { + for (FailureResult result : List.of(modifiedBody, modifiedTag, truncatedBody, truncatedTag, trailing, wrongKey, + wrongIv, empty)) { assertEquals(0, result.outputBytes()); assertAuthenticationFailure(result.failure()); } assertThrows(IOException.class, () -> decrypt(encrypted.spec, KEY, Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext)); - assertThrows(IOException.class, - () -> decrypt(encrypted.spec, KEY, null, encrypted.ciphertext)); + assertThrows(IOException.class, () -> decrypt(encrypted.spec, KEY, null, encrypted.ciphertext)); System.out.println("...cases=10"); System.out.println("gcmTamperMatrix...ok"); } @@ -102,18 +100,15 @@ class AesDecryptionSecurityTest { byte[] plaintext = repeated((byte) 0x44, 32); Encrypted encrypted = encrypt(spec, plaintext); - assertFalse(Arrays.equals(plaintext, - decrypt(spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext))); - assertFalse(Arrays.equals(plaintext, - decrypt(spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext))); - assertFalse(Arrays.equals(plaintext, - decrypt(spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0)))); + assertFalse(Arrays.equals(plaintext, decrypt(spec, KEY, changed(encrypted.iv, 0), encrypted.ciphertext))); + assertFalse(Arrays.equals(plaintext, decrypt(spec, WRONG_KEY, encrypted.iv, encrypted.ciphertext))); + assertFalse(Arrays.equals(plaintext, decrypt(spec, KEY, encrypted.iv, changed(encrypted.ciphertext, 0)))); assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv, Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length - 1))); assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv, Arrays.copyOf(encrypted.ciphertext, encrypted.ciphertext.length + 1))); - assertThrows(IOException.class, () -> decrypt(spec, KEY, - Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext)); + assertThrows(IOException.class, + () -> decrypt(spec, KEY, Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext)); assertThrows(IOException.class, () -> decrypt(spec, KEY, null, encrypted.ciphertext)); System.out.println("...cases=7"); System.out.println("cbcNoPaddingMatrix...ok"); @@ -132,8 +127,8 @@ class AesDecryptionSecurityTest { // authentication. assertThrows(IOException.class, () -> decrypt(spec, KEY, encrypted.iv, changed(encrypted.ciphertext, encrypted.ciphertext.length - 17))); - assertThrows(IOException.class, () -> decrypt(spec, KEY, - Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext)); + assertThrows(IOException.class, + () -> decrypt(spec, KEY, Arrays.copyOf(encrypted.iv, encrypted.iv.length - 1), encrypted.ciphertext)); assertThrows(IOException.class, () -> decrypt(spec, KEY, null, encrypted.ciphertext)); System.out.println("...cases=4"); System.out.println("cbcPkcsPaddingMatrix...ok"); @@ -141,7 +136,8 @@ class AesDecryptionSecurityTest { private static Encrypted encrypt(AesSpec spec, byte[] plaintext) throws Exception { CtxInterface context = newContext("aes-security-enc-"); - EncryptionContext encryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.ENCRYPT, KEY, spec); + EncryptionContext encryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.ENCRYPT, KEY, + spec); ((ContextAware) encryption).setContext(context); byte[] ciphertext; try (InputStream stream = encryption.attach(new ByteArrayInputStream(plaintext))) { @@ -157,7 +153,8 @@ class AesDecryptionSecurityTest { if (iv != null) { context.put(ConfluxKeys.iv("AES"), iv); } - EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, spec); + EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, + spec); ((ContextAware) decryption).setContext(context); try (InputStream stream = decryption.attach(new ByteArrayInputStream(ciphertext))) { return stream.readAllBytes(); @@ -170,7 +167,8 @@ class AesDecryptionSecurityTest { throws Exception { CtxInterface context = newContext("aes-security-fail-"); context.put(ConfluxKeys.iv("AES"), iv); - EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, spec); + EncryptionContext decryption = new zeroecho.sdk.ZeroEchoSession().createContext("AES", KeyUsage.DECRYPT, key, + spec); ((ContextAware) decryption).setContext(context); ByteArrayOutputStream output = new ByteArrayOutputStream(); try (InputStream stream = decryption.attach(new ByteArrayInputStream(ciphertext))) { @@ -226,7 +224,9 @@ class AesDecryptionSecurityTest { return result; } - private record Encrypted(AesSpec spec, byte[] iv, byte[] ciphertext) {} + private record Encrypted(AesSpec spec, byte[] iv, byte[] ciphertext) { + } - private record FailureResult(int outputBytes, IOException failure) {} + private record FailureResult(int outputBytes, IOException failure) { + } } diff --git a/lib/src/test/java/zeroecho/core/alg/aes/AesRandomSupportTest.java b/lib/src/test/java/zeroecho/core/alg/aes/AesRandomSupportTest.java index 7af8495..3b0eff3 100644 --- a/lib/src/test/java/zeroecho/core/alg/aes/AesRandomSupportTest.java +++ b/lib/src/test/java/zeroecho/core/alg/aes/AesRandomSupportTest.java @@ -198,7 +198,8 @@ class AesRandomSupportTest { @Test void publicBuilderExposesOnlyDecryptionIvConfiguration() throws Exception { System.out.print("AesRandomSupport/publicBuilderExposesOnlyDecryptionIvConfiguration..."); - List methodNames = Arrays.stream(AesDataContentBuilder.class.getMethods()).map(Method::getName).toList(); + List methodNames = Arrays.stream(AesDataContentBuilder.class.getMethods()).map(Method::getName) + .toList(); assertFalse(methodNames.contains("withIv")); assertTrue(methodNames.contains("withDecryptionIv")); diff --git a/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaLargeDataTest.java index 1181c8c..78edd11 100644 --- a/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaLargeDataTest.java @@ -116,17 +116,20 @@ public class ChaChaLargeDataTest { byte[] msg = randomBytes(SIZE); CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20"); - SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256()); + SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class) + .generateSecret(ChaChaKeyGenSpec.chacha256()); CtxInterface session = Ctx.INSTANCE.getContext("chacha-ctx-" + System.nanoTime()); ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(null).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, + spec); ((ContextAware) enc).setContext(session); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, + spec); ((ContextAware) dec).setContext(session); byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct))); dec.close(); @@ -143,17 +146,20 @@ public class ChaChaLargeDataTest { byte[] msg = randomBytes(SIZE); CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20"); - SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256()); + SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class) + .generateSecret(ChaChaKeyGenSpec.chacha256()); CtxInterface session = Ctx.INSTANCE.getContext("chacha-hdr-" + System.nanoTime()); ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(new ChaChaHeaderCodec()).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, + spec); ((ContextAware) enc).setContext(session); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, + spec); ((ContextAware) dec).setContext(session); byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct))); dec.close(); @@ -174,14 +180,16 @@ public class ChaChaLargeDataTest { byte[] msg = randomBytes(SIZE); CryptoAlgorithm chacha = CryptoAlgorithms.require("CHACHA20"); - SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class).generateSecret(ChaChaKeyGenSpec.chacha256()); + SecretKey key = chacha.symmetricKeyGenerator(ChaChaKeyGenSpec.class) + .generateSecret(ChaChaKeyGenSpec.chacha256()); // Encrypt with explicit counter=7 (in ctx), headerless (ctx-only). CtxInterface encCtx = Ctx.INSTANCE.getContext("chacha-ctr-enc-" + System.nanoTime()); encCtx.put(ConfluxKeys.tagBits("CHACHA20"), 7); ChaChaSpec spec = ChaChaSpec.builder().initialCounter(1).header(null).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.ENCRYPT, key, + spec); ((ContextAware) enc).setContext(encCtx); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); @@ -194,7 +202,8 @@ public class ChaChaLargeDataTest { decCtxOk.put(ConfluxKeys.iv("CHACHA20"), nonce); decCtxOk.put(ConfluxKeys.tagBits("CHACHA20"), 7); - EncryptionContext decOk = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec); + EncryptionContext decOk = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, + spec); ((ContextAware) decOk).setContext(decCtxOk); byte[] ptOk = readAll(decOk.attach(new ByteArrayInputStream(ct))); decOk.close(); @@ -205,7 +214,8 @@ public class ChaChaLargeDataTest { decCtxBad.put(ConfluxKeys.iv("CHACHA20"), nonce); decCtxBad.put(ConfluxKeys.tagBits("CHACHA20"), 8); - EncryptionContext decBad = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, spec); + EncryptionContext decBad = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20", KeyUsage.DECRYPT, key, + spec); ((ContextAware) decBad).setContext(decCtxBad); byte[] ptBad = readAll(decBad.attach(new ByteArrayInputStream(ct))); decBad.close(); @@ -235,12 +245,14 @@ public class ChaChaLargeDataTest { ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(null).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, + key, spec); ((ContextAware) enc).setContext(session); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, + key, spec); ((ContextAware) dec).setContext(session); byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct))); dec.close(); @@ -266,12 +278,14 @@ public class ChaChaLargeDataTest { ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(new ChaCha20Poly1305HeaderCodec()).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, + key, spec); ((ContextAware) enc).setContext(session); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, + key, spec); ((ContextAware) dec).setContext(session); byte[] pt = readAll(dec.attach(new ByteArrayInputStream(ct))); dec.close(); @@ -302,7 +316,8 @@ public class ChaChaLargeDataTest { ChaCha20Poly1305Spec spec = ChaCha20Poly1305Spec.builder().header(null).build(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, key, spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.ENCRYPT, + key, spec); ((ContextAware) enc).setContext(encCtx); byte[] ct = readAll(enc.attach(new ByteArrayInputStream(msg))); enc.close(); @@ -314,7 +329,8 @@ public class ChaChaLargeDataTest { byte[] nonce = encCtx.get(ConfluxKeys.iv("CHACHA20-POLY1305")); decCtx.put(ConfluxKeys.iv("CHACHA20-POLY1305"), nonce); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, key, spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("CHACHA20-POLY1305", KeyUsage.DECRYPT, + key, spec); ((ContextAware) dec).setContext(decCtx); assertThrows(IOException.class, () -> { diff --git a/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaNonceLifecycleTest.java b/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaNonceLifecycleTest.java index afb7e85..0902e59 100644 --- a/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaNonceLifecycleTest.java +++ b/lib/src/test/java/zeroecho/core/alg/chacha/ChaChaNonceLifecycleTest.java @@ -177,8 +177,8 @@ class ChaChaNonceLifecycleTest { private static ChaCha20Poly1305CipherContext encryptingContext(ChaCha20Poly1305Spec spec, SecureRandom random, CtxInterface operation) { - ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext( - new ChaCha20Poly1305Algorithm(), KEY, true, spec, random); + ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(new ChaCha20Poly1305Algorithm(), KEY, + true, spec, random); ((ContextAware) context).setContext(operation); return context; } @@ -190,8 +190,8 @@ class ChaChaNonceLifecycleTest { } private static byte[] decryptHeader(byte[] ciphertext) throws Exception { - ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext( - new ChaCha20Poly1305Algorithm(), KEY, false, HEADER_SPEC, null); + ChaCha20Poly1305CipherContext context = new ChaCha20Poly1305CipherContext(new ChaCha20Poly1305Algorithm(), KEY, + false, HEADER_SPEC, null); ((ContextAware) context).setContext(Ctx.INSTANCE.getContext("chacha-header-decrypt-" + System.nanoTime())); try (InputStream stream = context.attach(new ByteArrayInputStream(ciphertext))) { return stream.readAllBytes(); diff --git a/lib/src/test/java/zeroecho/core/alg/common/agreement/AgreementAlgorithmsRoundTripTest.java b/lib/src/test/java/zeroecho/core/alg/common/agreement/AgreementAlgorithmsRoundTripTest.java index d8a516c..03b6b23 100644 --- a/lib/src/test/java/zeroecho/core/alg/common/agreement/AgreementAlgorithmsRoundTripTest.java +++ b/lib/src/test/java/zeroecho/core/alg/common/agreement/AgreementAlgorithmsRoundTripTest.java @@ -206,8 +206,10 @@ public class AgreementAlgorithmsRoundTripTest { MessageAgreementContext bCtx = null; try { - aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, aliceKey, spec); - bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bobKey, spec); + aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, aliceKey, + spec); + bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bobKey, + spec); byte[] aMsg = aCtx.getPeerMessage(); byte[] bMsg = bCtx.getPeerMessage(); @@ -274,12 +276,12 @@ public class AgreementAlgorithmsRoundTripTest { try { // Alice (initiator): has Bob's public key - aliceCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPublic(), - VoidSpec.INSTANCE); + aliceCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, + bob.getPublic(), VoidSpec.INSTANCE); // Bob (responder): has his private key - bobCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPrivate(), - VoidSpec.INSTANCE); + bobCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, + bob.getPrivate(), VoidSpec.INSTANCE); // Initiator produces encapsulation message (ciphertext) to send byte[] enc = aliceCtx.getPeerMessage(); @@ -362,8 +364,10 @@ public class AgreementAlgorithmsRoundTripTest { AgreementContext bCtx = null; try { - aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, alice.getPrivate(), spec); - bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, bob.getPrivate(), spec); + aCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, + alice.getPrivate(), spec); + bCtx = new zeroecho.sdk.ZeroEchoSession().createContext(alg.id(), KeyUsage.AGREEMENT, + bob.getPrivate(), spec); aCtx.setPeerPublic(bob.getPublic()); bCtx.setPeerPublic(alice.getPublic()); diff --git a/lib/src/test/java/zeroecho/core/alg/ecdsa/EcdsaLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/ecdsa/EcdsaLargeDataTest.java index b6cf953..076cb0d 100644 --- a/lib/src/test/java/zeroecho/core/alg/ecdsa/EcdsaLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/ecdsa/EcdsaLargeDataTest.java @@ -135,7 +135,8 @@ public class EcdsaLargeDataTest { KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA", spec); // SIGN (streaming): emits [body][signature]; capture trailer - SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.SIGN, kp.getPrivate(), spec); + SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.SIGN, + kp.getPrivate(), spec); final byte[][] sigHolder = new byte[1][]; final int sigLen = signer.tagLength(); // should equal spec.signFixedLength() @@ -160,7 +161,8 @@ public class EcdsaLargeDataTest { System.out.println("...signature size: " + ourSig.length + " (expected " + spec.signFixedLength() + ")"); // VERIFY with our streaming verifier - SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, kp.getPublic(), spec); + SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, + kp.getPublic(), spec); verifier.setExpectedTag(ourSig); byte[] sink2; try (InputStream verIn = verifier.wrap(new ByteArrayInputStream(msg))) { @@ -181,7 +183,8 @@ public class EcdsaLargeDataTest { // Extra symmetry check (optional): our verifier must accept a JCA signature // (different bytes) byte[] jcaSig = jcaEcdsaSign(spec.jcaFactory(), kp.getPrivate(), msg); - SignatureContext verifier2 = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, kp.getPublic(), spec); + SignatureContext verifier2 = new zeroecho.sdk.ZeroEchoSession().createContext("ECDSA", KeyUsage.VERIFY, + kp.getPublic(), spec); verifier2.setExpectedTag(jcaSig); try (InputStream verIn2 = verifier2.wrap(new ByteArrayInputStream(msg))) { byte[] passthrough = readAll(verIn2); diff --git a/lib/src/test/java/zeroecho/core/alg/ed25519/Ed25519LargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/ed25519/Ed25519LargeDataTest.java index 729fc94..8e6b77b 100644 --- a/lib/src/test/java/zeroecho/core/alg/ed25519/Ed25519LargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/ed25519/Ed25519LargeDataTest.java @@ -132,11 +132,13 @@ public class Ed25519LargeDataTest { return; } - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", + Ed25519KeyGenSpec.defaultSpec()); // SIGN: context emits [body][signature] — capture trailer via // TailStrippingInputStream - SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.SIGN, kp.getPrivate(), null); + SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.SIGN, + kp.getPrivate(), null); final byte[][] sigHolder = new byte[1][]; final int sigLen = signer.tagLength(); @@ -166,7 +168,8 @@ public class Ed25519LargeDataTest { assertArrayEquals(refSig, ourSig, "signature mismatch vs JCA reference"); // VERIFY: supply expected tag and drain (throws on mismatch) - SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.VERIFY, kp.getPublic(), null); + SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed25519", KeyUsage.VERIFY, + kp.getPublic(), null); verifier.setExpectedTag(ourSig); byte[] sink2; diff --git a/lib/src/test/java/zeroecho/core/alg/ed448/Ed448LargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/ed448/Ed448LargeDataTest.java index 28d515f..cc2516c 100644 --- a/lib/src/test/java/zeroecho/core/alg/ed448/Ed448LargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/ed448/Ed448LargeDataTest.java @@ -132,11 +132,13 @@ public class Ed448LargeDataTest { return; } - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed448", Ed448KeyGenSpec.defaultSpec()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed448", + Ed448KeyGenSpec.defaultSpec()); // SIGN: context emits [body][signature] — capture trailer via // TailStrippingInputStream - SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.SIGN, kp.getPrivate(), null); + SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.SIGN, + kp.getPrivate(), null); final byte[][] sigHolder = new byte[1][]; final int sigLen = signer.tagLength(); @@ -166,7 +168,8 @@ public class Ed448LargeDataTest { assertArrayEquals(refSig, ourSig, "signature mismatch vs JCA reference"); // VERIFY: supply expected tag and drain (throws on mismatch) - SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.VERIFY, kp.getPublic(), null); + SignatureContext verifier = new zeroecho.sdk.ZeroEchoSession().createContext("Ed448", KeyUsage.VERIFY, + kp.getPublic(), null); verifier.setExpectedTag(ourSig); byte[] sink2; diff --git a/lib/src/test/java/zeroecho/core/alg/elgamal/ElgamalLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/elgamal/ElgamalLargeDataTest.java index a56a9d4..740a1df 100644 --- a/lib/src/test/java/zeroecho/core/alg/elgamal/ElgamalLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/elgamal/ElgamalLargeDataTest.java @@ -92,16 +92,19 @@ public class ElgamalLargeDataTest { byte[] msg = randomBytes(SIZE); System.out.printf("...input: %d bytes%n", msg.length); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); ElgamalEncSpec spec = ElgamalEncSpec.pkcs1(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + kp.getPublic(), spec); InputStream ctIn = enc.attach(new ByteArrayInputStream(msg)); byte[] ct = ctIn.readAllBytes(); enc.close(); System.out.printf("...encrypted: %d bytes%n", ct.length); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, kp.getPrivate(), spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, + kp.getPrivate(), spec); InputStream ptIn = dec.attach(new ByteArrayInputStream(ct)); byte[] pt = ptIn.readAllBytes(); dec.close(); @@ -120,16 +123,19 @@ public class ElgamalLargeDataTest { byte[] msg = randomBytes(SIZE); System.out.printf("...input: %d bytes%n", msg.length); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); ElgamalEncSpec spec = ElgamalEncSpec.noPadding(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + kp.getPublic(), spec); InputStream ctIn = enc.attach(new ByteArrayInputStream(msg)); byte[] ct = ctIn.readAllBytes(); enc.close(); System.out.printf("...encrypted: %d bytes%n", ct.length); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, kp.getPrivate(), spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.DECRYPT, + kp.getPrivate(), spec); InputStream ptIn = dec.attach(new ByteArrayInputStream(ct)); byte[] pt = ptIn.readAllBytes(); dec.close(); @@ -148,10 +154,12 @@ public class ElgamalLargeDataTest { byte[] msg = randomBytes(SIZE); System.out.printf("...input: %d bytes%n", msg.length); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); ElgamalEncSpec spec = ElgamalEncSpec.noPadding(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, kp.getPublic(), spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + kp.getPublic(), spec); InputStream ctIn = enc.attach(new ByteArrayInputStream(msg)); assertThrowsExactly(IllegalStateException.class, () -> ctIn.readAllBytes(), "No-padding cipher streams cannot processes incomplete blocks: 3 instead of 255"); diff --git a/lib/src/test/java/zeroecho/core/alg/hmac/HmacLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/hmac/HmacLargeDataTest.java index d9619b4..3dd0ed1 100644 --- a/lib/src/test/java/zeroecho/core/alg/hmac/HmacLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/hmac/HmacLargeDataTest.java @@ -127,7 +127,8 @@ public class HmacLargeDataTest { CryptoAlgorithm algo = CryptoAlgorithms.require(ALG_ID); // Generate a key (macName must match) - SecretKey key = algo.symmetricKeyGenerator(HmacKeyGenSpec.class).generateSecret(new HmacKeyGenSpec(JCA_MAC, 256)); + SecretKey key = algo.symmetricKeyGenerator(HmacKeyGenSpec.class) + .generateSecret(new HmacKeyGenSpec(JCA_MAC, 256)); // --- MAC (produce): engine emits [body][tag]; capture trailer --- HmacSpec spec = HmacSpec.sha256(); diff --git a/lib/src/test/java/zeroecho/core/alg/mldsa/MldsaLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/mldsa/MldsaLargeDataTest.java index b50b048..6c7c3b1 100644 --- a/lib/src/test/java/zeroecho/core/alg/mldsa/MldsaLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/mldsa/MldsaLargeDataTest.java @@ -119,11 +119,13 @@ public final class MldsaLargeDataTest { KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-DSA", spec); - SignatureContext mldsaVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, kp.getPublic()); + SignatureContext mldsaVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, + kp.getPublic()); int expectedSigLen = mldsaVerifier.tagLength(); System.out.println(INDENT + " expectedSigLen=" + expectedSigLen); - SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.SIGN, kp.getPrivate()); + SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.SIGN, + kp.getPrivate()); final byte[][] sigHolder = new byte[1][]; byte[] passthrough; @@ -178,7 +180,8 @@ public final class MldsaLargeDataTest { byte[] badSig = Arrays.copyOf(signature, signature.length); badSig[0] = (byte) (badSig[0] ^ 0x01); - SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, kp.getPublic()); + SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("ML-DSA", KeyUsage.VERIFY, + kp.getPublic()); try { badVerifier.setExpectedTag(badSig); diff --git a/lib/src/test/java/zeroecho/core/alg/rsa/BlockGeometryTest.java b/lib/src/test/java/zeroecho/core/alg/rsa/BlockGeometryTest.java index f14344a..7fee995 100644 --- a/lib/src/test/java/zeroecho/core/alg/rsa/BlockGeometryTest.java +++ b/lib/src/test/java/zeroecho/core/alg/rsa/BlockGeometryTest.java @@ -47,8 +47,7 @@ class BlockGeometryTest { assertEquals(2, first.inChunkSize()); assertEquals(3, first.outChunkSize()); assertEquals(0, first.finalizationOutputChunks()); - assertEquals("BlockGeometry[inChunkSize=2, outChunkSize=3, finalizationOutputChunks=0]", - first.toString()); + assertEquals("BlockGeometry[inChunkSize=2, outChunkSize=3, finalizationOutputChunks=0]", first.toString()); System.out.println("...legacyFields=true"); System.out.println("valueSemantics...ok"); } @@ -56,7 +55,8 @@ class BlockGeometryTest { @Test void actualRsaPath() throws Exception { System.out.println("actualRsaPath"); - KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); RsaEncSpec spec = RsaEncSpec.oaep(RsaEncSpec.Hash.SHA256); BlockGeometry encryptGeometry = BlockGeometry.forRsa(spec, keyPair.getPublic(), true); BlockGeometry decryptGeometry = BlockGeometry.forRsa(spec, keyPair.getPrivate(), false); @@ -66,7 +66,8 @@ class BlockGeometryTest { assertEquals(256, decryptGeometry.inChunkSize()); assertEquals(256, decryptGeometry.outChunkSize()); - EncryptionContext context = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, keyPair.getPublic(), spec); + EncryptionContext context = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + keyPair.getPublic(), spec); try (InputStream stream = context.attach(new ByteArrayInputStream(new byte[] { 1, 2, 3 }))) { assertEquals(256, stream.readAllBytes().length); } finally { diff --git a/lib/src/test/java/zeroecho/core/alg/rsa/RsaLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/rsa/RsaLargeDataTest.java index db7cbe0..d44dfce 100644 --- a/lib/src/test/java/zeroecho/core/alg/rsa/RsaLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/rsa/RsaLargeDataTest.java @@ -88,16 +88,19 @@ public class RsaLargeDataTest { byte[] msg = randomBytes(SIZE); System.out.printf("...input: %d bytes%n", msg.length); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); RsaEncSpec spec = RsaEncSpec.oaep(RsaEncSpec.Hash.SHA256); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, kp.getPublic(), spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + kp.getPublic(), spec); InputStream ctIn = enc.attach(new ByteArrayInputStream(msg)); byte[] ct = ctIn.readAllBytes(); enc.close(); System.out.printf("...encrypted: %d bytes%n", ct.length); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, kp.getPrivate(), spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, + kp.getPrivate(), spec); InputStream ptIn = dec.attach(new ByteArrayInputStream(ct)); byte[] pt = ptIn.readAllBytes(); dec.close(); @@ -116,16 +119,19 @@ public class RsaLargeDataTest { byte[] msg = randomBytes(SIZE); System.out.printf("...input: %d bytes%n", msg.length); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); RsaEncSpec spec = RsaEncSpec.pkcs1v15(); - EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, kp.getPublic(), spec); + EncryptionContext enc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + kp.getPublic(), spec); InputStream ctIn = enc.attach(new ByteArrayInputStream(msg)); byte[] ct = ctIn.readAllBytes(); enc.close(); System.out.printf("...encrypted: %d bytes%n", ct.length); - EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, kp.getPrivate(), spec); + EncryptionContext dec = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.DECRYPT, + kp.getPrivate(), spec); InputStream ptIn = dec.attach(new ByteArrayInputStream(ct)); byte[] pt = ptIn.readAllBytes(); dec.close(); diff --git a/lib/src/test/java/zeroecho/core/alg/slhdsa/SlhDsaLargeDataTest.java b/lib/src/test/java/zeroecho/core/alg/slhdsa/SlhDsaLargeDataTest.java index 64763f0..016b6a1 100644 --- a/lib/src/test/java/zeroecho/core/alg/slhdsa/SlhDsaLargeDataTest.java +++ b/lib/src/test/java/zeroecho/core/alg/slhdsa/SlhDsaLargeDataTest.java @@ -126,14 +126,16 @@ public final class SlhDsaLargeDataTest { // Create verifier FIRST to obtain tag length via // SlhDsaSignatureContext.sigLenFromPublicKey. - SignatureContext verifierCtx = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, kp.getPublic()); + SignatureContext verifierCtx = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, + kp.getPublic()); int expectedSigLen = verifierCtx.tagLength(); System.out.println(INDENT + " expectedSigLen=" + expectedSigLen); // Now sign and strip trailer using the expected length from verifier (not from // signer). - SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.SIGN, kp.getPrivate()); + SignatureContext signer = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.SIGN, + kp.getPrivate()); final byte[][] sigHolder = new byte[1][]; byte[] passthrough; @@ -188,7 +190,8 @@ public final class SlhDsaLargeDataTest { byte[] badSig = Arrays.copyOf(signature, signature.length); badSig[0] = (byte) (badSig[0] ^ 0x01); - SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, kp.getPublic()); + SignatureContext badVerifier = new zeroecho.sdk.ZeroEchoSession().createContext("SLH-DSA", KeyUsage.VERIFY, + kp.getPublic()); try { badVerifier.setExpectedTag(badSig); diff --git a/lib/src/test/java/zeroecho/core/audit/AuditedContextsRegressionTest.java b/lib/src/test/java/zeroecho/core/audit/AuditedContextsRegressionTest.java index 91fd682..2291d49 100644 --- a/lib/src/test/java/zeroecho/core/audit/AuditedContextsRegressionTest.java +++ b/lib/src/test/java/zeroecho/core/audit/AuditedContextsRegressionTest.java @@ -48,9 +48,8 @@ class AuditedContextsRegressionTest { DigestContext target = mock(DigestContext.class); when(target.tagLength()).thenReturn(2); when(target.wrap(any(InputStream.class))).thenAnswer(invocation -> invocation.getArgument(0)); - DigestContext unrelated = (DigestContext) Proxy.newProxyInstance( - DigestContext.class.getClassLoader(), new Class[] { DigestContext.class }, - (proxy, method, arguments) -> { + DigestContext unrelated = (DigestContext) Proxy.newProxyInstance(DigestContext.class.getClassLoader(), + new Class[] { DigestContext.class }, (proxy, method, arguments) -> { try { return method.invoke(target, arguments); } catch (InvocationTargetException exception) { @@ -62,7 +61,8 @@ class AuditedContextsRegressionTest { assertNotSame(unrelated, wrapped); assertTrue(Proxy.isProxyClass(wrapped.getClass())); - assertArrayEquals(new byte[] { 1, 2 }, wrapped.wrap(new ByteArrayInputStream(new byte[] { 1, 2 })).readAllBytes()); + assertArrayEquals(new byte[] { 1, 2 }, + wrapped.wrap(new ByteArrayInputStream(new byte[] { 1, 2 })).readAllBytes()); assertSame(wrapped, AuditedContexts.wrap(wrapped, AuditListener.noop(), KeyUsage.DIGEST)); System.out.println("...nestedProxy=true"); System.out.println("unrelatedProxyGetsWrapped...ok"); @@ -179,8 +179,8 @@ class AuditedContextsRegressionTest { AgreementContext agreement = mock(AgreementContext.class); doThrow(peerFailure).when(agreement).setPeerPublic(any(PublicKey.class)); when(agreement.deriveSecret()).thenThrow(deriveFailure); - AgreementContext wrappedAgreement = (AgreementContext) AuditedContexts.wrap( - agreement, listener, KeyUsage.AGREEMENT); + AgreementContext wrappedAgreement = (AgreementContext) AuditedContexts.wrap(agreement, listener, + KeyUsage.AGREEMENT); assertSame(peerFailure, assertThrows(IllegalArgumentException.class, () -> wrappedAgreement.setPeerPublic(mock(PublicKey.class)))); @@ -193,14 +193,13 @@ class AuditedContextsRegressionTest { doThrow(setMessageFailure).when(messageAgreement).setPeerMessage(any(byte[].class)); when(messageAgreement.getPeerMessage()).thenThrow(getMessageFailure); when(messageAgreement.deriveSecret()).thenThrow(messageDeriveFailure); - MessageAgreementContext wrappedMessage = (MessageAgreementContext) AuditedContexts.wrap( - messageAgreement, listener, KeyUsage.AGREEMENT); + MessageAgreementContext wrappedMessage = (MessageAgreementContext) AuditedContexts.wrap(messageAgreement, + listener, KeyUsage.AGREEMENT); - assertSame(setMessageFailure, assertThrows(IllegalArgumentException.class, - () -> wrappedMessage.setPeerMessage(new byte[] { 1 }))); + assertSame(setMessageFailure, + assertThrows(IllegalArgumentException.class, () -> wrappedMessage.setPeerMessage(new byte[] { 1 }))); assertSame(getMessageFailure, assertThrows(IllegalStateException.class, wrappedMessage::getPeerMessage)); - assertSame(messageDeriveFailure, - assertThrows(IllegalStateException.class, wrappedMessage::deriveSecret)); + assertSame(messageDeriveFailure, assertThrows(IllegalStateException.class, wrappedMessage::deriveSecret)); assertEquals(List.of(peerFailure, deriveFailure, setMessageFailure, getMessageFailure, messageDeriveFailure), listener.failures); diff --git a/lib/src/test/java/zeroecho/core/audit/JulAuditListenerStdSecurityTest.java b/lib/src/test/java/zeroecho/core/audit/JulAuditListenerStdSecurityTest.java index 0b0f7ab..f459781 100644 --- a/lib/src/test/java/zeroecho/core/audit/JulAuditListenerStdSecurityTest.java +++ b/lib/src/test/java/zeroecho/core/audit/JulAuditListenerStdSecurityTest.java @@ -65,8 +65,8 @@ class JulAuditListenerStdSecurityTest { } private static JulAuditListenerStd listener(RecordingHandler handler) { - Logger logger = Logger.getLogger( - JulAuditListenerStdSecurityTest.class.getName() + "." + LOGGER_IDS.incrementAndGet()); + Logger logger = Logger + .getLogger(JulAuditListenerStdSecurityTest.class.getName() + "." + LOGGER_IDS.incrementAndGet()); logger.setUseParentHandlers(false); logger.setLevel(Level.ALL); handler.setLevel(Level.ALL); diff --git a/lib/src/test/java/zeroecho/core/io/CipherTransformInputStreamBuilderTest.java b/lib/src/test/java/zeroecho/core/io/CipherTransformInputStreamBuilderTest.java index 0e5fe66..804aa59 100644 --- a/lib/src/test/java/zeroecho/core/io/CipherTransformInputStreamBuilderTest.java +++ b/lib/src/test/java/zeroecho/core/io/CipherTransformInputStreamBuilderTest.java @@ -25,10 +25,8 @@ class CipherTransformInputStreamBuilderTest { assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, 0, 0)); assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, 1, -1)); assertThrows(IllegalArgumentException.class, () -> new TestStream(Integer.MAX_VALUE, 1, 2, 0)); - assertThrows(IllegalArgumentException.class, - () -> new TestStream(2, 1, Integer.MAX_VALUE, 1)); - assertThrows(IllegalArgumentException.class, - () -> new TestStream(2, Integer.MAX_VALUE, 1, 1)); + assertThrows(IllegalArgumentException.class, () -> new TestStream(2, 1, Integer.MAX_VALUE, 1)); + assertThrows(IllegalArgumentException.class, () -> new TestStream(2, Integer.MAX_VALUE, 1, 1)); new TestStream(2, 1, 1, 0); new TestStream(2, 1, 1, 1); @@ -57,16 +55,14 @@ class CipherTransformInputStreamBuilderTest { System.out.print("CipherBuilder/algorithm-guard..."); CountingInputStream gcmInput = new CountingInputStream(); Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding"); - assertThrows(IllegalArgumentException.class, - () -> CipherTransformInputStreamBuilder.builder().withUpstream(gcmInput).withCipher(gcm) - .withIndependentBlocks().build()); + assertThrows(IllegalArgumentException.class, () -> CipherTransformInputStreamBuilder.builder() + .withUpstream(gcmInput).withCipher(gcm).withIndependentBlocks().build()); assertEquals(0, gcmInput.reads); CountingInputStream cbcInput = new CountingInputStream(); Cipher cbc = Cipher.getInstance("AES/CBC/PKCS5Padding"); - assertThrows(IllegalArgumentException.class, - () -> CipherTransformInputStreamBuilder.builder().withUpstream(cbcInput).withCipher(cbc) - .withLeftZeroPadding(true).withIndependentBlocks().build()); + assertThrows(IllegalArgumentException.class, () -> CipherTransformInputStreamBuilder.builder() + .withUpstream(cbcInput).withCipher(cbc).withLeftZeroPadding(true).withIndependentBlocks().build()); assertEquals(0, cbcInput.reads); System.out.println("ok"); } diff --git a/lib/src/test/java/zeroecho/core/marshal/PairSeqTest.java b/lib/src/test/java/zeroecho/core/marshal/PairSeqTest.java index ccd5ef4..8d19b7c 100644 --- a/lib/src/test/java/zeroecho/core/marshal/PairSeqTest.java +++ b/lib/src/test/java/zeroecho/core/marshal/PairSeqTest.java @@ -44,8 +44,8 @@ class PairSeqTest { assertEquals("pair 0 value must not be null", assertThrows(IllegalArgumentException.class, () -> PairSeq.of("key", null)).getMessage()); assertEquals("pair 1 key must not be null", - assertThrows(IllegalArgumentException.class, - () -> PairSeq.of("first", "value", null, "other")).getMessage()); + assertThrows(IllegalArgumentException.class, () -> PairSeq.of("first", "value", null, "other")) + .getMessage()); String[] source = { "key", "value" }; PairSeq sequence = PairSeq.of(source); @@ -66,8 +66,7 @@ class PairSeqTest { IOException partial = new IOException("partial"); FailingAppendable partialOutput = new FailingAppendable(2, partial); - IOException partialActual = assertThrows(IOException.class, - () -> PairSeq.of("a", "b").writeTo(partialOutput)); + IOException partialActual = assertThrows(IOException.class, () -> PairSeq.of("a", "b").writeTo(partialOutput)); assertSame(partial, partialActual); assertEquals("a=", partialOutput.output.toString()); diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringAlgorithmCoverageTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringAlgorithmCoverageTest.java index 57e8c3f..4eb9dea 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringAlgorithmCoverageTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringAlgorithmCoverageTest.java @@ -50,8 +50,7 @@ import zeroecho.sdk.util.BouncyCastleActivator; * then proves that the reconstructed key remains operational. */ class KeyringAlgorithmCoverageTest { - private static final byte[] MESSAGE = "keyring-algorithm-matrix".getBytes( - java.nio.charset.StandardCharsets.UTF_8); + private static final byte[] MESSAGE = "keyring-algorithm-matrix".getBytes(java.nio.charset.StandardCharsets.UTF_8); @TempDir Path temporaryDirectory; @@ -62,8 +61,7 @@ class KeyringAlgorithmCoverageTest { @Test void persistentImporterUniverseHasAcceptedCardinality() { - List mappings = - KeyringImportRegistry.mappings(); + List mappings = KeyringImportRegistry.mappings(); assertEquals(19, count(mappings, KeyringStore.Kind.PUBLIC_KEY)); assertEquals(19, count(mappings, KeyringStore.Kind.PRIVATE_KEY)); assertEquals(6, count(mappings, KeyringStore.Kind.SECRET_KEY)); @@ -72,49 +70,39 @@ class KeyringAlgorithmCoverageTest { @Test void publicApiRequiresUnlockMaterialAndExposesNoPlaintextStorePath() { List publicMethods = Arrays.stream(KeyringStore.class.getDeclaredMethods()) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .toList(); - publicMethods.stream() - .filter(method -> "create".equals(method.getName()) - || "open".equals(method.getName())) + .filter(method -> Modifier.isPublic(method.getModifiers())).toList(); + publicMethods.stream().filter(method -> "create".equals(method.getName()) || "open".equals(method.getName())) .forEach(method -> { - assertTrue(Arrays.asList(method.getParameterTypes()) - .contains(KeyringPassword.class)); - assertTrue(Arrays.stream(method.getParameterTypes()) - .noneMatch(String.class::equals)); + assertTrue(Arrays.asList(method.getParameterTypes()).contains(KeyringPassword.class)); + assertTrue(Arrays.stream(method.getParameterTypes()).noneMatch(String.class::equals)); }); assertTrue(publicMethods.stream().noneMatch(method -> switch (method.getName()) { case "exportText", "importText", "save", "load" -> true; default -> false; })); - assertTrue(Arrays.stream(KeyringStore.class.getFields()) - .noneMatch(field -> field.getType() == byte[].class)); + assertTrue(Arrays.stream(KeyringStore.class.getFields()).noneMatch(field -> field.getType() == byte[].class)); } @TestFactory Stream asymmetricEncryptedStoreRoundTrips() { List algorithms = KeyringImportRegistry.mappings().stream() .filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY) - .map(KeyringImportRegistry.PersistentMapping::algorithmId) - .toList(); - return algorithms.stream().map(algorithmId -> DynamicTest.dynamicTest( - algorithmId + " PUBLIC/X.509 + PRIVATE/PKCS#8", - () -> roundTripAsymmetric(algorithmId))); + .map(KeyringImportRegistry.PersistentMapping::algorithmId).toList(); + return algorithms.stream().map(algorithmId -> DynamicTest + .dynamicTest(algorithmId + " PUBLIC/X.509 + PRIVATE/PKCS#8", () -> roundTripAsymmetric(algorithmId))); } @TestFactory Stream secretEncryptedStoreRoundTrips() { return KeyringImportRegistry.mappings().stream() .filter(mapping -> mapping.kind() == KeyringStore.Kind.SECRET_KEY) - .map(mapping -> DynamicTest.dynamicTest(secretDisplayName(mapping), - () -> roundTripSecret(mapping))); + .map(mapping -> DynamicTest.dynamicTest(secretDisplayName(mapping), () -> roundTripSecret(mapping))); } private void roundTripAsymmetric(String algorithmId) throws Exception { KeyPair original = generatePair(algorithmId); Path path = temporaryDirectory.resolve("asymmetric-" + safeName(algorithmId) + ".zek"); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) { store.putPublic("matrix", algorithmId, original.getPublic()); store.putPrivate("matrix", algorithmId, original.getPrivate()); } @@ -123,8 +111,7 @@ class KeyringAlgorithmCoverageTest { PrivateKey reconstructedPrivate = null; byte[] originalPublic = null; byte[] reconstructedPublicBytes = null; - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { KeyringStore.PublicWithId publicWithId = store.getPublicWithId("matrix"); KeyringStore.PrivateWithId privateWithId = store.getPrivateWithId("matrix"); assertEquals(algorithmId, publicWithId.algorithm()); @@ -134,8 +121,7 @@ class KeyringAlgorithmCoverageTest { originalPublic = original.getPublic().getEncoded(); reconstructedPublicBytes = reconstructedPublic.getEncoded(); assertArrayEquals(originalPublic, reconstructedPublicBytes); - proveAsymmetricOperation(algorithmId, reconstructedPublic, - reconstructedPrivate); + proveAsymmetricOperation(algorithmId, reconstructedPublic, reconstructedPrivate); } finally { wipe(originalPublic); wipe(reconstructedPublicBytes); @@ -146,23 +132,19 @@ class KeyringAlgorithmCoverageTest { } } - private void roundTripSecret(KeyringImportRegistry.PersistentMapping mapping) - throws Exception { + private void roundTripSecret(KeyringImportRegistry.PersistentMapping mapping) throws Exception { byte[] material = new byte[32]; Arrays.fill(material, secretFill(mapping)); String jcaName = secretJcaName(mapping); SecretKey original = new SecretKeySpec(material, jcaName); - Path path = temporaryDirectory.resolve("secret-" + safeName(secretDisplayName(mapping)) - + ".zek"); + Path path = temporaryDirectory.resolve("secret-" + safeName(secretDisplayName(mapping)) + ".zek"); SecretKey reconstructed = null; byte[] reconstructedBytes = null; try { - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) { store.putSecret("matrix", mapping.algorithmId(), original); } - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { KeyringStore.SecretWithId withId = store.getSecretWithId("matrix"); assertEquals(mapping.algorithmId(), withId.algorithm()); reconstructed = withId.key(); @@ -179,11 +161,10 @@ class KeyringAlgorithmCoverageTest { } } - private static void proveAsymmetricOperation(String algorithmId, - PublicKey publicKey, PrivateKey privateKey) throws Exception { + private static void proveAsymmetricOperation(String algorithmId, PublicKey publicKey, PrivateKey privateKey) + throws Exception { CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId); - if (algorithm.roles().contains(KeyUsage.SIGN) - && algorithm.roles().contains(KeyUsage.VERIFY)) { + if (algorithm.roles().contains(KeyUsage.SIGN) && algorithm.roles().contains(KeyUsage.VERIFY)) { proveSignature(algorithmId, publicKey, privateKey); } else if (algorithm.roles().contains(KeyUsage.ENCAPSULATE) && algorithm.roles().contains(KeyUsage.DECAPSULATE)) { @@ -191,21 +172,18 @@ class KeyringAlgorithmCoverageTest { } else if (algorithm.roles().contains(KeyUsage.AGREEMENT)) { proveAgreement(algorithmId, publicKey, privateKey); } else { - assertTrue(algorithm.roles().contains(KeyUsage.ENCRYPT) - && algorithm.roles().contains(KeyUsage.DECRYPT)); + assertTrue(algorithm.roles().contains(KeyUsage.ENCRYPT) && algorithm.roles().contains(KeyUsage.DECRYPT)); proveEncryption(algorithmId, publicKey, privateKey); } } - private static void proveSignature(String algorithmId, PublicKey publicKey, - PrivateKey privateKey) throws Exception { + private static void proveSignature(String algorithmId, PublicKey publicKey, PrivateKey privateKey) + throws Exception { ZeroEchoSession session = new ZeroEchoSession(); AtomicReference signatureHolder = new AtomicReference<>(); byte[] signature = null; - try (SignatureContext signer = session.createContext( - algorithmId, KeyUsage.SIGN, privateKey); - InputStream signed = new TailStrippingInputStream( - signer.wrap(new ByteArrayInputStream(MESSAGE)), + try (SignatureContext signer = session.createContext(algorithmId, KeyUsage.SIGN, privateKey); + InputStream signed = new TailStrippingInputStream(signer.wrap(new ByteArrayInputStream(MESSAGE)), signer.tagLength(), 512) { @Override protected void processTail(byte[] tail) { @@ -217,8 +195,7 @@ class KeyringAlgorithmCoverageTest { } try { assertTrue(signature != null && signature.length > 0); - try (SignatureContext verifier = session.createContext( - algorithmId, KeyUsage.VERIFY, publicKey); + try (SignatureContext verifier = session.createContext(algorithmId, KeyUsage.VERIFY, publicKey); InputStream verified = verificationStream(verifier, signature)) { assertArrayEquals(MESSAGE, verified.readAllBytes()); } @@ -227,23 +204,19 @@ class KeyringAlgorithmCoverageTest { } } - private static InputStream verificationStream(SignatureContext verifier, - byte[] signature) throws IOException { + private static InputStream verificationStream(SignatureContext verifier, byte[] signature) throws IOException { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(signature); return verifier.wrap(new ByteArrayInputStream(MESSAGE)); } - private static void proveKem(String algorithmId, PublicKey publicKey, - PrivateKey privateKey) throws Exception { + private static void proveKem(String algorithmId, PublicKey publicKey, PrivateKey privateKey) throws Exception { ZeroEchoSession session = new ZeroEchoSession(); byte[] encapsulated = null; byte[] senderSecret = null; byte[] recipientSecret = null; - try (KemContext sender = session.createContext( - algorithmId, KeyUsage.ENCAPSULATE, publicKey); - KemContext recipient = session.createContext( - algorithmId, KeyUsage.DECAPSULATE, privateKey)) { + try (KemContext sender = session.createContext(algorithmId, KeyUsage.ENCAPSULATE, publicKey); + KemContext recipient = session.createContext(algorithmId, KeyUsage.DECAPSULATE, privateKey)) { KemContext.KemResult result = sender.encapsulate(); encapsulated = result.ciphertext(); senderSecret = result.sharedSecret(); @@ -256,16 +229,14 @@ class KeyringAlgorithmCoverageTest { } } - private static void proveAgreement(String algorithmId, PublicKey publicKey, - PrivateKey privateKey) throws Exception { + private static void proveAgreement(String algorithmId, PublicKey publicKey, PrivateKey privateKey) + throws Exception { KeyPair peer = generatePair(algorithmId); ZeroEchoSession session = new ZeroEchoSession(); byte[] firstSecret = null; byte[] secondSecret = null; - try (AgreementContext first = session.createContext( - algorithmId, KeyUsage.AGREEMENT, privateKey); - AgreementContext second = session.createContext( - algorithmId, KeyUsage.AGREEMENT, peer.getPrivate())) { + try (AgreementContext first = session.createContext(algorithmId, KeyUsage.AGREEMENT, privateKey); + AgreementContext second = session.createContext(algorithmId, KeyUsage.AGREEMENT, peer.getPrivate())) { first.setPeerPublic(peer.getPublic()); second.setPeerPublic(publicKey); firstSecret = first.deriveSecret(); @@ -279,32 +250,26 @@ class KeyringAlgorithmCoverageTest { } } - private static void proveEncryption(String algorithmId, Key encryptionKey, - Key decryptionKey) throws Exception { + private static void proveEncryption(String algorithmId, Key encryptionKey, Key decryptionKey) throws Exception { ZeroEchoSession session = new ZeroEchoSession(); - conflux.CtxInterface operationContext = - conflux.Ctx.INSTANCE.getContext("keyring-matrix-" + algorithmId); + conflux.CtxInterface operationContext = conflux.Ctx.INSTANCE.getContext("keyring-matrix-" + algorithmId); byte[] ciphertext = null; byte[] plaintext = null; try { - try (EncryptionContext encryption = session.createContext( - algorithmId, KeyUsage.ENCRYPT, encryptionKey)) { + try (EncryptionContext encryption = session.createContext(algorithmId, KeyUsage.ENCRYPT, encryptionKey)) { if (encryption instanceof zeroecho.core.spi.ContextAware contextAware) { contextAware.setContext(operationContext); } - try (InputStream encrypted = encryption.attach( - new ByteArrayInputStream(MESSAGE))) { + try (InputStream encrypted = encryption.attach(new ByteArrayInputStream(MESSAGE))) { ciphertext = encrypted.readAllBytes(); } } - try (EncryptionContext decryption = session.createContext( - algorithmId, KeyUsage.DECRYPT, decryptionKey)) { + try (EncryptionContext decryption = session.createContext(algorithmId, KeyUsage.DECRYPT, decryptionKey)) { if (decryption instanceof zeroecho.core.spi.ContextAware contextAware) { contextAware.setContext(operationContext); } - try (InputStream decrypted = decryption.attach( - new ByteArrayInputStream(ciphertext))) { - plaintext = decrypted.readAllBytes(); + try (InputStream decrypted = decryption.attach(new ByteArrayInputStream(ciphertext))) { + plaintext = decrypted.readAllBytes(); } } assertArrayEquals(MESSAGE, plaintext); @@ -314,8 +279,7 @@ class KeyringAlgorithmCoverageTest { } } - private static void proveSecretOperation( - KeyringImportRegistry.PersistentMapping mapping, SecretKey key) + private static void proveSecretOperation(KeyringImportRegistry.PersistentMapping mapping, SecretKey key) throws Exception { if ("HMAC".equals(mapping.algorithmId())) { proveMac(key); @@ -345,30 +309,22 @@ class KeyringAlgorithmCoverageTest { private static KeyPair generatePair(String algorithmId) throws Exception { CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId); KeyOperationInfo generation = algorithm.keyOperations().stream() - .filter(info -> info.operation() - == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE) - .filter(info -> info.defaultSpec() != null) - .findFirst() - .orElseThrow(() -> new AssertionError( - "No default key-pair generation mapping for " + algorithmId)); - AsymmetricKeyPairGenerator generator = - algorithm.asymmetricKeyPairGenerator(generation.specType()); + .filter(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE) + .filter(info -> info.defaultSpec() != null).findFirst() + .orElseThrow(() -> new AssertionError("No default key-pair generation mapping for " + algorithmId)); + AsymmetricKeyPairGenerator generator = algorithm.asymmetricKeyPairGenerator(generation.specType()); return generator.generateKeyPair((AlgorithmKeySpec) generation.defaultSpec()); } - private static long count(List mappings, - KeyringStore.Kind kind) { + private static long count(List mappings, KeyringStore.Kind kind) { return mappings.stream().filter(mapping -> mapping.kind() == kind).count(); } - private static String secretDisplayName( - KeyringImportRegistry.PersistentMapping mapping) { - return mapping.algorithmId() + "/" + mapping.hmacVariant().name() - + " SECRET/RAW"; + private static String secretDisplayName(KeyringImportRegistry.PersistentMapping mapping) { + return mapping.algorithmId() + "/" + mapping.hmacVariant().name() + " SECRET/RAW"; } - private static String secretJcaName( - KeyringImportRegistry.PersistentMapping mapping) { + private static String secretJcaName(KeyringImportRegistry.PersistentMapping mapping) { return switch (mapping.algorithmId()) { case "AES" -> "AES"; case "CHACHA20", "CHACHA20-POLY1305" -> "ChaCha20"; @@ -377,8 +333,7 @@ class KeyringAlgorithmCoverageTest { }; } - private static byte secretFill( - KeyringImportRegistry.PersistentMapping mapping) { + private static byte secretFill(KeyringImportRegistry.PersistentMapping mapping) { return (byte) (mapping.hmacVariant().code() + mapping.algorithmId().length() + 1); } diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringAtomicPersistenceTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringAtomicPersistenceTest.java index affbdde..e01fdd6 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringAtomicPersistenceTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringAtomicPersistenceTest.java @@ -32,8 +32,8 @@ class KeyringAtomicPersistenceTest { private static final char[] PASSWORD = { 'a', 't', 'o', 'm', 'i', 'c' }; private static final byte[] ENTRY_A = material((byte) 0x31); private static final byte[] ENTRY_B = material((byte) 0x72); - private static final Set FILE_PERMISSIONS = Set.of( - PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); + private static final Set FILE_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE); @TempDir Path temporaryDirectory; @@ -41,20 +41,16 @@ class KeyringAtomicPersistenceTest { @Test void mainImagePreCommitFailuresPreserveAuthoritativeState() throws Exception { start("mainImagePreCommitFailuresPreserveAuthoritativeState"); - for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, - FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP, + for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP, FailureStage.ATOMIC_MOVE)) { Path path = initialized("main-" + stage + ".zek"); byte[] before = Files.readAllBytes(path); - FailingFileOperations operations = - new FailingFileOperations(Target.MAIN_IMAGE, stage, false); + FailingFileOperations operations = new FailingFileOperations(Target.MAIN_IMAGE, stage, false); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password, - KeyringProtection.standard(), deterministicRandom(50), - operations)) { + KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(), + deterministicRandom(50), operations)) { long highWater = longField(store, "nonceHighWater"); - KeyringException failure = assertThrows(KeyringException.class, - () -> put(store, "B", ENTRY_B)); + KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B)); assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED); assertTrue(store.contains("A")); assertFalse(store.contains("B")); @@ -76,16 +72,15 @@ class KeyringAtomicPersistenceTest { void mainImageDirectoryForceFailurePoisonsUntilReopen() throws Exception { start("mainImageDirectoryForceFailurePoisonsUntilReopen"); Path path = initialized("main-directory.zek"); - FailingFileOperations operations = new FailingFileOperations( - Target.MAIN_IMAGE, FailureStage.FORCE_DIRECTORY, false); + FailingFileOperations operations = new FailingFileOperations(Target.MAIN_IMAGE, FailureStage.FORCE_DIRECTORY, + false); KeyringStore store; try (KeyringPassword password = password()) { - store = KeyringStore.open(path, password, KeyringProtection.standard(), - deterministicRandom(70), operations); + store = KeyringStore.open(path, password, KeyringProtection.standard(), deterministicRandom(70), + operations); } try { - KeyringException failure = assertThrows(KeyringException.class, - () -> put(store, "B", ENTRY_B)); + KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B)); assertSafe(failure, KeyringException.Code.KEYRING_DURABILITY_UNCONFIRMED); assertTrue(store.isDestroyed()); assertThrows(IllegalStateException.class, () -> store.contains("A")); @@ -101,21 +96,17 @@ class KeyringAtomicPersistenceTest { @Test void sidecarPreCommitFailuresIssueNoUncommittedNonce() throws Exception { start("sidecarPreCommitFailuresIssueNoUncommittedNonce"); - for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, - FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP, + for (FailureStage stage : List.of(FailureStage.CREATE_TEMP, FailureStage.WRITE_TEMP, FailureStage.FORCE_TEMP, FailureStage.ATOMIC_MOVE)) { Path path = initialized("sidecar-" + stage + ".zek"); byte[] mainBefore = Files.readAllBytes(path); byte[] sidecarBefore = Files.readAllBytes(sidecar(path)); - FailingFileOperations operations = - new FailingFileOperations(Target.NONCE_RESERVATION, stage, false); + FailingFileOperations operations = new FailingFileOperations(Target.NONCE_RESERVATION, stage, false); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password, - KeyringProtection.standard(), deterministicRandom(90), - operations)) { + KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(), + deterministicRandom(90), operations)) { long highWater = longField(store, "nonceHighWater"); - KeyringException failure = assertThrows(KeyringException.class, - () -> put(store, "B", ENTRY_B)); + KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B)); assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED); assertEquals(highWater, longField(store, "nonceHighWater")); assertFalse(store.contains("B")); @@ -138,17 +129,16 @@ class KeyringAtomicPersistenceTest { start("sidecarDirectoryForceFailureIssuesNothingUntilReopen"); Path path = initialized("sidecar-directory.zek"); long initialHighWater; - FailingFileOperations operations = new FailingFileOperations( - Target.NONCE_RESERVATION, FailureStage.FORCE_DIRECTORY, false); + FailingFileOperations operations = new FailingFileOperations(Target.NONCE_RESERVATION, + FailureStage.FORCE_DIRECTORY, false); KeyringStore store; try (KeyringPassword password = password()) { - store = KeyringStore.open(path, password, KeyringProtection.standard(), - deterministicRandom(110), operations); + store = KeyringStore.open(path, password, KeyringProtection.standard(), deterministicRandom(110), + operations); } try { initialHighWater = longField(store, "nonceHighWater"); - KeyringException failure = assertThrows(KeyringException.class, - () -> put(store, "B", ENTRY_B)); + KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B)); assertSafe(failure, KeyringException.Code.KEYRING_DURABILITY_UNCONFIRMED); assertFalse(store.isDestroyed()); assertThrows(IllegalStateException.class, () -> store.contains("A")); @@ -157,8 +147,8 @@ class KeyringAtomicPersistenceTest { store.close(); } try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password, - KeyringProtection.standard(), deterministicRandom(130))) { + KeyringStore reopened = KeyringStore.open(path, password, KeyringProtection.standard(), + deterministicRandom(130))) { assertTrue(reopened.contains("A")); assertFalse(reopened.contains("B")); assertEquals(initialHighWater + 1, longField(reopened, "nonceHighWater")); @@ -179,18 +169,14 @@ class KeyringAtomicPersistenceTest { private void assertCleanupFailure(Target target, String file) throws Exception { Path path = initialized(file); - FailingFileOperations operations = new FailingFileOperations( - target, FailureStage.WRITE_TEMP, true); + FailingFileOperations operations = new FailingFileOperations(target, FailureStage.WRITE_TEMP, true); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password, - KeyringProtection.standard(), deterministicRandom(150), - operations)) { - KeyringException failure = assertThrows(KeyringException.class, - () -> put(store, "B", ENTRY_B)); + KeyringStore store = KeyringStore.open(path, password, KeyringProtection.standard(), + deterministicRandom(150), operations)) { + KeyringException failure = assertThrows(KeyringException.class, () -> put(store, "B", ENTRY_B)); assertSafe(failure, KeyringException.Code.KEYRING_IO_FAILED); assertEquals(1, failure.getSuppressed().length); - assertSafe((KeyringException) failure.getSuppressed()[0], - KeyringException.Code.KEYRING_IO_FAILED); + assertSafe((KeyringException) failure.getSuppressed()[0], KeyringException.Code.KEYRING_IO_FAILED); assertTrue(store.contains("A")); assertFalse(store.contains("B")); Path residual = operations.firstTemporary(); @@ -207,21 +193,19 @@ class KeyringAtomicPersistenceTest { private Path initialized(String file) throws Exception { Path path = temporaryDirectory.resolve(file); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(1))) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(1))) { put(store, "A", ENTRY_A); } return path; } - private static void put(KeyringStore store, String alias, byte[] material) - throws Exception { + private static void put(KeyringStore store, String alias, byte[] material) throws Exception { store.putSecret(alias, "AES", new SecretKeySpec(material, "AES")); } private static void assertReopened(Path path, boolean hasB) throws Exception { - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { assertTrue(store.contains("A")); assertEquals(hasB, store.contains("B")); assertSecretEquals(ENTRY_A, store.getSecret("A")); @@ -242,21 +226,18 @@ class KeyringAtomicPersistenceTest { private static void assertAlreadyOpen(Path path) throws Exception { try (KeyringPassword password = password()) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertSafe(failure, KeyringException.Code.KEYRING_ALREADY_OPEN); } } - private static void assertSafe(KeyringException failure, - KeyringException.Code expected) { + private static void assertSafe(KeyringException failure, KeyringException.Code expected) { assertEquals(expected, failure.code()); assertEquals(expected.name(), failure.getMessage()); assertNull(failure.getCause()); } - private static void assertSentinelAbsent(Path directory, byte[] sentinel) - throws IOException { + private static void assertSentinelAbsent(Path directory, byte[] sentinel) throws IOException { try (java.util.stream.Stream paths = Files.list(directory)) { for (Path current : paths.toList()) { if (Files.isRegularFile(current)) { @@ -272,8 +253,7 @@ class KeyringAtomicPersistenceTest { } private static int indexOf(byte[] haystack, byte[] needle) { - outer: - for (int index = 0; index <= haystack.length - needle.length; index++) { + outer: for (int index = 0; index <= haystack.length - needle.length; index++) { for (int offset = 0; offset < needle.length; offset++) { if (haystack[index + offset] != needle[offset]) { continue outer; @@ -329,12 +309,7 @@ class KeyringAtomicPersistenceTest { } private enum FailureStage { - CREATE_TEMP, - WRITE_TEMP, - FORCE_TEMP, - ATOMIC_MOVE, - FORCE_DIRECTORY, - DELETE_TEMP + CREATE_TEMP, WRITE_TEMP, FORCE_TEMP, ATOMIC_MOVE, FORCE_DIRECTORY, DELETE_TEMP } private static final class FailingFileOperations implements KeyringFileOperations { @@ -345,20 +320,17 @@ class KeyringAtomicPersistenceTest { private boolean primaryFailed; private boolean cleanupFailed; - private FailingFileOperations(Target target, FailureStage primaryStage, - boolean failCleanup) { + private FailingFileOperations(Target target, FailureStage primaryStage, boolean failCleanup) { this.target = target; this.primaryStage = primaryStage; this.failCleanup = failCleanup; } @Override - public Path createTemporary(Target actualTarget, Path parent, String prefix, - String suffix, FileAttribute> permissions) - throws IOException { + public Path createTemporary(Target actualTarget, Path parent, String prefix, String suffix, + FileAttribute> permissions) throws IOException { failBefore(actualTarget, FailureStage.CREATE_TEMP); - Path temporary = NIO.createTemporary(actualTarget, parent, prefix, suffix, - permissions); + Path temporary = NIO.createTemporary(actualTarget, parent, prefix, suffix, permissions); if (actualTarget == target) { temporaryPaths.add(temporary); } @@ -366,22 +338,19 @@ class KeyringAtomicPersistenceTest { } @Override - public void writeTemporary(Target actualTarget, Path temporary, byte[] image) - throws IOException { + public void writeTemporary(Target actualTarget, Path temporary, byte[] image) throws IOException { NIO.writeTemporary(actualTarget, temporary, image); failAfter(actualTarget, FailureStage.WRITE_TEMP); } @Override - public void forceTemporary(Target actualTarget, Path temporary) - throws IOException { + public void forceTemporary(Target actualTarget, Path temporary) throws IOException { NIO.forceTemporary(actualTarget, temporary); failAfter(actualTarget, FailureStage.FORCE_TEMP); } @Override - public void atomicReplace(Target actualTarget, Path temporary, Path destination) - throws IOException { + public void atomicReplace(Target actualTarget, Path temporary, Path destination) throws IOException { failBefore(actualTarget, FailureStage.ATOMIC_MOVE); NIO.atomicReplace(actualTarget, temporary, destination); } @@ -393,8 +362,7 @@ class KeyringAtomicPersistenceTest { } @Override - public void deleteTemporary(Target actualTarget, Path temporary) - throws IOException { + public void deleteTemporary(Target actualTarget, Path temporary) throws IOException { if (actualTarget == target && failCleanup && !cleanupFailed) { cleanupFailed = true; throw new IOException("DELETE_TEMP_SENTINEL"); @@ -402,16 +370,14 @@ class KeyringAtomicPersistenceTest { NIO.deleteTemporary(actualTarget, temporary); } - private void failBefore(Target actualTarget, FailureStage stage) - throws IOException { + private void failBefore(Target actualTarget, FailureStage stage) throws IOException { if (actualTarget == target && primaryStage == stage && !primaryFailed) { primaryFailed = true; throw new IOException(stage.name() + "_SENTINEL"); } } - private void failAfter(Target actualTarget, FailureStage stage) - throws IOException { + private void failAfter(Target actualTarget, FailureStage stage) throws IOException { failBefore(actualTarget, stage); } diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringCryptographicFormatTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringCryptographicFormatTest.java index c2c786c..10b1877 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringCryptographicFormatTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringCryptographicFormatTest.java @@ -56,62 +56,57 @@ class KeyringCryptographicFormatTest { Path source = createEmpty("master-source.zek", 1); byte[] image = Files.readAllBytes(source); try { - assertUniformUnlockFailure(source, - new char[] { 'w', 'r', 'o', 'n', 'g' }); + assertUniformUnlockFailure(source, new char[] { 'w', 'r', 'o', 'n', 'g' }); - assertUniformUnlockFailure(copyWithMutation(source, image, "wrapped-cipher", - value -> value[WRAPPED_OFFSET] ^= 1), PASSWORD); - assertUniformUnlockFailure(copyWithMutation(source, image, "wrapped-tag", - value -> value[WRAPPED_OFFSET + 47] ^= 1), PASSWORD); - assertUniformUnlockFailure(copyWithMutation(source, image, "salt", - value -> value[SALT_OFFSET] ^= 1), PASSWORD); + assertUniformUnlockFailure( + copyWithMutation(source, image, "wrapped-cipher", value -> value[WRAPPED_OFFSET] ^= 1), PASSWORD); + assertUniformUnlockFailure( + copyWithMutation(source, image, "wrapped-tag", value -> value[WRAPPED_OFFSET + 47] ^= 1), PASSWORD); + assertUniformUnlockFailure(copyWithMutation(source, image, "salt", value -> value[SALT_OFFSET] ^= 1), + PASSWORD); assertUniformUnlockFailure(copyWithMutation(source, image, "iterations-auth", value -> putInt(value, ITERATIONS_OFFSET, 600_001)), PASSWORD); - assertUniformUnlockFailure(copyWithMutation(source, image, "wrap-nonce-auth", - value -> value[WRAP_NONCE_OFFSET + 1] ^= 1), PASSWORD); + assertUniformUnlockFailure( + copyWithMutation(source, image, "wrap-nonce-auth", value -> value[WRAP_NONCE_OFFSET + 1] ^= 1), + PASSWORD); - assertRejected(copyWithMutation(source, image, "iterations-low", - value -> putInt(value, ITERATIONS_OFFSET, 599_999)), + assertRejected( + copyWithMutation(source, image, "iterations-low", + value -> putInt(value, ITERATIONS_OFFSET, 599_999)), KeyringException.Code.KEYRING_LIMIT_EXCEEDED); - assertRejected(copyWithMutation(source, image, "iterations-operational", - value -> putInt(value, ITERATIONS_OFFSET, 1_000_001)), + assertRejected( + copyWithMutation(source, image, "iterations-operational", + value -> putInt(value, ITERATIONS_OFFSET, 1_000_001)), KeyringException.Code.KEYRING_LIMIT_EXCEEDED); - assertRejected(copyWithMutation(source, image, "iterations-absolute", - value -> putInt(value, ITERATIONS_OFFSET, 10_000_001)), + assertRejected( + copyWithMutation(source, image, "iterations-absolute", + value -> putInt(value, ITERATIONS_OFFSET, 10_000_001)), KeyringException.Code.KEYRING_LIMIT_EXCEEDED); - assertRejected(copyWithMutation(source, image, "unknown-kdf", - value -> value[KDF_OFFSET] = 99), + assertRejected(copyWithMutation(source, image, "unknown-kdf", value -> value[KDF_OFFSET] = 99), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "unknown-aead", - value -> value[AEAD_OFFSET] = 99), + assertRejected(copyWithMutation(source, image, "unknown-aead", value -> value[AEAD_OFFSET] = 99), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "kek-length", - value -> putInt(value, KEK_LENGTH_OFFSET, 31)), + assertRejected(copyWithMutation(source, image, "kek-length", value -> putInt(value, KEK_LENGTH_OFFSET, 31)), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "wrap-domain", - value -> value[WRAP_NONCE_OFFSET] = 2), + assertRejected(copyWithMutation(source, image, "wrap-domain", value -> value[WRAP_NONCE_OFFSET] = 2), KeyringException.Code.KEYRING_FORMAT_INVALID); for (int wrappedLength : new int[] { -1, 47, 49 }) { - assertRejected(copyWithMutation(source, image, - "wrapped-length-" + wrappedLength, - value -> putInt(value, WRAPPED_LENGTH_OFFSET, wrappedLength)), - wrappedLength == 47 - ? KeyringException.Code.KEYRING_FORMAT_INVALID + assertRejected( + copyWithMutation(source, image, "wrapped-length-" + wrappedLength, + value -> putInt(value, WRAPPED_LENGTH_OFFSET, wrappedLength)), + wrappedLength == 47 ? KeyringException.Code.KEYRING_FORMAT_INVALID : KeyringException.Code.KEYRING_LIMIT_EXCEEDED); } - int[] boundaries = { 1, VERSION_OFFSET, STORE_ID_OFFSET, KDF_OFFSET, - ITERATIONS_OFFSET, SALT_OFFSET, KEK_LENGTH_OFFSET, AEAD_OFFSET, - WRAP_NONCE_OFFSET, WRAPPED_LENGTH_OFFSET, WRAPPED_OFFSET, + int[] boundaries = { 1, VERSION_OFFSET, STORE_ID_OFFSET, KDF_OFFSET, ITERATIONS_OFFSET, SALT_OFFSET, + KEK_LENGTH_OFFSET, AEAD_OFFSET, WRAP_NONCE_OFFSET, WRAPPED_LENGTH_OFFSET, WRAPPED_OFFSET, WRAPPED_OFFSET + 47, ENTRY_COUNT_OFFSET }; for (int boundary : boundaries) { - Path truncated = copyImage(source, Arrays.copyOf(image, boundary), - "truncated-" + boundary); + Path truncated = copyImage(source, Arrays.copyOf(image, boundary), "truncated-" + boundary); assertSafeRejected(truncated); } byte[] trailing = Arrays.copyOf(image, image.length + 1); - assertRejected(copyImage(source, trailing, "trailing"), - KeyringException.Code.KEYRING_FORMAT_INVALID); + assertRejected(copyImage(source, trailing, "trailing"), KeyringException.Code.KEYRING_FORMAT_INVALID); } finally { wipe(image); } @@ -125,44 +120,47 @@ class KeyringCryptographicFormatTest { byte[] image = Files.readAllBytes(source); ImageLayout layout = layout(image); try { - assertRejected(copyWithMutation(source, image, "entry-cipher", - value -> value[layout.entries.get(0).ciphertextOffset] ^= 1), + assertRejected( + copyWithMutation(source, image, "entry-cipher", + value -> value[layout.entries.get(0).ciphertextOffset] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "entry-tag", - value -> value[layout.entries.get(0).endOffset - 1] ^= 1), + assertRejected( + copyWithMutation(source, image, "entry-tag", + value -> value[layout.entries.get(0).endOffset - 1] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "entry-nonce", - value -> value[layout.entries.get(0).nonceOffset] ^= 1), + assertRejected( + copyWithMutation(source, image, "entry-nonce", + value -> value[layout.entries.get(0).nonceOffset] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "entry-id", - value -> value[layout.entries.get(0).entryIdOffset] ^= 1), + assertRejected( + copyWithMutation(source, image, "entry-id", + value -> value[layout.entries.get(0).entryIdOffset] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "manifest-nonce", - value -> value[layout.manifestNonceOffset] ^= 1), + assertRejected( + copyWithMutation(source, image, "manifest-nonce", value -> value[layout.manifestNonceOffset] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "manifest-cipher", - value -> value[layout.manifestCipherOffset] ^= 1), + assertRejected( + copyWithMutation(source, image, "manifest-cipher", + value -> value[layout.manifestCipherOffset] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "manifest-tag", - value -> value[image.length - 1] ^= 1), + assertRejected(copyWithMutation(source, image, "manifest-tag", value -> value[image.length - 1] ^= 1), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyImage(source, reorderEntries(image, layout), - "entry-reorder"), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyImage(source, duplicateFirstEntry(image, layout), - "entry-duplicate"), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyImage(source, deleteFirstEntry(image, layout), - "entry-delete"), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyImage(source, insertUnauthenticatedEntry(image, layout), - "entry-insert"), KeyringException.Code.KEYRING_FORMAT_INVALID); + assertRejected(copyImage(source, reorderEntries(image, layout), "entry-reorder"), + KeyringException.Code.KEYRING_FORMAT_INVALID); + assertRejected(copyImage(source, duplicateFirstEntry(image, layout), "entry-duplicate"), + KeyringException.Code.KEYRING_FORMAT_INVALID); + assertRejected(copyImage(source, deleteFirstEntry(image, layout), "entry-delete"), + KeyringException.Code.KEYRING_FORMAT_INVALID); + assertRejected(copyImage(source, insertUnauthenticatedEntry(image, layout), "entry-insert"), + KeyringException.Code.KEYRING_FORMAT_INVALID); Path other = createTwoEntries("outer-other.zek", 107); byte[] otherImage = Files.readAllBytes(other); try { ImageLayout otherLayout = layout(otherImage); byte[] copied = replaceEntry(otherImage, otherLayout.entries.get(0), - slice(image, layout.entries.get(0).entryIdOffset, - layout.entries.get(0).endOffset)); + slice(image, layout.entries.get(0).entryIdOffset, layout.entries.get(0).endOffset)); assertRejected(copyImage(other, copied, "entry-cross-store"), KeyringException.Code.KEYRING_FORMAT_INVALID); wipe(copied); @@ -192,19 +190,15 @@ class KeyringCryptographicFormatTest { value -> value[descriptor(value, 0).nonceOffset] ^= 1); assertManifestMutationRejected(fixture.path, image, masterKey, "cipher-length", value -> putInt(value, descriptor(value, 0).ciphertextLengthOffset, - getInt(value, - descriptor(value, 0).ciphertextLengthOffset) + 1)); + getInt(value, descriptor(value, 0).ciphertextLengthOffset) + 1)); assertManifestMutationRejected(fixture.path, image, masterKey, "cipher-digest", value -> value[descriptor(value, 0).digestOffset] ^= 1); - assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-id", - value -> copyField(value, descriptor(value, 0).entryIdOffset, - descriptor(value, 1).entryIdOffset, KeyringStore.UUID_BYTES)); - assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-nonce", - value -> copyField(value, descriptor(value, 0).nonceOffset, - descriptor(value, 1).nonceOffset, KeyringStore.NONCE_BYTES)); + assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-id", value -> copyField(value, + descriptor(value, 0).entryIdOffset, descriptor(value, 1).entryIdOffset, KeyringStore.UUID_BYTES)); + assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-nonce", value -> copyField(value, + descriptor(value, 0).nonceOffset, descriptor(value, 1).nonceOffset, KeyringStore.NONCE_BYTES)); assertManifestMutationRejected(fixture.path, image, masterKey, "duplicate-alias", - value -> copyField(value, descriptor(value, 0).aliasOffset, - descriptor(value, 1).aliasOffset, + value -> copyField(value, descriptor(value, 0).aliasOffset, descriptor(value, 1).aliasOffset, descriptor(value, 0).aliasLength)); assertManifestMutationRejected(fixture.path, image, masterKey, "unknown-algorithm", value -> overwriteAscii(value, descriptor(value, 0).algorithmOffset, "BAD"), @@ -222,33 +216,29 @@ class KeyringCryptographicFormatTest { value -> putInt(value, 16, 1)); assertManifestMutationRejected(fixture.path, image, masterKey, "manifest-reorder", KeyringCryptographicFormatTest::swapManifestDescriptors); - assertManifestReplacementRejected(fixture.path, image, masterKey, - "manifest-truncated", value -> Arrays.copyOf(value, value.length - 1)); - assertManifestReplacementRejected(fixture.path, image, masterKey, - "manifest-trailing", value -> Arrays.copyOf(value, value.length + 1)); + assertManifestReplacementRejected(fixture.path, image, masterKey, "manifest-truncated", + value -> Arrays.copyOf(value, value.length - 1)); + assertManifestReplacementRejected(fixture.path, image, masterKey, "manifest-trailing", + value -> Arrays.copyOf(value, value.length + 1)); - assertEntryMutationRejected(fixture.path, image, masterKey, "entry-cipher-auth", - value -> value[0] ^= 1, false); + assertEntryMutationRejected(fixture.path, image, masterKey, "entry-cipher-auth", value -> value[0] ^= 1, + false); assertEntryMutationRejected(fixture.path, image, masterKey, "entry-tag-auth", value -> value[value.length - 1] ^= 1, false); - assertEntryMutationRejected(fixture.path, image, masterKey, "entry-version", - value -> putInt(value, 0, 2), true); + assertEntryMutationRejected(fixture.path, image, masterKey, "entry-version", value -> putInt(value, 0, 2), + true); assertEntryMutationRejected(fixture.path, image, masterKey, "alias-substitution", value -> overwriteAscii(value, Integer.BYTES * 2, "xxx"), true); - assertEntryMutationRejected(fixture.path, image, masterKey, - "algorithm-substitution", + assertEntryMutationRejected(fixture.path, image, masterKey, "algorithm-substitution", value -> overwriteAscii(value, Integer.BYTES * 3 + 3, "BAD"), true); assertEntryMutationRejected(fixture.path, image, masterKey, "kind-substitution", value -> value[Integer.BYTES * 4 + 2] = 1, true); - assertEntryMutationRejected(fixture.path, image, masterKey, - "encoding-substitution", + assertEntryMutationRejected(fixture.path, image, masterKey, "encoding-substitution", value -> value[Integer.BYTES * 4 + 3] = 1, true); assertEntryMutationRejected(fixture.path, image, masterKey, "hmac-substitution", value -> value[Integer.BYTES * 4 + 4] = 1, true); - assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, - "coherent-entry-id", true); - assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, - "coherent-entry-nonce", false); + assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, "coherent-entry-id", true); + assertCoherentIdentityMutationRejected(fixture.path, image, masterKey, "coherent-entry-nonce", false); } finally { wipe(image); wipe(masterKey); @@ -259,35 +249,24 @@ class KeyringCryptographicFormatTest { @Test void decodedEntryAndManifestSchemasRejectTypeConfusionAndBounds() throws Exception { start("decodedEntryAndManifestSchemasRejectTypeConfusionAndBounds"); - byte[] validEntry = entryPlaintext(1, "a", "AES", 3, 3, 0, - new byte[32], false); + byte[] validEntry = entryPlaintext(1, "a", "AES", 3, 3, 0, new byte[32], false); try { invokeDecodeEntry(validEntry); - assertDecodeEntryRejected(entryPlaintext(2, "a", "AES", 3, 3, 0, - new byte[32], false)); - assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 99, 3, 0, - new byte[32], false)); - assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 99, 0, - new byte[32], false)); - assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 99, - new byte[32], false)); - assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 0, - new byte[32], true)); + assertDecodeEntryRejected(entryPlaintext(2, "a", "AES", 3, 3, 0, new byte[32], false)); + assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 99, 3, 0, new byte[32], false)); + assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 99, 0, new byte[32], false)); + assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 99, new byte[32], false)); + assertDecodeEntryRejected(entryPlaintext(1, "a", "AES", 3, 3, 0, new byte[32], true)); assertDecodeEntryRejected(lengthOnlyEntry(-1, "AES")); - assertDecodeEntryRejected(lengthOnlyEntry(KeyringStore.MAX_ALIAS_BYTES + 1, - "AES")); + assertDecodeEntryRejected(lengthOnlyEntry(KeyringStore.MAX_ALIAS_BYTES + 1, "AES")); assertDecodeEntryRejected(metadataLengthEntry(-1)); - assertDecodeEntryRejected(metadataLengthEntry( - KeyringStore.MAX_METADATA_BYTES + 1)); + assertDecodeEntryRejected(metadataLengthEntry(KeyringStore.MAX_METADATA_BYTES + 1)); assertDecodeEntryRejected(encodedLengthEntry(-1)); - assertDecodeEntryRejected(encodedLengthEntry( - KeyringStore.MAX_ENTRY_CIPHERTEXT_BYTES)); + assertDecodeEntryRejected(encodedLengthEntry(KeyringStore.MAX_ENTRY_CIPHERTEXT_BYTES)); - byte[] maximumAlias = entryPlaintext(1, - "a".repeat(KeyringStore.MAX_ALIAS_BYTES), "AES", 3, 3, 0, + byte[] maximumAlias = entryPlaintext(1, "a".repeat(KeyringStore.MAX_ALIAS_BYTES), "AES", 3, 3, 0, new byte[1], false); - byte[] maximumMetadata = entryPlaintext(1, "a", - "A".repeat(KeyringStore.MAX_METADATA_BYTES), 3, 3, 0, + byte[] maximumMetadata = entryPlaintext(1, "a", "A".repeat(KeyringStore.MAX_METADATA_BYTES), 3, 3, 0, new byte[1], false); try { invokeDecodeEntry(maximumAlias); @@ -320,26 +299,26 @@ class KeyringCryptographicFormatTest { Path source = createEmpty("bounds-source.zek", 31); byte[] image = Files.readAllBytes(source); try { - assertRejected(copyWithMutation(source, image, "negative-count", - value -> putInt(value, ENTRY_COUNT_OFFSET, -1)), + assertRejected( + copyWithMutation(source, image, "negative-count", value -> putInt(value, ENTRY_COUNT_OFFSET, -1)), KeyringException.Code.KEYRING_LIMIT_EXCEEDED); - assertRejected(copyWithMutation(source, image, "oversized-count", - value -> putInt(value, ENTRY_COUNT_OFFSET, - KeyringStore.MAX_ENTRY_COUNT + 1)), + assertRejected( + copyWithMutation(source, image, "oversized-count", + value -> putInt(value, ENTRY_COUNT_OFFSET, KeyringStore.MAX_ENTRY_COUNT + 1)), KeyringException.Code.KEYRING_LIMIT_EXCEEDED); - assertRejected(copyWithMutation(source, image, "old-main-version", - value -> putInt(value, VERSION_OFFSET, 1)), + assertRejected( + copyWithMutation(source, image, "old-main-version", value -> putInt(value, VERSION_OFFSET, 1)), KeyringException.Code.KEYRING_FORMAT_INVALID); - assertRejected(copyWithMutation(source, image, "draft-main-version", - value -> putInt(value, VERSION_OFFSET, 3)), + assertRejected( + copyWithMutation(source, image, "draft-main-version", value -> putInt(value, VERSION_OFFSET, 3)), KeyringException.Code.KEYRING_FORMAT_INVALID); } finally { wipe(image); } Path oversized = temporaryDirectory.resolve("oversized.zek"); - try (java.nio.channels.FileChannel channel = java.nio.channels.FileChannel.open( - oversized, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + try (java.nio.channels.FileChannel channel = java.nio.channels.FileChannel.open(oversized, + StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { channel.position(KeyringStore.MAX_FILE_BYTES); channel.write(ByteBuffer.wrap(new byte[] { 0 })); } @@ -351,8 +330,7 @@ class KeyringCryptographicFormatTest { } Path plaintext = temporaryDirectory.resolve("plaintext-v1.zek"); - Files.writeString(plaintext, "# KeyringStore v1\njava.io.File\nHmacSHA1\n", - StandardCharsets.UTF_8); + Files.writeString(plaintext, "# KeyringStore v1\njava.io.File\nHmacSHA1\n", StandardCharsets.UTF_8); ownerOnly(plaintext); assertRejected(plaintext, KeyringException.Code.KEYRING_FORMAT_INVALID); @@ -363,10 +341,9 @@ class KeyringCryptographicFormatTest { root.addHandler(handler); try { try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(protectedStore, password, - KeyringProtection.standard(), deterministicRandom(61))) { - store.putSecret("sentinel", "AES", - new SecretKeySpec(Arrays.copyOf(sentinel, 32), "AES")); + KeyringStore store = KeyringStore.create(protectedStore, password, KeyringProtection.standard(), + deterministicRandom(61))) { + store.putSecret("sentinel", "AES", new SecretKeySpec(Arrays.copyOf(sentinel, 32), "AES")); } assertAbsent(Files.readAllBytes(protectedStore), sentinel); assertAbsent(Files.readAllBytes(sidecar(protectedStore)), sentinel); @@ -397,8 +374,8 @@ class KeyringCryptographicFormatTest { private Path createEmpty(String name, int seed) throws Exception { Path path = temporaryDirectory.resolve(name); try (KeyringPassword password = password(); - KeyringStore ignored = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(seed))) { + KeyringStore ignored = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(seed))) { // Current empty image and reservation sidecar are durable. } return path; @@ -418,8 +395,8 @@ class KeyringCryptographicFormatTest { Arrays.fill(first, (byte) 0x31); Arrays.fill(second, (byte) 0x42); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(seed))) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(seed))) { store.putSecret("one", "AES", new SecretKeySpec(first, "AES")); store.putSecret("two", "AES", new SecretKeySpec(second, "AES")); masterKey = bytesField(store, "masterKey").clone(); @@ -430,29 +407,28 @@ class KeyringCryptographicFormatTest { return new StoreFixture(path, masterKey); } - private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, - String name, Mutation mutation) throws Exception { + private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, String name, + Mutation mutation) throws Exception { assertManifestMutationRejected(source, image, masterKey, name, mutation, KeyringException.Code.KEYRING_FORMAT_INVALID); } - private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, - String name, Mutation mutation, KeyringException.Code code) throws Exception { + private void assertManifestMutationRejected(Path source, byte[] image, byte[] masterKey, String name, + Mutation mutation, KeyringException.Code code) throws Exception { assertManifestReplacementRejected(source, image, masterKey, name, value -> { mutation.apply(value); return value; }, code); } - private void assertManifestReplacementRejected(Path source, byte[] image, - byte[] masterKey, String name, Replacement mutation) throws Exception { + private void assertManifestReplacementRejected(Path source, byte[] image, byte[] masterKey, String name, + Replacement mutation) throws Exception { assertManifestReplacementRejected(source, image, masterKey, name, mutation, KeyringException.Code.KEYRING_FORMAT_INVALID); } - private void assertManifestReplacementRejected(Path source, byte[] image, - byte[] masterKey, String name, Replacement mutation, - KeyringException.Code code) throws Exception { + private void assertManifestReplacementRejected(Path source, byte[] image, byte[] masterKey, String name, + Replacement mutation, KeyringException.Code code) throws Exception { byte[] plaintext = decryptManifest(image, masterKey); byte[] replacement = null; byte[] rewritten = null; @@ -469,8 +445,8 @@ class KeyringCryptographicFormatTest { } } - private void assertEntryMutationRejected(Path source, byte[] image, byte[] masterKey, - String name, Mutation mutation, boolean plaintextMutation) throws Exception { + private void assertEntryMutationRejected(Path source, byte[] image, byte[] masterKey, String name, + Mutation mutation, boolean plaintextMutation) throws Exception { ImageLayout imageLayout = layout(image); WireEntry entry = imageLayout.entries.get(0); byte[] ciphertext = slice(image, entry.ciphertextOffset, entry.endOffset); @@ -478,8 +454,7 @@ class KeyringCryptographicFormatTest { byte[] rewritten = null; try { if (plaintextMutation) { - byte[] nonce = slice(image, entry.nonceOffset, - entry.nonceOffset + KeyringStore.NONCE_BYTES); + byte[] nonce = slice(image, entry.nonceOffset, entry.nonceOffset + KeyringStore.NONCE_BYTES); byte[] aad = entryAad(image, entry, 0); byte[] plaintext = null; try { @@ -497,13 +472,10 @@ class KeyringCryptographicFormatTest { } rewritten = installFirstCiphertext(image, masterKey, changed); Path path = copyImage(source, rewritten, name); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { - KeyringException failure = assertThrows(KeyringException.class, - () -> store.getSecret("one")); + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { + KeyringException failure = assertThrows(KeyringException.class, () -> store.getSecret("one")); assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, failure.code()); - assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), - failure.getMessage()); + assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), failure.getMessage()); } } finally { wipe(ciphertext); @@ -512,8 +484,8 @@ class KeyringCryptographicFormatTest { } } - private void assertCoherentIdentityMutationRejected(Path source, byte[] image, - byte[] masterKey, String name, boolean entryId) throws Exception { + private void assertCoherentIdentityMutationRejected(Path source, byte[] image, byte[] masterKey, String name, + boolean entryId) throws Exception { byte[] rewritten = image.clone(); byte[] manifest = decryptManifest(image, masterKey); try { @@ -531,10 +503,8 @@ class KeyringCryptographicFormatTest { wipe(rewritten); rewritten = withManifest; Path path = copyImage(source, rewritten, name); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { - KeyringException failure = assertThrows(KeyringException.class, - () -> store.getSecret("one")); + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { + KeyringException failure = assertThrows(KeyringException.class, () -> store.getSecret("one")); assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, failure.code()); } } finally { @@ -543,8 +513,7 @@ class KeyringCryptographicFormatTest { } } - private static byte[] installFirstCiphertext(byte[] image, byte[] masterKey, - byte[] ciphertext) throws Exception { + private static byte[] installFirstCiphertext(byte[] image, byte[] masterKey, byte[] ciphertext) throws Exception { byte[] result = image.clone(); ImageLayout imageLayout = layout(result); WireEntry entry = imageLayout.entries.get(0); @@ -581,8 +550,7 @@ class KeyringCryptographicFormatTest { } } - private static byte[] replaceManifest(byte[] image, byte[] masterKey, - byte[] plaintext) throws Exception { + private static byte[] replaceManifest(byte[] image, byte[] masterKey, byte[] plaintext) throws Exception { ImageLayout imageLayout = layout(image); byte[] nonce = slice(image, imageLayout.manifestNonceOffset, imageLayout.manifestNonceOffset + KeyringStore.NONCE_BYTES); @@ -591,11 +559,9 @@ class KeyringCryptographicFormatTest { try { ciphertext = crypt(Cipher.ENCRYPT_MODE, masterKey, nonce, aad, plaintext); int lengthOffset = imageLayout.manifestCipherOffset - Integer.BYTES; - byte[] result = Arrays.copyOf(image, imageLayout.manifestCipherOffset - + ciphertext.length); + byte[] result = Arrays.copyOf(image, imageLayout.manifestCipherOffset + ciphertext.length); putInt(result, lengthOffset, ciphertext.length); - System.arraycopy(ciphertext, 0, result, imageLayout.manifestCipherOffset, - ciphertext.length); + System.arraycopy(ciphertext, 0, result, imageLayout.manifestCipherOffset, ciphertext.length); return result; } finally { wipe(nonce); @@ -605,8 +571,7 @@ class KeyringCryptographicFormatTest { } private static byte[] manifestAad(byte[] image) { - ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES - + KeyringStore.UUID_BYTES + Integer.BYTES * 2); + ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES * 2); buffer.put(KeyringStore.MAGIC); buffer.putInt(KeyringStore.FORMAT_VERSION); buffer.put(image, STORE_ID_OFFSET, KeyringStore.UUID_BYTES); @@ -616,8 +581,7 @@ class KeyringCryptographicFormatTest { } private static byte[] entryAad(byte[] image, WireEntry entry, int position) { - ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES - + KeyringStore.UUID_BYTES * 2 + Integer.BYTES * 2); + ByteBuffer buffer = ByteBuffer.allocate(8 + Integer.BYTES + KeyringStore.UUID_BYTES * 2 + Integer.BYTES * 2); buffer.put(KeyringStore.MAGIC); buffer.putInt(KeyringStore.FORMAT_VERSION); buffer.put(image, STORE_ID_OFFSET, KeyringStore.UUID_BYTES); @@ -627,11 +591,9 @@ class KeyringCryptographicFormatTest { return buffer.array(); } - private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad, - byte[] input) throws Exception { + private static byte[] crypt(int mode, byte[] key, byte[] nonce, byte[] aad, byte[] input) throws Exception { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(mode, new SecretKeySpec(key, "AES"), - new GCMParameterSpec(KeyringStore.GCM_TAG_BITS, nonce)); + cipher.init(mode, new SecretKeySpec(key, "AES"), new GCMParameterSpec(KeyringStore.GCM_TAG_BITS, nonce)); cipher.updateAAD(aad); return cipher.doFinal(input); } @@ -664,9 +626,8 @@ class KeyringCryptographicFormatTest { int digest = buffer.position(); buffer.position(digest + KeyringStore.SHA256_BYTES); if (index == target) { - return new ManifestDescriptor(start, buffer.position(), entryId, position, - alias, aliasLength, algorithm, kind, encoding, - hmac, nonce, ciphertextLength, digest); + return new ManifestDescriptor(start, buffer.position(), entryId, position, alias, aliasLength, + algorithm, kind, encoding, hmac, nonce, ciphertextLength, digest); } } throw new IllegalArgumentException("descriptor index"); @@ -712,11 +673,9 @@ class KeyringCryptographicFormatTest { private void assertUniformUnlockFailure(Path source, char[] candidate) throws Exception { try (KeyringPassword password = new KeyringPassword(candidate)) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(source, password)); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(source, password)); assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED, failure.code()); - assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED.name(), - failure.getMessage()); + assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED.name(), failure.getMessage()); assertNull(failure.getCause()); assertEquals(0, failure.getSuppressed().length); } @@ -724,8 +683,7 @@ class KeyringCryptographicFormatTest { private void assertRejected(Path path, KeyringException.Code code) throws Exception { try (KeyringPassword password = password()) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(code, failure.code()); assertEquals(code.name(), failure.getMessage()); assertNull(failure.getCause()); @@ -734,18 +692,16 @@ class KeyringCryptographicFormatTest { private void assertSafeRejected(Path path) throws Exception { try (KeyringPassword password = password()) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); - assertTrue(Set.of(KeyringException.Code.KEYRING_FORMAT_INVALID, - KeyringException.Code.KEYRING_LIMIT_EXCEEDED, - KeyringException.Code.KEYRING_UNLOCK_FAILED).contains(failure.code())); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); + assertTrue( + Set.of(KeyringException.Code.KEYRING_FORMAT_INVALID, KeyringException.Code.KEYRING_LIMIT_EXCEEDED, + KeyringException.Code.KEYRING_UNLOCK_FAILED).contains(failure.code())); assertEquals(failure.code().name(), failure.getMessage()); assertNull(failure.getCause()); } } - private Path copyWithMutation(Path source, byte[] image, String name, - Mutation mutation) throws Exception { + private Path copyWithMutation(Path source, byte[] image, String name, Mutation mutation) throws Exception { byte[] copy = image.clone(); try { mutation.apply(copy); @@ -765,8 +721,7 @@ class KeyringCryptographicFormatTest { } private static List openAliases(Path path) throws Exception { - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { return store.aliases(); } } @@ -784,8 +739,7 @@ class KeyringCryptographicFormatTest { int length = buffer.getInt(); int ciphertext = buffer.position(); buffer.position(ciphertext + length); - entries.add(new WireEntry(entryId, nonce, ciphertext, - buffer.position())); + entries.add(new WireEntry(entryId, nonce, ciphertext, buffer.position())); } int manifestNonce = buffer.position(); buffer.position(manifestNonce + KeyringStore.NONCE_BYTES); @@ -812,8 +766,7 @@ class KeyringCryptographicFormatTest { private static byte[] duplicateFirstEntry(byte[] image, ImageLayout layout) { WireEntry first = layout.entries.get(0); WireEntry second = layout.entries.get(1); - return replaceEntry(image, second, - slice(image, first.entryIdOffset, first.endOffset)); + return replaceEntry(image, second, slice(image, first.entryIdOffset, first.endOffset)); } private static byte[] deleteFirstEntry(byte[] image, ImageLayout layout) { @@ -833,8 +786,7 @@ class KeyringCryptographicFormatTest { System.arraycopy(image, 0, result, 0, layout.manifestNonceOffset); putInt(result, ENTRY_COUNT_OFFSET, 3); System.arraycopy(encoded, 0, result, layout.manifestNonceOffset, encoded.length); - System.arraycopy(image, layout.manifestNonceOffset, result, - layout.manifestNonceOffset + encoded.length, + System.arraycopy(image, layout.manifestNonceOffset, result, layout.manifestNonceOffset + encoded.length, image.length - layout.manifestNonceOffset); wipe(encoded); return result; @@ -845,14 +797,14 @@ class KeyringCryptographicFormatTest { byte[] result = new byte[image.length - currentLength + replacement.length]; System.arraycopy(image, 0, result, 0, target.entryIdOffset); System.arraycopy(replacement, 0, result, target.entryIdOffset, replacement.length); - System.arraycopy(image, target.endOffset, result, - target.entryIdOffset + replacement.length, image.length - target.endOffset); + System.arraycopy(image, target.endOffset, result, target.entryIdOffset + replacement.length, + image.length - target.endOffset); wipe(replacement); return result; } - private static byte[] entryPlaintext(int version, String alias, String algorithm, - int kind, int encoding, int hmac, byte[] key, boolean trailing) throws Exception { + private static byte[] entryPlaintext(int version, String alias, String algorithm, int kind, int encoding, int hmac, + byte[] key, boolean trailing) throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (DataOutputStream out = new DataOutputStream(bytes)) { out.writeInt(version); @@ -870,8 +822,7 @@ class KeyringCryptographicFormatTest { return bytes.toByteArray(); } - private static byte[] lengthOnlyEntry(int aliasLength, String algorithm) - throws Exception { + private static byte[] lengthOnlyEntry(int aliasLength, String algorithm) throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (DataOutputStream out = new DataOutputStream(bytes)) { out.writeInt(1); @@ -905,10 +856,8 @@ class KeyringCryptographicFormatTest { return bytes.toByteArray(); } - private static byte[] emptyManifest(int version, long highWater, int count, - boolean trailing) { - ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 3 + Long.BYTES - + (trailing ? 1 : 0)); + private static byte[] emptyManifest(int version, long highWater, int count, boolean trailing) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES * 3 + Long.BYTES + (trailing ? 1 : 0)); buffer.putInt(version).putInt(0x010203).putLong(highWater).putInt(count); if (trailing) { buffer.put((byte) 0); @@ -917,8 +866,7 @@ class KeyringCryptographicFormatTest { } private static void invokeDecodeEntry(byte[] input) throws Exception { - Method method = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext", - byte[].class); + Method method = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext", byte[].class); method.setAccessible(true); method.invoke(null, (Object) input); } @@ -933,16 +881,13 @@ class KeyringCryptographicFormatTest { } } - private static void invokeDecodeManifest(byte[] input, int expectedCount) - throws Exception { - Method method = KeyringStore.class.getDeclaredMethod("decodeManifest", - byte[].class, int.class); + private static void invokeDecodeManifest(byte[] input, int expectedCount) throws Exception { + Method method = KeyringStore.class.getDeclaredMethod("decodeManifest", byte[].class, int.class); method.setAccessible(true); method.invoke(null, input, expectedCount); } - private static void assertDecodeManifestRejected(byte[] input, int expectedCount) - throws Exception { + private static void assertDecodeManifestRejected(byte[] input, int expectedCount) throws Exception { try { InvocationTargetException failure = assertThrows(InvocationTargetException.class, () -> invokeDecodeManifest(input, expectedCount)); @@ -971,8 +916,7 @@ class KeyringCryptographicFormatTest { } private static int indexOf(byte[] haystack, byte[] needle) { - outer: - for (int index = 0; index <= haystack.length - needle.length; index++) { + outer: for (int index = 0; index <= haystack.length - needle.length; index++) { for (int offset = 0; offset < needle.length; offset++) { if (haystack[index + offset] != needle[offset]) { continue outer; @@ -992,8 +936,7 @@ class KeyringCryptographicFormatTest { } private static void ownerOnly(Path path) throws Exception { - Files.setPosixFilePermissions(path, Set.of( - java.nio.file.attribute.PosixFilePermission.OWNER_READ, + Files.setPosixFilePermissions(path, Set.of(java.nio.file.attribute.PosixFilePermission.OWNER_READ, java.nio.file.attribute.PosixFilePermission.OWNER_WRITE)); } @@ -1039,18 +982,15 @@ class KeyringCryptographicFormatTest { byte[] apply(byte[] value); } - private record WireEntry(int entryIdOffset, int nonceOffset, - int ciphertextOffset, int endOffset) { + private record WireEntry(int entryIdOffset, int nonceOffset, int ciphertextOffset, int endOffset) { } - private record ImageLayout(List entries, int manifestNonceOffset, - int manifestCipherOffset) { + private record ImageLayout(List entries, int manifestNonceOffset, int manifestCipherOffset) { } - private record ManifestDescriptor(int startOffset, int endOffset, - int entryIdOffset, int positionOffset, int aliasOffset, int aliasLength, - int algorithmOffset, int kindOffset, int encodingOffset, int hmacOffset, int nonceOffset, - int ciphertextLengthOffset, int digestOffset) { + private record ManifestDescriptor(int startOffset, int endOffset, int entryIdOffset, int positionOffset, + int aliasOffset, int aliasLength, int algorithmOffset, int kindOffset, int encodingOffset, int hmacOffset, + int nonceOffset, int ciphertextLengthOffset, int digestOffset) { } private record StoreFixture(Path path, byte[] masterKey) { diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringFilesystemSecurityTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringFilesystemSecurityTest.java index 9720d62..e3b1805 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringFilesystemSecurityTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringFilesystemSecurityTest.java @@ -54,12 +54,9 @@ class KeyringFilesystemSecurityTest { private static final char[] PASSWORD = { 'f', 'i', 'l', 'e', 's', 'y', 's' }; private static final byte[] CHILD_PASSWORD = { 'f', 'i', 'l', 'e', 's', 'y', 's' }; private static final long TIMEOUT_SECONDS = 15L; - private static final Set DIRECTORY_PERMISSIONS = Set.of( - PosixFilePermission.OWNER_READ, - PosixFilePermission.OWNER_WRITE, - PosixFilePermission.OWNER_EXECUTE); - private static final Set FILE_PERMISSIONS = Set.of( - PosixFilePermission.OWNER_READ, + private static final Set DIRECTORY_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE); + private static final Set FILE_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); @TempDir @@ -80,20 +77,15 @@ class KeyringFilesystemSecurityTest { } assertOpenSucceeds(path); - for (PosixFilePermission unsafe : List.of( - PosixFilePermission.GROUP_READ, - PosixFilePermission.GROUP_WRITE, - PosixFilePermission.OTHERS_READ, - PosixFilePermission.OTHERS_WRITE)) { + for (PosixFilePermission unsafe : List.of(PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, + PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE)) { Path unsafeParent = temporaryDirectory.resolve("parent-" + unsafe.name()); Path unsafeStore = unsafeParent.resolve("keys.zek"); createPopulated(unsafeStore, unsafe.ordinal() + 10); - Set permissions = new java.util.HashSet<>( - DIRECTORY_PERMISSIONS); + Set permissions = new java.util.HashSet<>(DIRECTORY_PERMISSIONS); permissions.add(unsafe); Files.setPosixFilePermissions(unsafeParent, permissions); - assertRedactedFailure(unsafeStore, - KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED); + assertRedactedFailure(unsafeStore, KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED); Files.setPosixFilePermissions(unsafeParent, DIRECTORY_PERMISSIONS); } @@ -119,8 +111,8 @@ class KeyringFilesystemSecurityTest { Path path = temporaryDirectory.resolve("owner-mismatch.zek"); createPopulated(path, 31); - Method validator = KeyringStore.class.getDeclaredMethod("validateExistingFile", - Path.class, UserPrincipal.class); + Method validator = KeyringStore.class.getDeclaredMethod("validateExistingFile", Path.class, + UserPrincipal.class); validator.setAccessible(true); UserPrincipal other = () -> "controlled-other-owner"; InvocationTargetException failure = assertThrows(InvocationTargetException.class, @@ -151,15 +143,14 @@ class KeyringFilesystemSecurityTest { Path path = temporaryDirectory.resolve("shared.zek"); byte[] material = material((byte) 0x41); try (KeyringPassword password = password(); - KeyringStore first = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(41)); + KeyringStore first = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(41)); KeyringPassword secondPassword = password()) { first.putSecret("shared", "AES", new SecretKeySpec(material, "AES")); KeyringException contention = assertThrows(KeyringException.class, () -> KeyringStore.open(path, secondPassword)); assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN, contention.code()); - assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN.name(), - contention.getMessage()); + assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN.name(), contention.getMessage()); assertNull(contention.getCause()); assertArrayEquals(material, first.getSecret("shared").getEncoded()); } finally { @@ -216,8 +207,8 @@ class KeyringFilesystemSecurityTest { byte[] first = material((byte) 0x12); byte[] second = material((byte) 0x34); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(71))) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(71))) { store.putSecret("one", "AES", new SecretKeySpec(first, "AES")); store.putSecret("two", "AES", new SecretKeySpec(second, "AES")); runConcurrentReaders(store, first, second); @@ -273,8 +264,7 @@ class KeyringFilesystemSecurityTest { BlockingRandom random = new BlockingRandom(81); KeyringStore store; try (KeyringPassword password = password()) { - store = KeyringStore.create(path, password, - KeyringProtection.standard(), random); + store = KeyringStore.create(path, password, KeyringProtection.standard(), random); } byte[] material = material((byte) 0x5c); AtomicReference putFailure = new AtomicReference<>(); @@ -282,8 +272,7 @@ class KeyringFilesystemSecurityTest { random.arm(); Thread mutation = new Thread(() -> { try { - store.putSecret("admitted", "AES", - new SecretKeySpec(material, "AES")); + store.putSecret("admitted", "AES", new SecretKeySpec(material, "AES")); } catch (Throwable throwable) { putFailure.set(throwable); } @@ -305,11 +294,8 @@ class KeyringFilesystemSecurityTest { assertNull(putFailure.get()); assertNull(closeFailure.get()); assertTrue(store.isDestroyed()); - assertThrows(KeyringException.class, - () -> store.putSecret("late", "AES", - new SecretKeySpec(material, "AES"))); - try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password)) { + assertThrows(KeyringException.class, () -> store.putSecret("late", "AES", new SecretKeySpec(material, "AES"))); + try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) { assertArrayEquals(material, reopened.getSecret("admitted").getEncoded()); } finally { wipe(material); @@ -367,8 +353,7 @@ class KeyringFilesystemSecurityTest { createPopulated(path, 111); Path link = temporaryDirectory.resolve("linked-parent"); Files.createSymbolicLink(link, real.getFileName()); - assertRedactedFailure(link.resolve("keys.zek"), - KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED); + assertRedactedFailure(link.resolve("keys.zek"), KeyringException.Code.KEYRING_FILESYSTEM_UNSUPPORTED); } private void assertArtifactHardLinkRejected(Artifact artifact) throws Exception { @@ -394,8 +379,7 @@ class KeyringFilesystemSecurityTest { Path malicious = temporaryDirectory.resolve(".temp-race.zek.precreated.tmp"); Files.createSymbolicLink(malicious, target.getFileName()); byte[] material = material((byte) 0x63); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { store.putSecret("safe", "AES", new SecretKeySpec(material, "AES")); assertArrayEquals(before, Files.readAllBytes(target)); } finally { @@ -404,8 +388,7 @@ class KeyringFilesystemSecurityTest { } } - private void runConcurrentReaders(KeyringStore store, byte[] first, byte[] second) - throws Exception { + private void runConcurrentReaders(KeyringStore store, byte[] first, byte[] second) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(8); CountDownLatch start = new CountDownLatch(1); List> results = new ArrayList<>(); @@ -415,8 +398,7 @@ class KeyringFilesystemSecurityTest { results.add(executor.submit(() -> { start.await(); if (selected == 7) { - assertThrows(IllegalArgumentException.class, - () -> store.getSecret("missing")); + assertThrows(IllegalArgumentException.class, () -> store.getSecret("missing")); return true; } String alias = selected % 2 == 0 ? "one" : "two"; @@ -472,8 +454,8 @@ class KeyringFilesystemSecurityTest { private void createPopulated(Path path, int seed) throws Exception { byte[] material = material((byte) seed); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(seed))) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(seed))) { store.putSecret("child", "AES", new SecretKeySpec(material, "AES")); } finally { wipe(material); @@ -481,8 +463,7 @@ class KeyringFilesystemSecurityTest { } private static void assertOpenSucceeds(Path path) throws Exception { - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { assertFalse(store.isDestroyed()); store.aliases(); } @@ -490,19 +471,16 @@ class KeyringFilesystemSecurityTest { private static void assertAlreadyOpen(Path path) throws Exception { try (KeyringPassword password = password()) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN, failure.code()); assertEquals(failure.code().name(), failure.getMessage()); assertNull(failure.getCause()); } } - private static void assertRedactedFailure(Path path, KeyringException.Code code) - throws Exception { + private static void assertRedactedFailure(Path path, KeyringException.Code code) throws Exception { try (KeyringPassword password = password()) { - KeyringException failure = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException failure = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(code, failure.code()); assertEquals(code.name(), failure.getMessage()); assertNull(failure.getCause()); @@ -511,8 +489,8 @@ class KeyringFilesystemSecurityTest { } } - private static Thread closeThread(KeyringStore store, CountDownLatch started, - AtomicReference failure, String name) { + private static Thread closeThread(KeyringStore store, CountDownLatch started, AtomicReference failure, + String name) { return new Thread(() -> { started.countDown(); try { @@ -523,8 +501,7 @@ class KeyringFilesystemSecurityTest { }, name); } - private static void awaitQueued(ReentrantReadWriteLock lock, int minimum) - throws Exception { + private static void awaitQueued(ReentrantReadWriteLock lock, int minimum) throws Exception { long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(TIMEOUT_SECONDS); while (lock.getQueueLength() < minimum && System.nanoTime() < deadline) { Thread.onSpinWait(); @@ -674,19 +651,17 @@ class KeyringFilesystemSecurityTest { private ChildOwner(Process process) { this.process = process; - output = new BufferedReader(new InputStreamReader(process.getInputStream(), - StandardCharsets.UTF_8)); + output = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); input = process.getOutputStream(); } private static ChildOwner start(Path path) throws Exception { - String executable = System.getProperty("os.name", "") - .toLowerCase(java.util.Locale.ROOT).contains("win") - ? "java.exe" : "java"; + String executable = System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT).contains("win") + ? "java.exe" + : "java"; Path java = Path.of(System.getProperty("java.home"), "bin", executable); Process process = new ProcessBuilder(java.toString(), "-cp", childClasspath(), - KeyringStoreLockProcess.class.getName(), path.toString()) - .redirectErrorStream(true).start(); + KeyringStoreLockProcess.class.getName(), path.toString()).redirectErrorStream(true).start(); return new ChildOwner(process); } @@ -777,8 +752,7 @@ class KeyringFilesystemSecurityTest { } private static String codeSource(Class type) throws Exception { - return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()) - .toString(); + return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toString(); } } @@ -802,8 +776,8 @@ final class KeyringStoreLockProcess { } try (KeyringPassword password = new KeyringPassword(passwordChars); KeyringStore store = KeyringStore.open(Path.of(args[0]), password); - BufferedReader control = new BufferedReader(new InputStreamReader( - System.in, StandardCharsets.US_ASCII))) { + BufferedReader control = new BufferedReader( + new InputStreamReader(System.in, StandardCharsets.US_ASCII))) { Arrays.fill(passwordChars, '\0'); Arrays.fill(passwordBytes, (byte) 0); System.out.println("KEYRING_OPEN"); diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringImportRegistryTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringImportRegistryTest.java index 4a477e2..c068e44 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringImportRegistryTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringImportRegistryTest.java @@ -36,34 +36,31 @@ class KeyringImportRegistryTest { @Test void persistentImporterMatrixIsClosedUniqueAndExecutable() throws Exception { start("persistentImporterMatrixIsClosedUniqueAndExecutable"); - List mappings = - KeyringImportRegistry.mappings(); + List mappings = KeyringImportRegistry.mappings(); assertEquals(19, count(mappings, KeyringStore.Kind.PUBLIC_KEY)); assertEquals(19, count(mappings, KeyringStore.Kind.PRIVATE_KEY)); assertEquals(6, count(mappings, KeyringStore.Kind.SECRET_KEY)); assertEquals(mappings.size(), mappings.stream().distinct().count()); for (KeyringImportRegistry.PersistentMapping mapping : mappings) { - KeyringImportRegistry.validateMapping(mapping.algorithmId(), mapping.kind(), - mapping.encoding(), mapping.hmacVariant()); + KeyringImportRegistry.validateMapping(mapping.algorithmId(), mapping.kind(), mapping.encoding(), + mapping.hmacVariant()); } roundTripAsymmetricMappings(mappings); roundTripSecretMappings(mappings); assertEquals(KeyringException.Code.KEYRING_IMPORT_MAPPING_INVALID, assertThrows(KeyringException.class, - () -> KeyringImportRegistry.validateMapping("RSA", - KeyringStore.Kind.SECRET_KEY, KeyringStore.Encoding.RAW, - KeyringImportRegistry.HmacVariant.NONE)).code()); + () -> KeyringImportRegistry.validateMapping("RSA", KeyringStore.Kind.SECRET_KEY, + KeyringStore.Encoding.RAW, KeyringImportRegistry.HmacVariant.NONE)) + .code()); System.out.println("...mapping-count=" + mappings.size()); ok(); } @Test - void alternateProviderStandardEncodingUsesCanonicalImporter( - @TempDir Path temporaryDirectory) throws Exception { + void alternateProviderStandardEncodingUsesCanonicalImporter(@TempDir Path temporaryDirectory) throws Exception { start("alternateProviderStandardEncodingUsesCanonicalImporter"); - java.security.KeyPairGenerator generator = - java.security.KeyPairGenerator.getInstance("RSA", "BC"); + java.security.KeyPairGenerator generator = java.security.KeyPairGenerator.getInstance("RSA", "BC"); generator.initialize(2048); KeyPair pair = generator.generateKeyPair(); byte[] encoded = pair.getPublic().getEncoded(); @@ -72,20 +69,17 @@ class KeyringImportRegistryTest { byte[] importedEncoding = null; byte[] reopenedEncoding = null; try { - imported = KeyringImportRegistry.importKey("RSA", - KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, + imported = KeyringImportRegistry.importKey("RSA", KeyringStore.Kind.PUBLIC_KEY, KeyringStore.Encoding.X509, KeyringImportRegistry.HmacVariant.NONE, encoded); importedEncoding = imported.getEncoded(); assertArrayEquals(encoded, importedEncoding); assertEquals("RSA", imported.getAlgorithm()); assertNotEquals(pair.getPublic().getClass(), imported.getClass()); Path path = temporaryDirectory.resolve("alternate-provider.zek"); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) { store.putPublic("alternate", "RSA", pair.getPublic()); } - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.open(path, password)) { reopened = store.getPublic("alternate"); reopenedEncoding = reopened.getEncoded(); assertArrayEquals(encoded, reopenedEncoding); @@ -100,28 +94,23 @@ class KeyringImportRegistryTest { ok(); } - private static long count(List mappings, - KeyringStore.Kind kind) { + private static long count(List mappings, KeyringStore.Kind kind) { return mappings.stream().filter(mapping -> mapping.kind() == kind).count(); } - private static void roundTripAsymmetricMappings( - List mappings) throws Exception { - List algorithms = mappings.stream() - .filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY) - .map(KeyringImportRegistry.PersistentMapping::algorithmId) - .toList(); + private static void roundTripAsymmetricMappings(List mappings) + throws Exception { + List algorithms = mappings.stream().filter(mapping -> mapping.kind() == KeyringStore.Kind.PUBLIC_KEY) + .map(KeyringImportRegistry.PersistentMapping::algorithmId).toList(); for (String algorithmId : algorithms) { KeyPair pair = generatePair(algorithmId); - roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY), - pair.getPublic()); - roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY), - pair.getPrivate()); + roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PUBLIC_KEY), pair.getPublic()); + roundTrip(mapping(mappings, algorithmId, KeyringStore.Kind.PRIVATE_KEY), pair.getPrivate()); } } - private static void roundTripSecretMappings( - List mappings) throws Exception { + private static void roundTripSecretMappings(List mappings) + throws Exception { for (KeyringImportRegistry.PersistentMapping mapping : mappings) { if (mapping.kind() != KeyringStore.Kind.SECRET_KEY) { continue; @@ -147,33 +136,26 @@ class KeyringImportRegistryTest { CryptoAlgorithm algorithm = CryptoAlgorithms.require(algorithmId); KeyOperationInfo generation = algorithm.keyOperations().stream() .filter(info -> info.operation() == KeyOperation.ASYMMETRIC_KEY_PAIR_GENERATE) - .filter(info -> info.defaultSpec() != null) - .findFirst() - .orElseThrow(() -> new AssertionError( - "No default key-pair generation mapping for " + algorithmId)); - AsymmetricKeyPairGenerator generator = - algorithm.asymmetricKeyPairGenerator(generation.specType()); + .filter(info -> info.defaultSpec() != null).findFirst() + .orElseThrow(() -> new AssertionError("No default key-pair generation mapping for " + algorithmId)); + AsymmetricKeyPairGenerator generator = algorithm.asymmetricKeyPairGenerator(generation.specType()); return generator.generateKeyPair((AlgorithmKeySpec) generation.defaultSpec()); } private static KeyringImportRegistry.PersistentMapping mapping( - List mappings, String algorithmId, - KeyringStore.Kind kind) { + List mappings, String algorithmId, KeyringStore.Kind kind) { return mappings.stream() - .filter(candidate -> candidate.algorithmId().equals(algorithmId) - && candidate.kind() == kind) - .findFirst() - .orElseThrow(); + .filter(candidate -> candidate.algorithmId().equals(algorithmId) && candidate.kind() == kind) + .findFirst().orElseThrow(); } - private static void roundTrip(KeyringImportRegistry.PersistentMapping mapping, - Key source) throws Exception { + private static void roundTrip(KeyringImportRegistry.PersistentMapping mapping, Key source) throws Exception { byte[] encoded = source.getEncoded(); Key imported = null; byte[] reconstructed = null; try { - imported = KeyringImportRegistry.importKey(mapping.algorithmId(), mapping.kind(), - mapping.encoding(), mapping.hmacVariant(), encoded); + imported = KeyringImportRegistry.importKey(mapping.algorithmId(), mapping.kind(), mapping.encoding(), + mapping.hmacVariant(), encoded); reconstructed = imported.getEncoded(); assertArrayEquals(encoded, reconstructed); } finally { diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringNonceReservationTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringNonceReservationTest.java index 9da5ed4..0756860 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringNonceReservationTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringNonceReservationTest.java @@ -27,8 +27,7 @@ import org.junit.jupiter.api.io.TempDir; class KeyringNonceReservationTest { private static final int MAGIC_BYTES = 8; private static final int VERSION_OFFSET = MAGIC_BYTES; - private static final int HIGH_WATER_OFFSET = - MAGIC_BYTES + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES; + private static final int HIGH_WATER_OFFSET = MAGIC_BYTES + Integer.BYTES + KeyringStore.UUID_BYTES + Integer.BYTES; private static final int TAG_BYTES = 32; private static final int CURRENT_SIDECAR_VERSION = 2; private static final char[] PASSWORD = { 'n', 'o', 'n', 'c', 'e' }; @@ -47,8 +46,7 @@ class KeyringNonceReservationTest { for (int index = 0; index < storeId.length; index++) { storeId[index] = (byte) (0xa0 + index); } - byte[] expected = HexFormat.of().parseHex( - "54e0e054749745a3ef5e2cc5a5c16bafed6f39df9daa4ff412bac74d56bd27b9"); + byte[] expected = HexFormat.of().parseHex("54e0e054749745a3ef5e2cc5a5c16bafed6f39df9daa4ff412bac74d56bd27b9"); byte[] first = null; byte[] second = null; byte[] changedMaster = null; @@ -88,13 +86,12 @@ class KeyringNonceReservationTest { byte[] retainedMacKey; byte[] macKeyCopy; try (KeyringPassword password = password()) { - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom()); + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom()); retainedMacKey = field(store, "nonceReservationMacKey"); macKeyCopy = retainedMacKey.clone(); assertEquals(CURRENT_SIDECAR_VERSION, - ByteBuffer.wrap(Files.readAllBytes(sidecar(path)), - VERSION_OFFSET, Integer.BYTES).getInt()); + ByteBuffer.wrap(Files.readAllBytes(sidecar(path)), VERSION_OFFSET, Integer.BYTES).getInt()); store.close(); store.close(); assertTrue(allZero(retainedMacKey)); @@ -109,8 +106,7 @@ class KeyringNonceReservationTest { wipe(reservation); wipe(macKeyCopy); } - try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) { assertTrue(reopened.aliases().isEmpty()); } ok(); @@ -153,14 +149,13 @@ class KeyringNonceReservationTest { Arrays.fill(material, (byte) 0x4a); try { try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom())) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom())) { assertEquals(1L, longField(store, "nonceHighWater")); Files.move(path, saved); Files.createDirectory(path); assertThrows(IOException.class, - () -> store.putSecret("failed", "AES", - new SecretKeySpec(material, "AES"))); + () -> store.putSecret("failed", "AES", new SecretKeySpec(material, "AES"))); assertEquals(3L, sidecarHighWater(sidecar(path))); assertEquals(3L, longField(store, "nonceHighWater")); Files.delete(path); @@ -168,17 +163,15 @@ class KeyringNonceReservationTest { } try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password, - KeyringProtection.standard(), deterministicRandom(51))) { + KeyringStore reopened = KeyringStore.open(path, password, KeyringProtection.standard(), + deterministicRandom(51))) { assertEquals(3L, longField(reopened, "nonceHighWater")); assertTrue(reopened.aliases().isEmpty()); - reopened.putSecret("accepted", "AES", - new SecretKeySpec(material, "AES")); + reopened.putSecret("accepted", "AES", new SecretKeySpec(material, "AES")); assertEquals(5L, longField(reopened, "nonceHighWater")); assertEquals(5L, sidecarHighWater(sidecar(path))); } - try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) { assertEquals(5L, longField(reopened, "nonceHighWater")); assertArrayEquals(material, reopened.getSecret("accepted").getEncoded()); } @@ -198,24 +191,21 @@ class KeyringNonceReservationTest { Arrays.fill(material, (byte) 0x35); RecordingRandom random = new RecordingRandom(1); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), random)) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), random)) { byte[] mainBefore = Files.readAllBytes(path); try { assertEquals(List.of(32, 32, 16, 11, 3), random.requests()); Files.move(reservation, saved); Files.createDirectory(reservation); assertThrows(IOException.class, - () -> store.putSecret("failed", "AES", - new SecretKeySpec(material, "AES"))); + () -> store.putSecret("failed", "AES", new SecretKeySpec(material, "AES"))); assertEquals(1L, longField(store, "nonceHighWater")); assertArrayEquals(mainBefore, Files.readAllBytes(path)); assertEquals(List.of(32, 32, 16, 11, 3, 16), random.requests()); Files.delete(reservation); Files.move(saved, reservation); - store.putSecret("accepted", "AES", - new SecretKeySpec(material, "AES")); + store.putSecret("accepted", "AES", new SecretKeySpec(material, "AES")); assertEquals(3L, longField(store, "nonceHighWater")); assertEquals(List.of(32, 32, 16, 11, 3, 16, 16), random.requests()); } finally { @@ -237,8 +227,7 @@ class KeyringNonceReservationTest { Arrays.fill(second, (byte) 0x22); RecordingRandom random = new RecordingRandom(7); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), random)) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), random)) { assertEquals(1L, longField(store, "nonceHighWater")); assertEquals(List.of(32, 32, 16, 11, 3), random.requests()); store.putSecret("one", "AES", new SecretKeySpec(first, "AES")); @@ -261,8 +250,8 @@ class KeyringNonceReservationTest { byte[] image; byte[] master; try (KeyringPassword password = password()) { - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom()); + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom()); image = Files.readAllBytes(sidecar(path)); master = field(store, "masterKey").clone(); store.close(); @@ -304,14 +293,13 @@ class KeyringNonceReservationTest { } } - private Path sidecarWithVersion(String file, int version, boolean useMasterKey) - throws Exception { + private Path sidecarWithVersion(String file, int version, boolean useMasterKey) throws Exception { Path path = temporaryDirectory.resolve(file); byte[] image; byte[] key; try (KeyringPassword password = password()) { - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom()); + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom()); image = Files.readAllBytes(sidecar(path)); key = field(store, useMasterKey ? "masterKey" : "nonceReservationMacKey").clone(); store.close(); @@ -343,8 +331,7 @@ class KeyringNonceReservationTest { private Path sidecarWithTrailingByte(String file) throws Exception { Path path = createAndClose(file); - Files.write(sidecar(path), new byte[] { 0 }, - java.nio.file.StandardOpenOption.APPEND); + Files.write(sidecar(path), new byte[] { 0 }, java.nio.file.StandardOpenOption.APPEND); return path; } @@ -356,11 +343,9 @@ class KeyringNonceReservationTest { byte[] mainBefore = Files.readAllBytes(path); byte[] sidecarBefore = Files.readAllBytes(sidecar(path)); try (KeyringPassword password = password()) { - KeyringException exception = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code()); - assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), - exception.getMessage()); + assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID.name(), exception.getMessage()); assertArrayEquals(mainBefore, Files.readAllBytes(path)); assertArrayEquals(sidecarBefore, Files.readAllBytes(sidecar(path))); } finally { @@ -376,8 +361,8 @@ class KeyringNonceReservationTest { private Path createAndClose(String file, int randomSeed) throws Exception { Path path = temporaryDirectory.resolve(file); try (KeyringPassword password = password(); - KeyringStore ignored = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom(randomSeed))) { + KeyringStore ignored = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom(randomSeed))) { // Creation writes the initial durable reservation. } return path; @@ -454,8 +439,7 @@ class KeyringNonceReservationTest { } private static int indexOf(byte[] haystack, byte[] needle) { - outer: - for (int index = 0; index <= haystack.length - needle.length; index++) { + outer: for (int index = 0; index <= haystack.length - needle.length; index++) { for (int offset = 0; offset < needle.length; offset++) { if (haystack[index + offset] != needle[offset]) { continue outer; diff --git a/lib/src/test/java/zeroecho/core/storage/KeyringStoreTest.java b/lib/src/test/java/zeroecho/core/storage/KeyringStoreTest.java index c3f947f..c35be30 100644 --- a/lib/src/test/java/zeroecho/core/storage/KeyringStoreTest.java +++ b/lib/src/test/java/zeroecho/core/storage/KeyringStoreTest.java @@ -39,8 +39,8 @@ class KeyringStoreTest { KeyPair pair = generator.generateKeyPair(); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom())) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom())) { store.putPublic("rsa", "RSA", pair.getPublic()); store.putPrivate("rsa", "RSA", pair.getPrivate()); store.putSecret("aes", "AES", aes); @@ -64,13 +64,11 @@ class KeyringStoreTest { void wrongPasswordAndCorruptionFailUniformly() throws Exception { start("wrongPasswordAndCorruptionFailUniformly"); Path path = temporaryDirectory.resolve("wrong.zek"); - try (KeyringPassword password = password(); - KeyringStore ignored = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore ignored = KeyringStore.create(path, password)) { // empty current-format store } try (KeyringPassword wrong = new KeyringPassword(new char[] { 'w', 'r', 'o', 'n', 'g' })) { - KeyringException exception = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, wrong)); + KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, wrong)); assertEquals(KeyringException.Code.KEYRING_UNLOCK_FAILED, exception.code()); } byte[] bytes = Files.readAllBytes(path); @@ -78,8 +76,7 @@ class KeyringStoreTest { Files.write(path, bytes); Arrays.fill(bytes, (byte) 0); try (KeyringPassword password = password()) { - KeyringException exception = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code()); } ok(); @@ -90,13 +87,11 @@ class KeyringStoreTest { start("plaintextV1AndDuplicateOwnerAreRejected"); Path old = temporaryDirectory.resolve("old.txt"); Files.writeString(old, "# KeyringStore v1\n", StandardCharsets.UTF_8); - Files.setPosixFilePermissions(old, java.util.Set.of( - java.nio.file.attribute.PosixFilePermission.OWNER_READ, + Files.setPosixFilePermissions(old, java.util.Set.of(java.nio.file.attribute.PosixFilePermission.OWNER_READ, java.nio.file.attribute.PosixFilePermission.OWNER_WRITE)); try (KeyringPassword password = password()) { assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, - assertThrows(KeyringException.class, - () -> KeyringStore.open(old, password)).code()); + assertThrows(KeyringException.class, () -> KeyringStore.open(old, password)).code()); } Path path = temporaryDirectory.resolve("owned.zek"); @@ -104,12 +99,10 @@ class KeyringStoreTest { KeyringStore first = KeyringStore.create(path, password); KeyringPassword secondPassword = password()) { assertEquals(KeyringException.Code.KEYRING_ALREADY_OPEN, - assertThrows(KeyringException.class, - () -> KeyringStore.open(path, secondPassword)).code()); + assertThrows(KeyringException.class, () -> KeyringStore.open(path, secondPassword)).code()); assertTrue(first.aliases().isEmpty()); } - try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(path, password)) { + try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(path, password)) { assertTrue(reopened.aliases().isEmpty()); } ok(); @@ -134,8 +127,7 @@ class KeyringStoreTest { void trailingDataAndNonExportableKeysAreRejected() throws Exception { start("trailingDataAndNonExportableKeysAreRejected"); Path path = temporaryDirectory.resolve("strict.zek"); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) { SecretKey nonExportable = new SecretKey() { private static final long serialVersionUID = 1L; @@ -162,8 +154,7 @@ class KeyringStoreTest { Files.write(path, new byte[] { 1 }, java.nio.file.StandardOpenOption.APPEND); try (KeyringPassword password = password()) { - KeyringException exception = assertThrows(KeyringException.class, - () -> KeyringStore.open(path, password)); + KeyringException exception = assertThrows(KeyringException.class, () -> KeyringStore.open(path, password)); assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, exception.code()); } ok(); @@ -175,8 +166,8 @@ class KeyringStoreTest { Path path = temporaryDirectory.resolve("hmac.zek"); List accepted = List.of("HmacSHA256", "HmacSHA384", "HmacSHA512"); try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password, - KeyringProtection.standard(), deterministicRandom())) { + KeyringStore store = KeyringStore.create(path, password, KeyringProtection.standard(), + deterministicRandom())) { for (String variant : accepted) { byte[] material = new byte[64]; Arrays.fill(material, (byte) variant.length()); @@ -190,23 +181,19 @@ class KeyringStoreTest { } byte[] before = Files.readAllBytes(path); try { - for (String rejected : List.of("HmacMD5", "HmacSHA1", "HmacSHA224", - "hmacsha256", "HmacSha384", " HmacSHA512", "HmacSHA512 ", - "BC:HmacSHA256", "", "X".repeat(4097))) { + for (String rejected : List.of("HmacMD5", "HmacSHA1", "HmacSHA224", "hmacsha256", "HmacSha384", + " HmacSHA512", "HmacSHA512 ", "BC:HmacSHA256", "", "X".repeat(4097))) { SecretKey key = controlledSecret(rejected, new byte[32]); KeyringException exception = assertThrows(KeyringException.class, () -> store.putSecret("rejected", "HMAC", key)); - assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID, - exception.code()); - assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID.name(), - exception.getMessage()); + assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID, exception.code()); + assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID.name(), exception.getMessage()); assertArrayEquals(before, Files.readAllBytes(path)); assertFalse(store.contains("rejected")); } SecretKey mismatched = controlledSecret("HmacSHA256", new byte[32]); assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE, - assertThrows(KeyringException.class, - () -> store.putSecret("wrong", "AES", mismatched)).code()); + assertThrows(KeyringException.class, () -> store.putSecret("wrong", "AES", mismatched)).code()); assertArrayEquals(before, Files.readAllBytes(path)); } finally { Arrays.fill(before, (byte) 0); @@ -219,19 +206,16 @@ class KeyringStoreTest { void providerDraftAndUnknownHmacVariantAreRejectedStructurally() throws Exception { start("providerDraftAndUnknownHmacVariantAreRejectedStructurally"); assertEquals(KeyringException.Code.KEYRING_IMPORT_METADATA_INVALID, - assertThrows(KeyringException.class, - () -> KeyringImportRegistry.HmacVariant.fromCode(99)).code()); + assertThrows(KeyringException.class, () -> KeyringImportRegistry.HmacVariant.fromCode(99)).code()); byte[] staleDraft = staleProviderEntryPlaintext(); try { - java.lang.reflect.Method decoder = KeyringStore.class.getDeclaredMethod( - "decodeEntryPlaintext", byte[].class); + java.lang.reflect.Method decoder = KeyringStore.class.getDeclaredMethod("decodeEntryPlaintext", + byte[].class); decoder.setAccessible(true); - java.lang.reflect.InvocationTargetException failure = - assertThrows(java.lang.reflect.InvocationTargetException.class, - () -> decoder.invoke(null, (Object) staleDraft)); + java.lang.reflect.InvocationTargetException failure = assertThrows( + java.lang.reflect.InvocationTargetException.class, () -> decoder.invoke(null, (Object) staleDraft)); assertTrue(failure.getCause() instanceof KeyringException); - assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, - ((KeyringException) failure.getCause()).code()); + assertEquals(KeyringException.Code.KEYRING_FORMAT_INVALID, ((KeyringException) failure.getCause()).code()); } finally { Arrays.fill(staleDraft, (byte) 0); } @@ -242,15 +226,13 @@ class KeyringStoreTest { void providerBoundOrNoncanonicalKeysFailBeforeMutation() throws Exception { start("providerBoundOrNoncanonicalKeysFailBeforeMutation"); Path path = temporaryDirectory.resolve("provider-bound.zek"); - try (KeyringPassword password = password(); - KeyringStore store = KeyringStore.create(path, password)) { + try (KeyringPassword password = password(); KeyringStore store = KeyringStore.create(path, password)) { byte[] before = Files.readAllBytes(path); try { SecretKey unsupported = controlledSecret("ProviderAES", new byte[32]); KeyringException failure = assertThrows(KeyringException.class, () -> store.putSecret("bad", "AES", unsupported)); - assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE, - failure.code()); + assertEquals(KeyringException.Code.KEYRING_KEY_NOT_CANONICALIZABLE, failure.code()); assertArrayEquals(before, Files.readAllBytes(path)); assertFalse(store.contains("bad")); } finally { @@ -310,8 +292,7 @@ class KeyringStoreTest { return bytes.toByteArray(); } - private static void writeString(java.io.DataOutputStream out, String value) - throws java.io.IOException { + private static void writeString(java.io.DataOutputStream out, String value) throws java.io.IOException { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); try { out.writeInt(bytes.length); @@ -322,8 +303,7 @@ class KeyringStoreTest { } private static int indexOf(byte[] haystack, byte[] needle) { - outer: - for (int index = 0; index <= haystack.length - needle.length; index++) { + outer: for (int index = 0; index <= haystack.length - needle.length; index++) { for (int offset = 0; offset < needle.length; offset++) { if (haystack[index + offset] != needle[offset]) { continue outer; diff --git a/lib/src/test/java/zeroecho/sdk/ZeroEchoSessionDestroyKeyTest.java b/lib/src/test/java/zeroecho/sdk/ZeroEchoSessionDestroyKeyTest.java index c85a3da..7d09f61 100644 --- a/lib/src/test/java/zeroecho/sdk/ZeroEchoSessionDestroyKeyTest.java +++ b/lib/src/test/java/zeroecho/sdk/ZeroEchoSessionDestroyKeyTest.java @@ -81,10 +81,7 @@ class ZeroEchoSessionDestroyKeyTest { } private enum Behavior { - SUCCESS, - FAIL_CHECKED, - FAIL_RUNTIME, - NO_TRANSITION + SUCCESS, FAIL_CHECKED, FAIL_RUNTIME, NO_TRANSITION } private static final class TestKey implements Key, Destroyable { diff --git a/lib/src/test/java/zeroecho/sdk/builders/HybridKexBuilderTest.java b/lib/src/test/java/zeroecho/sdk/builders/HybridKexBuilderTest.java index 8cb8f94..c6ac45a 100644 --- a/lib/src/test/java/zeroecho/sdk/builders/HybridKexBuilderTest.java +++ b/lib/src/test/java/zeroecho/sdk/builders/HybridKexBuilderTest.java @@ -91,22 +91,26 @@ class HybridKexBuilderTest { HybridKexTranscript transcript = new HybridKexTranscript().addUtf8("suite", "X25519+ML-KEM-768").addUtf8("role", "builder-test"); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexContext alice = null; HybridKexContext bob = null; try { - alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript).classicAgreement() - .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()) + alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript) + .classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()) .peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()) .buildInitiator(); - bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript).classicAgreement().algorithm("Xdh") - .spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic()) - .pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder(); + bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcript) + .classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()) + .peerPublic(aliceClassic.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()) + .buildResponder(); byte[] aliceMessage = alice.getPeerMessage(); System.out.println("...aliceMessage(" + lens(aliceMessage) + ")=" + hex(aliceMessage)); @@ -137,21 +141,24 @@ class HybridKexBuilderTest { HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexContext alice = null; HybridKexContext bob = null; try { - alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh") - .spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem().algorithm("ML-KEM") - .peerPublic(bobPqc.getPublic()).buildInitiator(); + alice = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage() + .algorithm("Xdh").spec(XdhSpec.X25519).keyPair(new KeyPairKey(aliceClassic)).pqcKem() + .algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator(); - bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519) - .keyPair(new KeyPairKey(bobClassic)).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()) - .buildResponder(); + bob = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage() + .algorithm("Xdh").spec(XdhSpec.X25519).keyPair(new KeyPairKey(bobClassic)).pqcKem() + .algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder(); byte[] messageA = alice.getPeerMessage(); System.out.println("...messageA(" + lens(messageA) + ")=" + hex(messageA)); @@ -183,14 +190,17 @@ class HybridKexBuilderTest { void buildInitiatorWithoutProfileFails() throws Exception { System.out.println("buildInitiatorWithoutProfileFails"); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) - .privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem() - .algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).classicAgreement().algorithm("Xdh") + .spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()) + .pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator(); }); System.out.println("...exception=" + exception.getMessage()); @@ -204,11 +214,12 @@ class HybridKexBuilderTest { System.out.println("buildInitiatorWithoutClassicModeFails"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()) - .buildInitiator(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).pqcKem().algorithm("ML-KEM") + .peerPublic(bobPqc.getPublic()).buildInitiator(); }); System.out.println("...exception=" + exception.getMessage()); @@ -222,13 +233,15 @@ class HybridKexBuilderTest { System.out.println("buildInitiatorClassicAgreementWithoutPeerPublicFails"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) - .privateKey(aliceClassic.getPrivate()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()) - .buildInitiator(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement() + .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).pqcKem() + .algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator(); }); System.out.println("...exception=" + exception.getMessage()); @@ -242,11 +255,13 @@ class HybridKexBuilderTest { System.out.println("buildResponderPairMessageWithoutKeyPairFails"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage().algorithm("Xdh").spec(XdhSpec.X25519) - .pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()).buildResponder(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicPairMessage() + .algorithm("Xdh").spec(XdhSpec.X25519).pqcKem().algorithm("ML-KEM").privateKey(bobPqc.getPrivate()) + .buildResponder(); }); System.out.println("...exception=" + exception.getMessage()); @@ -260,13 +275,15 @@ class HybridKexBuilderTest { System.out.println("buildInitiatorWithoutPqcPeerPublicFails"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) - .privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()).pqcKem() - .algorithm("ML-KEM").buildInitiator(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement() + .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()) + .peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").buildInitiator(); }); System.out.println("...exception=" + exception.getMessage()); @@ -280,13 +297,15 @@ class HybridKexBuilderTest { System.out.println("buildResponderWithoutPqcPrivateFails"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) - .privateKey(bobClassic.getPrivate()).peerPublic(aliceClassic.getPublic()).pqcKem() - .algorithm("ML-KEM").buildResponder(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).classicAgreement() + .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassic.getPrivate()) + .peerPublic(aliceClassic.getPublic()).pqcKem().algorithm("ML-KEM").buildResponder(); }); System.out.println("...exception=" + exception.getMessage()); @@ -302,14 +321,18 @@ class HybridKexBuilderTest { HybridKexProfile profile = HybridKexProfile.defaultProfile(16); HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32); - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { - HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).policy(policy).classicAgreement().algorithm("Xdh") - .spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()).peerPublic(bobClassic.getPublic()) - .pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()).buildInitiator(); + HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).policy(policy) + .classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()) + .peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()) + .buildInitiator(); }); System.out.println("...exception=" + exception.getMessage()); @@ -328,21 +351,18 @@ class HybridKexBuilderTest { closedContexts.incrementAndGet(); } }; - ZeroEchoSession session = new ZeroEchoSession() - .withAuditListener(listener) - .withAuditMode(AuditMode.WRAP); + ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP); HybridKexProfile profile = HybridKexProfile.defaultProfile(16); HybridKexPolicy policy = new HybridKexPolicy(0, 0, 32); KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", - KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); assertThrows(IllegalArgumentException.class, () -> HybridKexBuilder.builder(session).profile(profile).policy(policy).classicAgreement() .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassic.getPrivate()) - .peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM") - .peerPublic(bobPqc.getPublic()).buildInitiator()); + .peerPublic(bobClassic.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqc.getPublic()) + .buildInitiator()); assertEquals(2, closedContexts.get()); System.out.println("...closedContexts=" + closedContexts.get()); @@ -362,21 +382,18 @@ class HybridKexBuilderTest { ZeroEchoSession keySession = new ZeroEchoSession(); KeyPair aliceClassic = keySession.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); KeyPair bobClassic = keySession.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = keySession.keyBuilders().asymmetric().generateKeyPair("ML-KEM", - KyberKeyGenSpec.kyber768()); - ZeroEchoSession operationSession = new ZeroEchoSession() - .withAuditListener(listener) - .withAuditMode(AuditMode.WRAP) - .withPolicy((id, role, key, spec) -> { + KeyPair bobPqc = keySession.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + ZeroEchoSession operationSession = new ZeroEchoSession().withAuditListener(listener) + .withAuditMode(AuditMode.WRAP).withPolicy((id, role, key, spec) -> { if ("ML-KEM".equals(id)) { throw new IllegalArgumentException("controlled PQ policy denial"); } }); assertThrows(IllegalArgumentException.class, - () -> HybridKexContexts.initiator(operationSession, HybridKexProfile.defaultProfile(32), - "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, - "ML-KEM", bobPqc.getPublic(), null)); + () -> HybridKexContexts.initiator(operationSession, HybridKexProfile.defaultProfile(32), "Xdh", + aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), + null)); assertEquals(1, closedContexts.get()); System.out.println("...closedContexts=" + closedContexts.get()); @@ -390,21 +407,17 @@ class HybridKexBuilderTest { AuditListener listener = new AuditListener() { @Override public void onContextCreatedMeta(String contextId, String algorithmId, String provider, - zeroecho.core.KeyUsage role, String keyFingerprint, - java.util.Map specMeta) { + zeroecho.core.KeyUsage role, String keyFingerprint, java.util.Map specMeta) { createdContexts.incrementAndGet(); } }; - ZeroEchoSession session = new ZeroEchoSession() - .withAuditListener(listener) - .withAuditMode(AuditMode.WRAP); + ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP); KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); assertThrows(NullPointerException.class, - () -> HybridKexContexts.initiator(session, HybridKexProfile.defaultProfile(32), - "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, - "ML-KEM", null, null)); + () -> HybridKexContexts.initiator(session, HybridKexProfile.defaultProfile(32), "Xdh", + aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", null, null)); assertEquals(0, createdContexts.get()); System.out.println("...createdContexts=0"); @@ -416,9 +429,12 @@ class HybridKexBuilderTest { System.out.println("switchingClassicModeClearsConflictingStateAndBuildsPairMessage"); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair agreementKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair pairMessageKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair agreementKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair pairMessageKeyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric() + .generateKeyPair("Xdh", XdhSpec.X25519); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexContext context = null; try { @@ -449,13 +465,19 @@ class HybridKexBuilderTest { HybridKexProfile profile = HybridKexProfile.defaultProfile(32); - KeyPair aliceClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqcA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassicA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqcA = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); - KeyPair aliceClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobPqcB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair aliceClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassicB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobPqcB = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexTranscript transcriptA = new HybridKexTranscript().addUtf8("context", "A"); HybridKexTranscript transcriptB = new HybridKexTranscript().addUtf8("context", "B"); @@ -469,13 +491,13 @@ class HybridKexBuilderTest { HybridKexContext bobB = null; try { - aliceA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA).classicAgreement() - .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicA.getPrivate()) - .peerPublic(bobClassicA.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcA.getPublic()) - .buildInitiator(); + aliceA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile) + .transcript(transcriptA).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) + .privateKey(aliceClassicA.getPrivate()).peerPublic(bobClassicA.getPublic()).pqcKem() + .algorithm("ML-KEM").peerPublic(bobPqcA.getPublic()).buildInitiator(); - bobA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA).classicAgreement() - .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate()) + bobA = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptA) + .classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicA.getPrivate()) .peerPublic(aliceClassicA.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcA.getPrivate()) .buildResponder(); @@ -486,13 +508,13 @@ class HybridKexBuilderTest { System.out.println("...responderA=" + hex(responderA)); assertArrayEquals(secretA, responderA); - aliceB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB).classicAgreement() - .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(aliceClassicB.getPrivate()) - .peerPublic(bobClassicB.getPublic()).pqcKem().algorithm("ML-KEM").peerPublic(bobPqcB.getPublic()) - .buildInitiator(); + aliceB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile) + .transcript(transcriptB).classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519) + .privateKey(aliceClassicB.getPrivate()).peerPublic(bobClassicB.getPublic()).pqcKem() + .algorithm("ML-KEM").peerPublic(bobPqcB.getPublic()).buildInitiator(); - bobB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB).classicAgreement() - .algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate()) + bobB = HybridKexBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).profile(profile).transcript(transcriptB) + .classicAgreement().algorithm("Xdh").spec(XdhSpec.X25519).privateKey(bobClassicB.getPrivate()) .peerPublic(aliceClassicB.getPublic()).pqcKem().algorithm("ML-KEM").privateKey(bobPqcB.getPrivate()) .buildResponder(); diff --git a/lib/src/test/java/zeroecho/sdk/builders/TagTrailerDataContentBuilderTest.java b/lib/src/test/java/zeroecho/sdk/builders/TagTrailerDataContentBuilderTest.java index 9b8c296..b7d9471 100644 --- a/lib/src/test/java/zeroecho/sdk/builders/TagTrailerDataContentBuilderTest.java +++ b/lib/src/test/java/zeroecho/sdk/builders/TagTrailerDataContentBuilderTest.java @@ -179,22 +179,27 @@ public class TagTrailerDataContentBuilderTest { .generateSecret(AesKeyGenSpec.aes256()); // Ed25519 keys (JCA) - KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); + KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", + Ed25519KeyGenSpec.defaultSpec()); - TagEngine tagEnc = TagEngineBuilder.ed25519Sign(new zeroecho.sdk.ZeroEchoSession(), ed.getPrivate()).get(); - TagEngine tagDec = TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()).get(); + TagEngine tagEnc = TagEngineBuilder.ed25519Sign(new zeroecho.sdk.ZeroEchoSession(), ed.getPrivate()) + .get(); + TagEngine tagDec = TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic()) + .get(); // ENCRYPT: body -> [body||signature] -> AES-GCM DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build(); + .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder + .builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .build(); byte[] ct = readAll(enc.getStream()); System.out.println("...ct=" + ct.length + " bytes"); // DECRYPT: AES-GCM -> strip trailer -> verify Ed25519 at EOF DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build(); byte[] pt = readAll(dec.getStream()); @@ -217,23 +222,28 @@ public class TagTrailerDataContentBuilderTest { // SPHINCS+ key pair via registry (uses default param set from // SphincsPlusKeyGenSpec) - KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec()); + KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", + SphincsPlusKeyGenSpec.defaultSpec()); // Tag engines (SPHINCS+) - TagEngine tagEnc = TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate()).get(); - TagEngine tagDec = TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()).get(); + TagEngine tagEnc = TagEngineBuilder + .sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate()).get(); + TagEngine tagDec = TagEngineBuilder + .sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic()).get(); // ENCRYPT: body -> [body||spxSig] -> AES-GCM DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build(); + .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder + .builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .build(); byte[] ct = readAll(enc.getStream()); System.out.println("...ct=" + ct.length + " bytes"); // DECRYPT: AES-GCM -> strip trailer -> verify SPHINCS+ at EOF DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build(); byte[] pt = readAll(dec.getStream()); @@ -255,24 +265,29 @@ public class TagTrailerDataContentBuilderTest { .generateSecret(AesKeyGenSpec.aes256()); // RSA-2048 keys (use registry for convenience) - KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); // Tag engines (SHA-256, saltLen=32) RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32); - TagEngine tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); - TagEngine tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); + TagEngine tagEnc = TagEngineBuilder + .rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); + TagEngine tagDec = TagEngineBuilder + .rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); // ENCRYPT: body -> [body||pssSig] -> AES-GCM DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build(); + .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder + .builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .build(); byte[] ct = readAll(enc.getStream()); System.out.println("...ct=" + ct.length + " bytes"); // DECRYPT: AES-GCM -> strip trailer -> verify PSS at EOF DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build(); byte[] pt = readAll(dec.getStream()); @@ -297,9 +312,12 @@ public class TagTrailerDataContentBuilderTest { // ENCRYPT: [source] -> [tag trailer] -> [aes gcm] DataContent encChain = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) // writes IV/AAD headers - // into stream + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192)) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) // writes IV/AAD headers + // into stream .build(); byte[] ciphertext = readAll(encChain.getStream()); @@ -307,10 +325,12 @@ public class TagTrailerDataContentBuilderTest { // DECRYPT: [source(ct)] -> [aes gcm] -> [tag trailer verify] DataContent decChain = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) // reads IV/AAD headers - // back - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) // reads IV/AAD headers + // back + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build(); byte[] plain = readAll(decChain.getStream()); @@ -330,12 +350,16 @@ public class TagTrailerDataContentBuilderTest { msg = Arrays.copyOf(msg, SIZE); // pad deterministic length for the test System.out.println("...msg=" + msg.length + " bytes"); - KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair kp = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); // ENCRYPT: [source] -> [tag trailer] -> [rsa/oaep] DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)) - .add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256).withPublicKey(kp.getPublic())) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192)) + .add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256) + .withPublicKey(kp.getPublic())) .build(); byte[] ct = readAll(enc.getStream()); @@ -343,9 +367,11 @@ public class TagTrailerDataContentBuilderTest { // DECRYPT: [source(ct)] -> [rsa/oaep] -> [tag verify] DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct)) - .add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256).withPrivateKey(kp.getPrivate())) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(RsaEncDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).oaep(RsaEncSpec.Hash.SHA256) + .withPrivateKey(kp.getPrivate())) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build(); byte[] pt = readAll(dec.getStream()); @@ -379,17 +405,21 @@ public class TagTrailerDataContentBuilderTest { System.out.println("...msg=" + msg.length + " bytes"); // ENCRYPT: [source] -> [tag trailer] -> [KEM envelope with AES/GCM payload] - AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) // 128-bit tag + AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) // 128-bit + // tag .withHeader(); // carry IV etc. DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192)) - .add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPublic(kemKeys.getPublic()).derivedKeyBytes(32) // AES-256 - // key - // derived - // from - // KEM - // secret + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192)) + .add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId) + .recipientPublic(kemKeys.getPublic()).derivedKeyBytes(32) // AES-256 + // key + // derived + // from + // KEM + // secret .hkdfSha256("KEM-tag-demo".getBytes(java.nio.charset.StandardCharsets.US_ASCII)) .withAes(aesEnc)) .build(); @@ -398,15 +428,17 @@ public class TagTrailerDataContentBuilderTest { System.out.println("...envelope=" + envelope.length + " bytes"); // DECRYPT: [source(envelope)] -> [KEM] -> [tag verify] - AesDataContentBuilder aesDec = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + AesDataContentBuilder aesDec = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) + .withHeader(); DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(envelope)) - .add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPrivate(kemKeys.getPrivate()) - .derivedKeyBytes(32) + .add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId) + .recipientPrivate(kemKeys.getPrivate()).derivedKeyBytes(32) .hkdfSha256("KEM-tag-demo".getBytes(java.nio.charset.StandardCharsets.US_ASCII)) .withAes(aesDec)) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build(); byte[] pt = readAll(dec.getStream()); @@ -430,26 +462,34 @@ public class TagTrailerDataContentBuilderTest { // --- recipients --- // RSA - KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); // ML-KEM (Kyber768 as a good mid-level) - KeyPair kem = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair kem = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); // --- symmetric payload (AES-256/GCM, tag 128) --- // IV length is handled internally (12 bytes for GCM) and persisted via header. - AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); // write - // IV/tagBits/AAD-hash - // header for decrypt - // side + AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) + .withHeader(); // write + // IV/tagBits/AAD-hash + // header for decrypt + // side // --- tag trailer (SHA-256 digest as a trailer) --- TagTrailerDataContentBuilder tagEnc = new TagTrailerDataContentBuilder<>( TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); - KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kem.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); + KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, + kem.getPublic()); // --- envelope (ENCRYPT) with 3 recipients --- - MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesEnc) + MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesEnc) // .addRsaOaepRecipient(rsa.getPublic()) // RSA-OAEP with SHA-256 MGF1 // .addKemRecipient("ML-KEM", kem.getPublic(), 32 /* kekBytes */, 16 /* // hkdfSaltLen */) @@ -467,36 +507,48 @@ public class TagTrailerDataContentBuilderTest { // -------------- Decrypt three ways on the same ciphertext -------------- // a) by RSA private key - AesDataContentBuilder aesDecRsa = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); // read header to - // recover - // IV/tagBits - MultiRecipientDataSourceBuilder envDecRsa = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecRsa) - .unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); + AesDataContentBuilder aesDecRsa = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) + .withHeader(); // read header to + // recover + // IV/tagBits + MultiRecipientDataSourceBuilder envDecRsa = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesDecRsa).unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); byte[] ptRsa = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecRsa) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build().getStream()); System.out.println("...decrypted(RSA)=" + ptRsa.length); assertArrayEquals(msg, ptRsa, "RSA path failed to recover the original"); // b) by KEM private key - AesDataContentBuilder aesDecKem = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); - MultiRecipientDataSourceBuilder envDecKem = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecKem) - .unlockWith(new UnlockMaterial.Private(kem.getPrivate())); + AesDataContentBuilder aesDecKem = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) + .withHeader(); + MultiRecipientDataSourceBuilder envDecKem = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesDecKem).unlockWith(new UnlockMaterial.Private(kem.getPrivate())); byte[] ptKem = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecKem) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build().getStream()); System.out.println("...decrypted(KEM)=" + ptKem.length); assertArrayEquals(msg, ptKem, "KEM path failed to recover the original"); // c) by password - AesDataContentBuilder aesDecPwd = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); - MultiRecipientDataSourceBuilder envDecPwd = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesDecPwd) - .unlockWith(new UnlockMaterial.Password(PASSWORD)); + AesDataContentBuilder aesDecPwd = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128) + .withHeader(); + MultiRecipientDataSourceBuilder envDecPwd = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesDecPwd).unlockWith(new UnlockMaterial.Password(PASSWORD)); byte[] ptPwd = readAll(DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(encrypted)).add(envDecPwd) - .add(new TagTrailerDataContentBuilder<>(TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192) - .throwOnMismatch()) + .add(new TagTrailerDataContentBuilder<>( + TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())) + .bufferSize(8192).throwOnMismatch()) .build().getStream()); System.out.println("...decrypted(PASSWORD)=" + ptPwd.length); assertArrayEquals(msg, ptPwd, "Password path failed to recover the original"); @@ -516,10 +568,12 @@ public class TagTrailerDataContentBuilderTest { byte[] msg = random(SIZE); System.out.println("...input=" + msg.length); - KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); // AES-256/CBC with header so IV/params are serialized by the AES stage - AesDataContentBuilder aesCbc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + AesDataContentBuilder aesCbc = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5() + .withHeader(); TagTrailerDataContentBuilder tagEnc = new TagTrailerDataContentBuilder<>( TagEngineBuilder.digest(new zeroecho.sdk.ZeroEchoSession(), DigestSpec.sha256())).bufferSize(8192); @@ -529,10 +583,14 @@ public class TagTrailerDataContentBuilderTest { // explicit for clarity .throwOnMismatch(); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); // Envelope: recipient table (RSA-OAEP) + AES payload (CBC/PKCS7 with header) - MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc) + MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc) // CEK length for AES-256 .payloadKeyBytes(32) // .addRsaOaepRecipient(rsa.getPublic()); old API @@ -545,7 +603,9 @@ public class TagTrailerDataContentBuilderTest { byte[] encrypted = readAll(encTail.getStream()); System.out.println("...encrypted=" + encrypted.length); - MultiRecipientDataSourceBuilder envDec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + MultiRecipientDataSourceBuilder envDec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) .withAes(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5() // must match encrypt side .withHeader()) @@ -577,23 +637,28 @@ public class TagTrailerDataContentBuilderTest { .generateSecret(AesKeyGenSpec.aes256()); // ECDSA P-256 keys (via your unified ECDSA algorithm) - KeyPair ecdsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA", zeroecho.core.alg.ecdsa.EcdsaCurveSpec.P256); + KeyPair ecdsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ECDSA", + zeroecho.core.alg.ecdsa.EcdsaCurveSpec.P256); // Tag engines (ECDSA/P-256 using P1363 format, fixed 64-byte tag) - TagEngine tagEnc = TagEngineBuilder.ecdsaP256Sign(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPrivate()).get(); - TagEngine tagDec = TagEngineBuilder.ecdsaP256Verify(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPublic()).get(); + TagEngine tagEnc = TagEngineBuilder + .ecdsaP256Sign(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPrivate()).get(); + TagEngine tagDec = TagEngineBuilder + .ecdsaP256Verify(new zeroecho.sdk.ZeroEchoSession(), ecdsa.getPublic()).get(); // ENCRYPT: body -> [body||ecdsaSig] -> AES-GCM DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) - .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()).build(); + .add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192)).add(AesDataContentBuilder + .builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .build(); byte[] ct = readAll(enc.getStream()); System.out.println("...ct=" + ct.length + " bytes"); // DECRYPT: AES-GCM -> strip trailer -> verify ECDSA at EOF DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ct)) - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withKey(aesKey).modeGcm(128) + .withHeader()) .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()).build(); byte[] pt = readAll(dec.getStream()); diff --git a/lib/src/test/java/zeroecho/sdk/builders/alg/KemHybridRoundTripTest.java b/lib/src/test/java/zeroecho/sdk/builders/alg/KemHybridRoundTripTest.java index f1c8bd8..c36e203 100644 --- a/lib/src/test/java/zeroecho/sdk/builders/alg/KemHybridRoundTripTest.java +++ b/lib/src/test/java/zeroecho/sdk/builders/alg/KemHybridRoundTripTest.java @@ -243,25 +243,29 @@ class KemHybridRoundTripTest { } private static DataContent encryptStage(String kemId, KeyPair kp, String mode) { - KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPublic(kp.getPublic()) - .derivedKeyBytes(32); // AES-256 or ChaCha20 key + KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId) + .recipientPublic(kp.getPublic()).derivedKeyBytes(32); // AES-256 or ChaCha20 key switch (mode) { case "GCM": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader().withAad(AAD); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader().withAad(AAD); return kem.withAes(aes).build(true); } case "CBC": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); return kem.withAes(aes).build(true); } case "CTR": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr().withHeader(); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr() + .withHeader(); return kem.withAes(aes).build(true); } case "CHACHA20-POLY1305": { - ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withAad(AAD) // non-empty → AEAD - // variant + ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .withAad(AAD) // non-empty → AEAD + // variant .withHeader(); // carry nonce return kem.withChaCha(ch).build(true); } @@ -271,24 +275,28 @@ class KemHybridRoundTripTest { } private static DataContent decryptStage(String kemId, KeyPair kp, String mode) { - KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId).recipientPrivate(kp.getPrivate()) - .derivedKeyBytes(32); + KemDataContentBuilder kem = KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).kem(kemId) + .recipientPrivate(kp.getPrivate()).derivedKeyBytes(32); switch (mode) { case "GCM": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader().withAad(AAD); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader().withAad(AAD); return kem.withAes(aes).build(false); } case "CBC": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); return kem.withAes(aes).build(false); } case "CTR": { - AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr().withHeader(); + AesDataContentBuilder aes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCtr() + .withHeader(); return kem.withAes(aes).build(false); } case "CHACHA20-POLY1305": { - ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withAad(AAD).withHeader(); + ChaChaDataContentBuilder ch = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .withAad(AAD).withHeader(); return kem.withChaCha(ch).build(false); } default: diff --git a/lib/src/test/java/zeroecho/sdk/builders/alg/SessionBoundBuilderTest.java b/lib/src/test/java/zeroecho/sdk/builders/alg/SessionBoundBuilderTest.java index e27827e..b156ec1 100644 --- a/lib/src/test/java/zeroecho/sdk/builders/alg/SessionBoundBuilderTest.java +++ b/lib/src/test/java/zeroecho/sdk/builders/alg/SessionBoundBuilderTest.java @@ -42,15 +42,12 @@ class SessionBoundBuilderTest { void policyDenialOccursBeforeContextCreation() { System.out.println("policyDenialOccursBeforeContextCreation"); RecordingListener listener = new RecordingListener(); - ZeroEchoSession session = new ZeroEchoSession() - .withAuditListener(listener) + ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener) .withPolicy((id, role, key, spec) -> { throw new IllegalArgumentException("controlled policy denial"); }); - DataContent encryption = AesDataContentBuilder.builder(session) - .withKey(new SecretKeySpec(new byte[16], "AES")) - .modeGcm(128) - .build(true); + DataContent encryption = AesDataContentBuilder.builder(session).withKey(new SecretKeySpec(new byte[16], "AES")) + .modeGcm(128).build(true); encryption.setInput(new PlainBytes(new byte[] { 1 })); assertThrows(IllegalArgumentException.class, encryption::getStream); @@ -63,13 +60,9 @@ class SessionBoundBuilderTest { void wrappedContextUsesBuilderSessionAuditConfiguration() throws Exception { System.out.println("wrappedContextUsesBuilderSessionAuditConfiguration"); RecordingListener listener = new RecordingListener(); - ZeroEchoSession session = new ZeroEchoSession() - .withAuditListener(listener) - .withAuditMode(AuditMode.WRAP); - DataContent encryption = AesDataContentBuilder.builder(session) - .withKey(new SecretKeySpec(new byte[16], "AES")) - .modeGcm(128) - .build(true); + ZeroEchoSession session = new ZeroEchoSession().withAuditListener(listener).withAuditMode(AuditMode.WRAP); + DataContent encryption = AesDataContentBuilder.builder(session).withKey(new SecretKeySpec(new byte[16], "AES")) + .modeGcm(128).build(true); encryption.setInput(new PlainBytes(new byte[] { 1, 2, 3 })); try (InputStream input = encryption.getStream()) { diff --git a/lib/src/test/java/zeroecho/sdk/guard/DecryptorCekCleanupTest.java b/lib/src/test/java/zeroecho/sdk/guard/DecryptorCekCleanupTest.java index a855e8f..bfe832d 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/DecryptorCekCleanupTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/DecryptorCekCleanupTest.java @@ -58,8 +58,8 @@ class DecryptorCekCleanupTest { System.out.println("acceptedCekIsClearedWhenPayloadSetupFails"); byte[] accepted = new byte[16]; RecipientOpener opener = (entryId, entryBlob, material) -> accepted; - Decryptor decryptor = new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), - null, null, 16, 4, 128); + Decryptor decryptor = new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), null, + null, 16, 4, 128); decryptor.setInput(() -> new ByteArrayInputStream(envelopeHeader())); assertThrows(NullPointerException.class, decryptor::getStream); @@ -104,8 +104,7 @@ class DecryptorCekCleanupTest { private static Decryptor decryptor(RecipientOpener opener) { AesDataContentBuilder aes = AesDataContentBuilder.builder(new ZeroEchoSession()).withHeader().modeGcm(128); - return new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), - aes, null, 16, 4, 128); + return new Decryptor(List.of(opener), new UnlockMaterial.Password(new char[] { 'p' }), aes, null, 16, 4, 128); } private static byte[] envelopeHeader() throws IOException { diff --git a/lib/src/test/java/zeroecho/sdk/guard/EncryptorCekAllocationTest.java b/lib/src/test/java/zeroecho/sdk/guard/EncryptorCekAllocationTest.java index 603459b..2d5914d 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/EncryptorCekAllocationTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/EncryptorCekAllocationTest.java @@ -83,8 +83,8 @@ class EncryptorCekAllocationTest { void recipientLimitIsCheckedBeforeCekGeneration() { System.out.print("EncryptorCekAllocation/recipientLimitIsCheckedBeforeCekGeneration..."); RecordingRandomFactory randomFactory = new RecordingRandomFactory(); - Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new CapturingRecipient(false)), - 1, randomFactory); + Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new CapturingRecipient(false)), 1, + randomFactory); encryptor.setInput(() -> new ByteArrayInputStream(new byte[0])); assertThrows(IOException.class, encryptor::getStream); @@ -97,8 +97,8 @@ class EncryptorCekAllocationTest { void recipientFailureClearsGenuineAndDecoyCeks() { System.out.print("EncryptorCekAllocation/recipientFailureClearsGenuineAndDecoyCeks..."); RecordingRandomFactory randomFactory = new RecordingRandomFactory(); - Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new FailingRecipient(true)), - 8, randomFactory); + Encryptor encryptor = newEncryptor(List.of(new CapturingRecipient(false), new FailingRecipient(true)), 8, + randomFactory); encryptor.setInput(() -> new ByteArrayInputStream(new byte[0])); assertThrows(IOException.class, encryptor::getStream); @@ -124,8 +124,8 @@ class EncryptorCekAllocationTest { void failedRecipientProcessingDestroysOwnedPassword() { System.out.print("EncryptorCekAllocation/failedRecipientProcessingDestroysOwnedPassword..."); PasswordRecipient passwordRecipient = passwordRecipient(); - Encryptor encryptor = newEncryptor(List.of(passwordRecipient, new FailingRecipient(false)), - 8, new RecordingRandomFactory()); + Encryptor encryptor = newEncryptor(List.of(passwordRecipient, new FailingRecipient(false)), 8, + new RecordingRandomFactory()); encryptor.setInput(() -> new ByteArrayInputStream(new byte[0])); assertThrows(IOException.class, encryptor::getStream); @@ -143,8 +143,8 @@ class EncryptorCekAllocationTest { private static Encryptor newEncryptor(List recipients, int maxRecipients, IntFunction randomFactory) { - return new Encryptor(recipients, AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128), null, KEY_BYTES, - maxRecipients, 1024, randomFactory); + return new Encryptor(recipients, AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128), + null, KEY_BYTES, maxRecipients, 1024, randomFactory); } private static byte[] repeated(byte value) { diff --git a/lib/src/test/java/zeroecho/sdk/guard/KemRecipientLifecycleTest.java b/lib/src/test/java/zeroecho/sdk/guard/KemRecipientLifecycleTest.java index 8bdc5df..f0065c5 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/KemRecipientLifecycleTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/KemRecipientLifecycleTest.java @@ -49,27 +49,23 @@ class KemRecipientLifecycleTest { void constructorsAndBuilderRejectUnsupportedKekBeforeOwnershipTransfer() throws Exception { System.out.println("constructorsAndBuilderRejectUnsupportedKekBeforeOwnershipTransfer"); int[] invalidValues = { -1, 0, 1, 15, 17, 24, 31, 33, Integer.MAX_VALUE }; - ZeroEchoSession session = new ZeroEchoSession() - .withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000)); + ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000)); MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()); for (int kekBytes : invalidValues) { ControlledKemContext direct = new ControlledKemContext(filled(32, (byte) 0x11)); - assertThrows(IllegalArgumentException.class, - () -> new KemCtxRecipient(direct, kekBytes, 16)); + assertThrows(IllegalArgumentException.class, () -> new KemCtxRecipient(direct, kekBytes, 16)); assertFalse(direct.closed); direct.close(); ControlledKemContext normal = new ControlledKemContext(filled(32, (byte) 0x22)); - assertThrows(IllegalArgumentException.class, - () -> builder.addRecipient(normal, kekBytes, 16)); + assertThrows(IllegalArgumentException.class, () -> builder.addRecipient(normal, kekBytes, 16)); assertEquals(0, recipients(builder).size()); assertFalse(normal.closed); normal.close(); ControlledKemContext decoy = new ControlledKemContext(filled(32, (byte) 0x33)); - assertThrows(IllegalArgumentException.class, - () -> builder.addRecipientDecoy(decoy, kekBytes, 16)); + assertThrows(IllegalArgumentException.class, () -> builder.addRecipientDecoy(decoy, kekBytes, 16)); assertEquals(0, recipients(builder).size()); assertFalse(decoy.closed); decoy.close(); diff --git a/lib/src/test/java/zeroecho/sdk/guard/MultiRecipientEnvelopeTest.java b/lib/src/test/java/zeroecho/sdk/guard/MultiRecipientEnvelopeTest.java index 89700e0..6f888fd 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/MultiRecipientEnvelopeTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/MultiRecipientEnvelopeTest.java @@ -103,11 +103,14 @@ public class MultiRecipientEnvelopeTest { final char[] password = "CorrectHorseBatteryStaple".toCharArray(); // AES-256-GCM with header so IV/tag are persisted in-band - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); // Encrypt - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32) + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32); DataContent encryptor = enc.build(true); @@ -120,8 +123,10 @@ public class MultiRecipientEnvelopeTest { // Decrypt UnlockMaterial.Password unlockMaterial = new UnlockMaterial.Password(password); - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).unlockWith(unlockMaterial); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(unlockMaterial); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -144,15 +149,12 @@ public class MultiRecipientEnvelopeTest { System.out.println("passwordRecipientWithAes128KekRoundTrips"); byte[] input = randomInput(257); char[] password = "controlled-password".toCharArray(); - ZeroEchoSession session = new ZeroEchoSession() - .withPbkdf2Limits(new Pbkdf2Limits(1_000_000, 1_000_000)); + ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(1_000_000, 1_000_000)); byte[] encrypted; try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) - .addPasswordRecipient(password, 10_000, 16, 16); - MultiRecipientContent content = builder.build(true)) { + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) + .addPasswordRecipient(password, 10_000, 16, 16); MultiRecipientContent content = builder.build(true)) { content.setInput(new BytesContent(input)); try (InputStream stream = content.getStream()) { encrypted = stream.readAllBytes(); @@ -162,10 +164,8 @@ public class MultiRecipientEnvelopeTest { UnlockMaterial.Password unlock = new UnlockMaterial.Password(password); byte[] decrypted; try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) - .unlockWith(unlock); - MultiRecipientContent content = builder.build(false)) { + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) + .unlockWith(unlock); MultiRecipientContent content = builder.build(false)) { content.setInput(new BytesContent(encrypted)); try (InputStream stream = content.getStream()) { decrypted = stream.readAllBytes(); @@ -192,10 +192,13 @@ public class MultiRecipientEnvelopeTest { final char[] password = "Tr0ub4dor&3".toCharArray(); // AES-256-CBC with PKCS7 padding, header persists IV - Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32) + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32) .addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32); DataContent encryptor = enc.build(true); @@ -206,8 +209,10 @@ public class MultiRecipientEnvelopeTest { encrypted = readAllBytesAndPrint(es, "... encrypted size"); } - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password)); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password)); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -234,13 +239,18 @@ public class MultiRecipientEnvelopeTest { final byte[] input = randomInput(128 * 1024 + 7); System.out.println("... input size: " + input.length); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).addRecipient(elgEnc); + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(elgEnc); DataContent encryptor = enc.build(true); encryptor.setInput(new BytesContent(input)); @@ -250,8 +260,10 @@ public class MultiRecipientEnvelopeTest { encrypted = readAllBytesAndPrint(es, "... encrypted size"); } - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate())); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate())); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -274,13 +286,18 @@ public class MultiRecipientEnvelopeTest { final byte[] input = randomInput(128 * 1024 + 13); // cross blocks System.out.println("... input size: " + input.length); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); - Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32).addRecipient(elgEnc); + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32).addRecipient(elgEnc); DataContent encryptor = enc.build(true); encryptor.setInput(new BytesContent(input)); @@ -290,8 +307,10 @@ public class MultiRecipientEnvelopeTest { encrypted = readAllBytesAndPrint(es, "... encrypted size"); } - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate())); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(elg.getPrivate())); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -322,12 +341,16 @@ public class MultiRecipientEnvelopeTest { kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).addRecipient(rsaEnc); + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc); DataContent encryptor = enc.build(true); encryptor.setInput(new BytesContent(input)); @@ -337,8 +360,10 @@ public class MultiRecipientEnvelopeTest { encrypted = readAllBytesAndPrint(es, "... encrypted size"); } - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -365,12 +390,16 @@ public class MultiRecipientEnvelopeTest { kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); - Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32).addRecipient(rsaEnc); + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32).addRecipient(rsaEnc); DataContent encryptor = enc.build(true); encryptor.setInput(new BytesContent(input)); @@ -380,8 +409,10 @@ public class MultiRecipientEnvelopeTest { encrypted = readAllBytesAndPrint(es, "... encrypted size"); } - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Private(rsa.getPrivate())); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); @@ -403,12 +434,12 @@ public class MultiRecipientEnvelopeTest { void testKemRecipientWith128BitKekRoundTrip() throws Exception { System.out.println("testKemRecipientWith128BitKekRoundTrip"); byte[] input = randomInput(257); - KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair keyPair = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber512()); byte[] encrypted = encryptKemRecipients(input, new KeyPair[] { keyPair }, new int[] { 16 }); - decryptAndAssert("...128-bit KEK", aesGcmSupplier(), new UnlockMaterial.Private(keyPair.getPrivate()), - input, encrypted); + decryptAndAssert("...128-bit KEK", aesGcmSupplier(), new UnlockMaterial.Private(keyPair.getPrivate()), input, + encrypted); System.out.println("...encryptedLength=" + encrypted.length); System.out.println("testKemRecipientWith128BitKekRoundTrip...ok"); @@ -418,17 +449,15 @@ public class MultiRecipientEnvelopeTest { void testMixedKemRecipientKekSizesRoundTrip() throws Exception { System.out.println("testMixedKemRecipientKekSizesRoundTrip"); zeroecho.sdk.ZeroEchoSession session = new zeroecho.sdk.ZeroEchoSession(); - KeyPair first = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); - KeyPair second = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair first = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair second = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); byte[] input = randomInput(513); byte[] encrypted = encryptKemRecipients(input, new KeyPair[] { first, second }, new int[] { 16, 32 }); - decryptAndAssert("...mixed 16-byte KEK", aesGcmSupplier(), - new UnlockMaterial.Private(first.getPrivate()), input, encrypted); - decryptAndAssert("...mixed 32-byte KEK", aesGcmSupplier(), - new UnlockMaterial.Private(second.getPrivate()), input, encrypted); + decryptAndAssert("...mixed 16-byte KEK", aesGcmSupplier(), new UnlockMaterial.Private(first.getPrivate()), + input, encrypted); + decryptAndAssert("...mixed 32-byte KEK", aesGcmSupplier(), new UnlockMaterial.Private(second.getPrivate()), + input, encrypted); System.out.println("...recipientCount=2"); System.out.println("testMixedKemRecipientKekSizesRoundTrip...ok"); @@ -438,22 +467,16 @@ public class MultiRecipientEnvelopeTest { void defaultKemOpenerContinuesAfterSameAlgorithmDecoy() throws Exception { System.out.println("defaultKemOpenerContinuesAfterSameAlgorithmDecoy"); ZeroEchoSession session = new ZeroEchoSession(); - KeyPair decoy = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); - KeyPair legitimate = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair decoy = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); + KeyPair legitimate = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); byte[] input = randomInput(385); byte[] encrypted; - KemContext decoyContext = - session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, decoy.getPublic()); - KemContext legitimateContext = - session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, legitimate.getPublic()); + KemContext decoyContext = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, decoy.getPublic()); + KemContext legitimateContext = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, legitimate.getPublic()); try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) - .addRecipient(decoyContext, 32, 16) - .addRecipient(legitimateContext, 32, 16); + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) + .addRecipient(decoyContext, 32, 16).addRecipient(legitimateContext, 32, 16); MultiRecipientContent content = builder.build(true)) { content.setInput(new BytesContent(input)); try (InputStream stream = content.getStream()) { @@ -463,8 +486,7 @@ public class MultiRecipientEnvelopeTest { byte[] decrypted; try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(legitimate.getPrivate())); MultiRecipientContent content = builder.build(false)) { content.setInput(new BytesContent(encrypted)); @@ -482,22 +504,16 @@ public class MultiRecipientEnvelopeTest { void defaultEncryptionOpenerContinuesAfterSameAlgorithmDecoy() throws Exception { System.out.println("defaultEncryptionOpenerContinuesAfterSameAlgorithmDecoy"); ZeroEchoSession session = new ZeroEchoSession(); - KeyPair decoy = session.keyBuilders().asymmetric() - .generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); - KeyPair legitimate = session.keyBuilders().asymmetric() - .generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair decoy = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); + KeyPair legitimate = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); byte[] input = randomInput(385); byte[] encrypted; - EncryptionContext decoyContext = - session.createContext("RSA", KeyUsage.ENCRYPT, decoy.getPublic()); - EncryptionContext legitimateContext = - session.createContext("RSA", KeyUsage.ENCRYPT, legitimate.getPublic()); + EncryptionContext decoyContext = session.createContext("RSA", KeyUsage.ENCRYPT, decoy.getPublic()); + EncryptionContext legitimateContext = session.createContext("RSA", KeyUsage.ENCRYPT, legitimate.getPublic()); try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) - .addRecipient(decoyContext) - .addRecipient(legitimateContext); + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) + .addRecipient(decoyContext).addRecipient(legitimateContext); MultiRecipientContent content = builder.build(true)) { content.setInput(new BytesContent(input)); try (InputStream stream = content.getStream()) { @@ -507,8 +523,7 @@ public class MultiRecipientEnvelopeTest { byte[] decrypted; try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .payloadKeyBytes(32) + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(legitimate.getPrivate())); MultiRecipientContent content = builder.build(false)) { content.setInput(new BytesContent(encrypted)); @@ -537,17 +552,25 @@ public class MultiRecipientEnvelopeTest { kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber512()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); - KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); + KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, + kyber.getPublic()); - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32) + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32) .addRecipient(rsaEnc).addRecipient(elgEnc).addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 32); @@ -585,18 +608,27 @@ public class MultiRecipientEnvelopeTest { kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa2048()); - KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + rsa = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("RSA", + RsaKeyGenSpec.rsa2048()); + KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); - KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); + KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, + kyber.getPublic()); - Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeCbcPkcs5().withHeader(); + Supplier aesCbc = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeCbcPkcs5().withHeader(); - MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesCbc.get()) - .payloadKeyBytes(32) + MultiRecipientDataSourceBuilder enc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesCbc.get()).payloadKeyBytes(32) .addPasswordRecipient(password, /* iterations */ 10000, /* saltLen */ 16, /* kekBytes */ 32) .addRecipient(rsaEnc).addRecipient(elgEnc).addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 32); @@ -636,23 +668,32 @@ public class MultiRecipientEnvelopeTest { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); // Sender signature keys (Ed25519) - KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); + KeyPair ed = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Ed25519", + Ed25519KeyGenSpec.defaultSpec()); // AES-256-GCM payload builder - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); // Context recipients - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); - KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); + KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, + kyber.getPublic()); // Envelope (encrypt) - MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc) + MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc) .addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 16) .addPasswordRecipient(password, /* iterations */ 100_000, /* saltLen */ 16, /* kekBytes */ 32); @@ -673,10 +714,13 @@ public class MultiRecipientEnvelopeTest { TagTrailerDataContentBuilder verifyTrailer; // via Password - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())) - .bufferSize(8192).throwOnMismatch(); - DataContent decPwd = DataContentChainBuilder - .decrypt().add(BytesSourceBuilder.of(ciphertext)).add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192) + .throwOnMismatch(); + DataContent decPwd = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) .withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password))) .add(verifyTrailer).build(); byte[] ptPwd; @@ -686,10 +730,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptPwd); // via RSA - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())) - .bufferSize(8192).throwOnMismatch(); + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192) + .throwOnMismatch(); DataContent decRsa = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(rsa.getPrivate()))) .add(verifyTrailer).build(); byte[] ptRsa; @@ -699,10 +747,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptRsa); // via ElGamal - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())) - .bufferSize(8192).throwOnMismatch(); + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192) + .throwOnMismatch(); DataContent decElgamal = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(elg.getPrivate()))) .add(verifyTrailer).build(); byte[] ptElgamal; @@ -712,10 +764,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptElgamal); // via ML-KEM - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())) - .bufferSize(8192).throwOnMismatch(); + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.ed25519Verify(new zeroecho.sdk.ZeroEchoSession(), ed.getPublic())).bufferSize(8192) + .throwOnMismatch(); DataContent decKem = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(kyber.getPrivate()))) .add(verifyTrailer).build(); byte[] ptKem; @@ -741,28 +797,38 @@ public class MultiRecipientEnvelopeTest { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(3072, new SecureRandom()); KeyPair rsa = kpg.generateKeyPair(); - KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber512()); - KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kyber = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber512()); + KeyPair elg = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ElGamal", + ElgamalParamSpec.ffdhe2048()); // Sender signature keys (SPHINCS+, default/best) - KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec()); + KeyPair spx = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("SPHINCS+", + SphincsPlusKeyGenSpec.defaultSpec()); - Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); + Supplier aesGcm = () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .modeGcm(128).withHeader(); // Context recipients - EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()); - EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()); - KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, kyber.getPublic()); + EncryptionContext rsaEnc = new zeroecho.sdk.ZeroEchoSession().createContext("RSA", KeyUsage.ENCRYPT, + rsa.getPublic()); + EncryptionContext elgEnc = new zeroecho.sdk.ZeroEchoSession().createContext("ElGamal", KeyUsage.ENCRYPT, + elg.getPublic()); + KemContext kybKem = new zeroecho.sdk.ZeroEchoSession().createContext("ML-KEM", KeyUsage.ENCAPSULATE, + kyber.getPublic()); // Envelope recipients - MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()) - .payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc) + MultiRecipientDataSourceBuilder envEnc = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32).addRecipient(rsaEnc).addRecipient(elgEnc) .addRecipient(kybKem, /* kekBytes */ 32, /* saltLen */ 16) .addPasswordRecipient(password, /* iterations */ 120_000, /* saltLen */ 16, /* kekBytes */ 32); // Tag trailer for SIGNING (SPHINCS+) TagTrailerDataContentBuilder signTrailer = new TagTrailerDataContentBuilder<>( - TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate())).bufferSize(8192); + TagEngineBuilder.sphincsPlusSign(new zeroecho.sdk.ZeroEchoSession(), spx.getPrivate())) + .bufferSize(8192); // Encrypt chain DataContent encryptChain = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)).add(signTrailer) @@ -776,10 +842,13 @@ public class MultiRecipientEnvelopeTest { TagTrailerDataContentBuilder verifyTrailer; // via Password - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) .bufferSize(8192).throwOnMismatch(); - DataContent decPwd = DataContentChainBuilder - .decrypt().add(BytesSourceBuilder.of(ciphertext)).add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + DataContent decPwd = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) .withAes(aesGcm.get()).payloadKeyBytes(32).unlockWith(new UnlockMaterial.Password(password))) .add(verifyTrailer).build(); byte[] ptPwd; @@ -789,10 +858,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptPwd); // via RSA - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) .bufferSize(8192).throwOnMismatch(); DataContent decRsa = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(rsa.getPrivate()))) .add(verifyTrailer).build(); byte[] ptRsa; @@ -802,10 +875,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptRsa); // via ElGamal - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) .bufferSize(8192).throwOnMismatch(); DataContent decElgamal = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(elg.getPrivate()))) .add(verifyTrailer).build(); byte[] ptElgamal; @@ -815,10 +892,14 @@ public class MultiRecipientEnvelopeTest { assertArrayEquals(msg, ptElgamal); // via ML-KEM - verifyTrailer = new TagTrailerDataContentBuilder<>(TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) + verifyTrailer = new TagTrailerDataContentBuilder<>( + TagEngineBuilder.sphincsPlusVerify(new zeroecho.sdk.ZeroEchoSession(), spx.getPublic())) .bufferSize(8192).throwOnMismatch(); DataContent decKem = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(ciphertext)) - .add(MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesGcm.get()).payloadKeyBytes(32) + .add(MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesGcm.get()).payloadKeyBytes(32) .unlockWith(new UnlockMaterial.Private(kyber.getPrivate()))) .add(verifyTrailer).build(); byte[] ptKem; @@ -834,14 +915,12 @@ public class MultiRecipientEnvelopeTest { // Helpers // ------------------------------------------------------------------------------------ - private static byte[] encryptKemRecipients(byte[] input, KeyPair[] keyPairs, int[] kekSizes) - throws Exception { + private static byte[] encryptKemRecipients(byte[] input, KeyPair[] keyPairs, int[] kekSizes) throws Exception { zeroecho.sdk.ZeroEchoSession session = new zeroecho.sdk.ZeroEchoSession(); MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) .withAes(aesGcmSupplier().get()).payloadKeyBytes(32); for (int index = 0; index < keyPairs.length; index++) { - KemContext context = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, - keyPairs[index].getPublic()); + KemContext context = session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, keyPairs[index].getPublic()); builder.addRecipient(context, kekSizes[index], 16); } DataContent encryptor = builder.build(true); @@ -852,8 +931,7 @@ public class MultiRecipientEnvelopeTest { } private static Supplier aesGcmSupplier() { - return () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) - .modeGcm(128).withHeader(); + return () -> AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).modeGcm(128).withHeader(); } /** Minimal source builder so we can compose pull-style chains. */ @@ -885,8 +963,10 @@ public class MultiRecipientEnvelopeTest { private static void decryptAndAssert(String banner, Supplier aesFactory, UnlockMaterial material, byte[] original, byte[] encrypted) throws IOException { - MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder.builder(new zeroecho.sdk.ZeroEchoSession().withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))).withAes(aesFactory.get()) - .payloadKeyBytes(32).unlockWith(material); + MultiRecipientDataSourceBuilder dec = MultiRecipientDataSourceBuilder + .builder(new zeroecho.sdk.ZeroEchoSession() + .withPbkdf2Limits(new zeroecho.sdk.Pbkdf2Limits(1_000_000, 1_000_000))) + .withAes(aesFactory.get()).payloadKeyBytes(32).unlockWith(material); DataContent decryptor = dec.build(false); decryptor.setInput(new BytesContent(encrypted)); diff --git a/lib/src/test/java/zeroecho/sdk/guard/PasswordRecipientTest.java b/lib/src/test/java/zeroecho/sdk/guard/PasswordRecipientTest.java index 0c8b34e..7b1a948 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/PasswordRecipientTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/PasswordRecipientTest.java @@ -75,10 +75,8 @@ class PasswordRecipientTest { () -> new PasswordRecipient(new char[] { 'p' }, 10_000, 16, kekBytes, false, LIMITS)); } - PasswordRecipient aes128 = - new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 16, false, LIMITS); - PasswordRecipient aes256 = - new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 32, false, LIMITS); + PasswordRecipient aes128 = new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 16, false, LIMITS); + PasswordRecipient aes256 = new PasswordRecipient(new char[] { 'p' }, 10_000, 16, 32, false, LIMITS); aes128.close(); aes256.close(); System.out.println("...acceptedKekBytes=16,32"); @@ -131,9 +129,8 @@ class PasswordRecipientTest { ByteArrayOutputStream blob = new ByteArrayOutputStream(); Util.writePack7I(blob, 9_999); - assertThrows(IOException.class, () -> new PasswordOpener(LIMITS).tryOpen( - "PWD:PBKDF2-SHA256:GCM-WRAP", blob.toByteArray(), - new UnlockMaterial.Password(new char[] { 'p' }))); + assertThrows(IOException.class, () -> new PasswordOpener(LIMITS).tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", + blob.toByteArray(), new UnlockMaterial.Password(new char[] { 'p' }))); System.out.println("ok"); } @@ -143,11 +140,9 @@ class PasswordRecipientTest { PasswordOpener opener = new PasswordOpener(LIMITS); UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' }); - assertThrows(IOException.class, - () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", new byte[] { 1 }, material)); - assertThrows(IOException.class, - () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", - new byte[] { 1, 1, 1, 1, 1, (byte) 0x80 }, material)); + assertThrows(IOException.class, () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", new byte[] { 1 }, material)); + assertThrows(IOException.class, () -> opener.tryOpen("PWD:PBKDF2-SHA256:GCM-WRAP", + new byte[] { 1, 1, 1, 1, 1, (byte) 0x80 }, material)); System.out.println("...malformedCases=2"); System.out.println("ok"); } @@ -201,8 +196,7 @@ class PasswordRecipientTest { assertTrue(content.isDestroyed()); assertTrue(recipient.isDestroyed()); - assertThrows(IllegalStateException.class, - () -> content.setInput(() -> new ByteArrayInputStream(new byte[0]))); + assertThrows(IllegalStateException.class, () -> content.setInput(() -> new ByteArrayInputStream(new byte[0]))); assertThrows(IllegalStateException.class, content::getStream); System.out.println("...contentDestroyed=true"); System.out.println("abandonedBuiltContentOwnsAndDestroysTransferredRecipient...ok"); @@ -218,14 +212,13 @@ class PasswordRecipientTest { DataContent limited = limitedBuilder.build(true); limited.setInput(() -> new ByteArrayInputStream(new byte[0])); assertThrows(IOException.class, limited::getStream); - assertTrue(limitedRecipients.stream() - .map(PasswordRecipient.class::cast).allMatch(PasswordRecipient::isDestroyed)); + assertTrue( + limitedRecipients.stream().map(PasswordRecipient.class::cast).allMatch(PasswordRecipient::isDestroyed)); - PasswordRecipient randomRecipient = - new PasswordRecipient(new char[] { 'c' }, 10_000, 16, 32, false, LIMITS); + PasswordRecipient randomRecipient = new PasswordRecipient(new char[] { 'c' }, 10_000, 16, 32, false, LIMITS); Encryptor randomFailure = new Encryptor(List.of(randomRecipient), - AesDataContentBuilder.builder(new ZeroEchoSession()).modeGcm(128).withHeader(), - null, 32, 4, 1024, ignored -> { + AesDataContentBuilder.builder(new ZeroEchoSession()).modeGcm(128).withHeader(), null, 32, 4, 1024, + ignored -> { throw new IllegalStateException("controlled random failure"); }); randomFailure.setInput(() -> new ByteArrayInputStream(new byte[0])); diff --git a/lib/src/test/java/zeroecho/sdk/guard/SessionRecipientOpenerContractTest.java b/lib/src/test/java/zeroecho/sdk/guard/SessionRecipientOpenerContractTest.java index 86250a7..f4b33ba 100644 --- a/lib/src/test/java/zeroecho/sdk/guard/SessionRecipientOpenerContractTest.java +++ b/lib/src/test/java/zeroecho/sdk/guard/SessionRecipientOpenerContractTest.java @@ -32,8 +32,7 @@ class SessionRecipientOpenerContractTest { assertSessionOnlyConstructor(EncCtxOpener.class); List addOpenerMethods = Arrays.stream(MultiRecipientDataSourceBuilder.class.getMethods()) - .filter(method -> method.getName().equals("addOpener")) - .toList(); + .filter(method -> method.getName().equals("addOpener")).toList(); assertEquals(1, addOpenerMethods.size()); assertEquals(RecipientOpener.class, addOpenerMethods.get(0).getParameterTypes()[0]); @@ -47,10 +46,8 @@ class SessionRecipientOpenerContractTest { ZeroEchoSession session = new ZeroEchoSession(); UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' }); try { - assertNull(new KemCtxOpener(session).tryOpen( - "CTX-ENC:RSA", new byte[0], material)); - assertNull(new EncCtxOpener(session).tryOpen( - "KEM:ML-KEM:GCM-WRAP", new byte[0], material)); + assertNull(new KemCtxOpener(session).tryOpen("CTX-ENC:RSA", new byte[0], material)); + assertNull(new EncCtxOpener(session).tryOpen("KEM:ML-KEM:GCM-WRAP", new byte[0], material)); } finally { material.destroy(); } @@ -61,15 +58,12 @@ class SessionRecipientOpenerContractTest { @Test void customReusableOpenerScansEveryEntryAndClosesOnce() throws Exception { System.out.println("customReusableOpenerScansEveryEntryAndClosesOnce"); - ZeroEchoSession session = new ZeroEchoSession() - .withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000)); + ZeroEchoSession session = new ZeroEchoSession().withPbkdf2Limits(new Pbkdf2Limits(20_000, 30_000)); TrackingOpener opener = new TrackingOpener(); UnlockMaterial.Password material = new UnlockMaterial.Password(new char[] { 'p' }); try (MultiRecipientDataSourceBuilder builder = MultiRecipientDataSourceBuilder.builder(session) - .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()) - .unlockWith(material) - .addOpener(opener); - MultiRecipientContent content = builder.build(false)) { + .withAes(AesDataContentBuilder.builder(session).modeGcm(128).withHeader()).unlockWith(material) + .addOpener(opener); MultiRecipientContent content = builder.build(false)) { content.setInput(() -> new ByteArrayInputStream(twoEntryHeader())); assertThrows(IOException.class, content::getStream); } finally { diff --git a/lib/src/test/java/zeroecho/sdk/hybrid/derived/HybridDerivedTest.java b/lib/src/test/java/zeroecho/sdk/hybrid/derived/HybridDerivedTest.java index a0e08b9..4622d78 100644 --- a/lib/src/test/java/zeroecho/sdk/hybrid/derived/HybridDerivedTest.java +++ b/lib/src/test/java/zeroecho/sdk/hybrid/derived/HybridDerivedTest.java @@ -76,7 +76,8 @@ public class HybridDerivedTest { byte[] aad = "aad".getBytes(StandardCharsets.UTF_8); byte[] msg = fixedBytes(1024, (byte) 0x5A); - AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128); + AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader() + .modeGcm(128); AesDataContentBuilder returnedEnc = HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript) .aad(aad).applyToAesGcm(encAes, 256); @@ -88,10 +89,10 @@ public class HybridDerivedTest { System.out.println("...ciphertextLen=" + ciphertext.length); System.out.println("...ciphertextPrefix=" + shortHex(ciphertext, 32)); - AesDataContentBuilder decAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128); + AesDataContentBuilder decAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader() + .modeGcm(128); - HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad) - .applyToAesGcm(decAes, 256); + HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad).applyToAesGcm(decAes, 256); byte[] out = runDecrypt(decAes, ciphertext); System.out.println("...outPrefix=" + shortHex(out, 32)); @@ -109,15 +110,16 @@ public class HybridDerivedTest { byte[] aad = "aad".getBytes(StandardCharsets.UTF_8); byte[] msg = fixedBytes(256, (byte) 0x1C); - AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128); + AesDataContentBuilder encAes = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader() + .modeGcm(128); - HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad) - .applyToAesGcm(encAes, 256); + HybridDerived.from(exporter).label("app/enc/aes").transcript(transcript).aad(aad).applyToAesGcm(encAes, 256); byte[] ciphertext = runEncrypt(encAes, msg); System.out.println("...ciphertextLen=" + ciphertext.length); - AesDataContentBuilder decAesWrong = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader().modeGcm(128); + AesDataContentBuilder decAesWrong = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .withHeader().modeGcm(128); // ...label mismatch -> wrong key/iv/aad -> decryption must fail HybridDerived.from(exporter).label("app/enc/aes_WRONG").transcript(transcript).aad(aad) @@ -137,7 +139,8 @@ public class HybridDerivedTest { byte[] aad = "aad".getBytes(StandardCharsets.UTF_8); byte[] msg = fixedBytes(777, (byte) 0x33); - ChaChaDataContentBuilder encChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader(); + ChaChaDataContentBuilder encChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .withHeader(); ChaChaDataContentBuilder returnedEnc = HybridDerived.from(exporter).label("app/enc/chacha") .transcript(transcript).aad(aad).applyToChaChaAead(encChaCha, 256); @@ -149,7 +152,8 @@ public class HybridDerivedTest { System.out.println("...ciphertextLen=" + ciphertext.length); System.out.println("...ciphertextPrefix=" + shortHex(ciphertext, 32)); - ChaChaDataContentBuilder decChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).withHeader(); + ChaChaDataContentBuilder decChaCha = ChaChaDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .withHeader(); HybridDerived.from(exporter).label("app/enc/chacha").transcript(transcript).aad(aad) .applyToChaChaAead(decChaCha, 256); @@ -174,7 +178,8 @@ public class HybridDerivedTest { // recommended bits // -------------------- - HmacDataContentBuilder macBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().emitHexTag(); + HmacDataContentBuilder macBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256() + .emitHexTag(); int recommendedBits = macBuilder.recommendedKeyBits(); System.out.println("...recommendedBits=" + recommendedBits); @@ -184,8 +189,8 @@ public class HybridDerivedTest { String tagHex = runHmacHex(macBuilder, msg); System.out.println("...tagHexPrefix=" + shortText(tagHex, 64)); - HmacDataContentBuilder verifyBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().expectedTagHex(tagHex) - .emitVerificationBoolean(); + HmacDataContentBuilder verifyBuilder = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .sha256().expectedTagHex(tagHex).emitVerificationBoolean(); HybridDerived.from(exporter).label("app/mac/hmac-default").transcript(transcript).applyToHmac(verifyBuilder); @@ -197,7 +202,8 @@ public class HybridDerivedTest { // Override key size path: applyToHmac(hmac, keyBits) // -------------------- - HmacDataContentBuilder macBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().emitHexTag(); + HmacDataContentBuilder macBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .sha256().emitHexTag(); // ...override to 512-bit keying material (still valid for HMAC; explicit expert // choice) @@ -207,8 +213,8 @@ public class HybridDerivedTest { String tagHexOv = runHmacHex(macBuilderOv, msg); System.out.println("...tagHexOvPrefix=" + shortText(tagHexOv, 64)); - HmacDataContentBuilder verifyBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).sha256().expectedTagHex(tagHexOv) - .emitVerificationBoolean(); + HmacDataContentBuilder verifyBuilderOv = HmacDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .sha256().expectedTagHex(tagHexOv).emitVerificationBoolean(); HybridDerived.from(exporter).label("app/mac/hmac-override").transcript(transcript).applyToHmac(verifyBuilderOv, 512); diff --git a/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexFrameCodecTest.java b/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexFrameCodecTest.java index 4acb18a..26dc5de 100644 --- a/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexFrameCodecTest.java +++ b/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexFrameCodecTest.java @@ -67,17 +67,9 @@ class HybridKexFrameCodecTest { @Test void rejectsMalformedFrames() { String name = start("rejectsMalformedFrames"); - byte[][] malformed = { - new byte[7], - ints(-1, 0), - ints(0, -1), - ints(1, 0), - ints(0, 1), - append(ints(0, 0), (byte) 0x7f), - new byte[HybridKexContext.MAX_FRAME_BYTES + 1], - ints(0, HybridKexContext.MAX_FRAME_BYTES), - ints(Integer.MAX_VALUE, 0) - }; + byte[][] malformed = { new byte[7], ints(-1, 0), ints(0, -1), ints(1, 0), ints(0, 1), + append(ints(0, 0), (byte) 0x7f), new byte[HybridKexContext.MAX_FRAME_BYTES + 1], + ints(0, HybridKexContext.MAX_FRAME_BYTES), ints(Integer.MAX_VALUE, 0) }; for (byte[] frame : malformed) { assertThrows(IOException.class, () -> HybridKexContext.decode(frame)); diff --git a/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexTest.java b/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexTest.java index 194e844..fec779e 100644 --- a/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexTest.java +++ b/lib/src/test/java/zeroecho/sdk/hybrid/kex/HybridKexTest.java @@ -128,11 +128,14 @@ public class HybridKexTest { HybridKexProfile profile = HybridKexProfile.defaultProfile(32); // Classic: X25519 key pairs (Xdh + XdhSpec.X25519) - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); // PQC: ML-KEM key pair (Kyber variant) - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexContext alice = null; HybridKexContext bob = null; @@ -140,13 +143,15 @@ public class HybridKexTest { try { // Initiator: classic uses Alice private + Bob classic public; PQC uses Bob PQC // public - alice = HybridKexContexts.initiator(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), - XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null); + alice = HybridKexContexts.initiator(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", + aliceClassic.getPrivate(), bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), + null); // Responder: classic uses Bob private + Alice classic public; PQC uses Bob PQC // private - bob = HybridKexContexts.responder(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", bobClassic.getPrivate(), aliceClassic.getPublic(), - XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null); + bob = HybridKexContexts.responder(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", + bobClassic.getPrivate(), aliceClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), + null); // Alice produces message (contains PQC ciphertext; classic part is empty here) byte[] aliceMsg = alice.getPeerMessage(); @@ -189,11 +194,14 @@ public class HybridKexTest { HybridKexProfile profile = HybridKexProfile.defaultProfile(32); // Classic: X25519 key pairs (Xdh + XdhSpec.X25519) - KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); - KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); + KeyPair aliceClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); + KeyPair bobClassic = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("Xdh", + XdhSpec.X25519); // PQC: ML-KEM key pair (recipient/responder) - KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = new zeroecho.sdk.ZeroEchoSession().keyBuilders().asymmetric().generateKeyPair("ML-KEM", + KyberKeyGenSpec.kyber768()); HybridKexContext alice = null; HybridKexContext bob = null; @@ -203,11 +211,11 @@ public class HybridKexTest { // KeyPairKey + ContextSpec). // PQC leg is KEM-style: initiator uses recipient public key; responder uses // recipient private key. - alice = HybridKexContexts.initiatorPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", new KeyPairKey(aliceClassic), XdhSpec.X25519, - "ML-KEM", bobPqc.getPublic(), null); + alice = HybridKexContexts.initiatorPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", + new KeyPairKey(aliceClassic), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null); - bob = HybridKexContexts.responderPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", new KeyPairKey(bobClassic), XdhSpec.X25519, - "ML-KEM", bobPqc.getPrivate(), null); + bob = HybridKexContexts.responderPairMessage(new zeroecho.sdk.ZeroEchoSession(), profile, "Xdh", + new KeyPairKey(bobClassic), XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null); // Step 1: Alice -> Bob (classic SPKI + PQC ciphertext) byte[] msgA = alice.getPeerMessage(); diff --git a/lib/src/test/java/zeroecho/sdk/hybrid/signature/HybridSignatureTest.java b/lib/src/test/java/zeroecho/sdk/hybrid/signature/HybridSignatureTest.java index 0851d08..bc40c4e 100644 --- a/lib/src/test/java/zeroecho/sdk/hybrid/signature/HybridSignatureTest.java +++ b/lib/src/test/java/zeroecho/sdk/hybrid/signature/HybridSignatureTest.java @@ -242,8 +242,7 @@ public class HybridSignatureTest { System.out.println("...msg=" + msg.length + " bytes"); ZeroEchoSession session = new ZeroEchoSession(); - KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", - Ed25519KeyGenSpec.defaultSpec()); + KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec()); @@ -256,15 +255,15 @@ public class HybridSignatureTest { HybridSignatureProfile.VerifyRule.AND); byte[] sigAnd; - try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), andProfile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { sigAnd = signTrailer(signer, msg); } System.out.println("...sig(AND).len=" + sigAnd.length + ", head=" + hexShort(sigAnd)); // verify OK - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(sigAnd); try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) { @@ -275,8 +274,8 @@ public class HybridSignatureTest { // corrupt classic => must fail byte[] badClassic = concat(flipOneBit(sub(sigAnd, 0, edLen), 0), sub(sigAnd, edLen, spxLen)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(badClassic); assertThrows(java.io.IOException.class, () -> { @@ -289,8 +288,8 @@ public class HybridSignatureTest { // corrupt pqc => must fail byte[] badPqc = concat(sub(sigAnd, 0, edLen), flipOneBit(sub(sigAnd, edLen, spxLen), 0)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), andProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(badPqc); assertThrows(java.io.IOException.class, () -> { @@ -306,16 +305,16 @@ public class HybridSignatureTest { HybridSignatureProfile.VerifyRule.OR); byte[] sigOr; - try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), orProfile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { sigOr = signTrailer(signer, msg); } System.out.println("...sig(OR).len=" + sigOr.length + ", head=" + hexShort(sigOr)); // corrupt classic => OR must pass byte[] orBadClassic = concat(flipOneBit(sub(sigOr, 0, edLen), 0), sub(sigOr, edLen, spxLen)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(orBadClassic); try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) { @@ -326,8 +325,8 @@ public class HybridSignatureTest { // corrupt pqc => OR must pass byte[] orBadPqc = concat(sub(sigOr, 0, edLen), flipOneBit(sub(sigOr, edLen, spxLen), 0)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(orBadPqc); try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) { @@ -338,8 +337,8 @@ public class HybridSignatureTest { // corrupt both => OR must fail byte[] orBadBoth = concat(flipOneBit(sub(sigOr, 0, edLen), 0), flipOneBit(sub(sigOr, edLen, spxLen), 0)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), orProfile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(orBadBoth); assertThrows(java.io.IOException.class, () -> { @@ -377,14 +376,14 @@ public class HybridSignatureTest { HybridSignatureProfile.VerifyRule.AND); byte[] sig; - try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext signer = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + rsa.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { sig = signTrailer(signer, msg); } System.out.println("...sig.len=" + sig.length + ", head=" + hexShort(sig)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(sig); try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) { @@ -395,8 +394,8 @@ public class HybridSignatureTest { // negative sanity: corrupt classic => must fail (AND) byte[] badClassic = concat(flipOneBit(sub(sig, 0, rsaLen), 0), sub(sig, rsaLen, spxLen)); - try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext verifier = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { verifier.setVerificationApproach(verifier.getVerificationCore().getThrowOnMismatch()); verifier.setExpectedTag(badClassic); assertThrows(java.io.IOException.class, () -> { @@ -426,8 +425,7 @@ public class HybridSignatureTest { System.out.println("...msg=" + msg.length + " bytes"); ZeroEchoSession session = new ZeroEchoSession(); - KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", - Ed25519KeyGenSpec.defaultSpec()); + KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec()); @@ -437,8 +435,8 @@ public class HybridSignatureTest { byte[] out; int tagLen; - try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) .add(new TagTrailerDataContentBuilder(tagEnc).bufferSize(8192)).build(); @@ -449,8 +447,8 @@ public class HybridSignatureTest { System.out.println("...out=" + out.length + " bytes"); - try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); // IMPORTANT: TagTrailerDataContentBuilder supplies expectedTag internally @@ -481,8 +479,7 @@ public class HybridSignatureTest { System.out.println("...msg=" + msg.length + " bytes"); ZeroEchoSession session = new ZeroEchoSession(); - KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", - Ed25519KeyGenSpec.defaultSpec()); + KeyPair ed = session.keyBuilders().asymmetric().generateKeyPair("Ed25519", Ed25519KeyGenSpec.defaultSpec()); KeyPair spx = session.keyBuilders().asymmetric().generateKeyPair("SPHINCS+", SphincsPlusKeyGenSpec.defaultSpec()); @@ -496,8 +493,8 @@ public class HybridSignatureTest { byte[] out; int tagLen; - try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) .add(new TagTrailerDataContentBuilder(tagEnc).bufferSize(8192)).build(); @@ -515,8 +512,8 @@ public class HybridSignatureTest { byte[] badClassic = concat(flipOneBit(sub(tag, 0, edLen), 0), sub(tag, edLen, spxLen)); byte[] outBadClassic = concat(body, badClassic); - try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadClassic)) @@ -533,8 +530,8 @@ public class HybridSignatureTest { byte[] badPqc = concat(sub(tag, 0, edLen), flipOneBit(sub(tag, edLen, spxLen), 0)); byte[] outBadPqc = concat(body, badPqc); - try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadPqc)) @@ -551,8 +548,8 @@ public class HybridSignatureTest { byte[] badBoth = concat(flipOneBit(sub(tag, 0, edLen), 0), flipOneBit(sub(tag, edLen, spxLen), 0)); byte[] outBadBoth = concat(body, badBoth); - try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(outBadBoth)) @@ -589,8 +586,8 @@ public class HybridSignatureTest { byte[] out; - try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024)) { + try (SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + rsa.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024)) { DataContent enc = DataContentChainBuilder.encrypt().add(BytesSourceBuilder.of(msg)) .add(new TagTrailerDataContentBuilder(tagEnc).bufferSize(8192)).build(); @@ -600,8 +597,8 @@ public class HybridSignatureTest { System.out.println("...out=" + out.length + " bytes"); - try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, rsa.getPublic(), spx.getPublic(), - 2 * 1024 * 1024)) { + try (SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + rsa.getPublic(), spx.getPublic(), 2 * 1024 * 1024)) { tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); DataContent dec = DataContentChainBuilder.decrypt().add(BytesSourceBuilder.of(out)) diff --git a/pki/src/main/java/zeroecho/pki/api/ProfileService.java b/pki/src/main/java/zeroecho/pki/api/ProfileService.java index 68e8fb6..cd8d7af 100644 --- a/pki/src/main/java/zeroecho/pki/api/ProfileService.java +++ b/pki/src/main/java/zeroecho/pki/api/ProfileService.java @@ -17,8 +17,8 @@ import zeroecho.pki.api.profile.ImportedCertificateProfileVersion; * Versioned certificate-profile import, activation, and lookup service. * *

    - * Import never activates a profile. All versions of one logical profile ID - * have one immutable certificate kind. End-entity issuance resolves only an + * Import never activates a profile. All versions of one logical profile ID have + * one immutable certificate kind. End-entity issuance resolves only an * explicitly activated persisted version through * {@link #requireActiveProfile(String)}. Root and intermediate issuance use the * same authoritative active-profile lookup and bind issued credentials to the diff --git a/pki/src/main/java/zeroecho/pki/api/credential/Credential.java b/pki/src/main/java/zeroecho/pki/api/credential/Credential.java index 5190e5a..50d7f2c 100644 --- a/pki/src/main/java/zeroecho/pki/api/credential/Credential.java +++ b/pki/src/main/java/zeroecho/pki/api/credential/Credential.java @@ -66,17 +66,16 @@ import zeroecho.pki.api.attr.AttributeSet; * @param publicKeyId stable identifier derived from the subject public key * @param profileBinding closed profile identity governing issuance * @param status persisted issuance and inventory metadata; this value - * is not sufficient for a trust decision because current - * revocation state and evaluation time are external - * runtime inputs. Security-sensitive callers must use - * {@link EffectiveCredentialStatusResolver}. + * is not sufficient for a trust decision because + * current revocation state and evaluation time are + * external runtime inputs. Security-sensitive callers + * must use {@link EffectiveCredentialStatusResolver}. * @param encoded encoded credential bytes * @param attributes universal attribute set */ public record Credential(PkiId credentialId, FormatId formatId, IssuerRef issuerRef, SubjectRef subjectRef, Validity validity, String serialOrUniqueId, PkiId publicKeyId, CredentialProfileBinding profileBinding, - CredentialStatus status, - EncodedObject encoded, AttributeSet attributes) { + CredentialStatus status, EncodedObject encoded, AttributeSet attributes) { /** * Creates a credential record. diff --git a/pki/src/main/java/zeroecho/pki/api/credential/EffectiveCredentialStatusResolver.java b/pki/src/main/java/zeroecho/pki/api/credential/EffectiveCredentialStatusResolver.java index be5593c..1c0c2a2 100644 --- a/pki/src/main/java/zeroecho/pki/api/credential/EffectiveCredentialStatusResolver.java +++ b/pki/src/main/java/zeroecho/pki/api/credential/EffectiveCredentialStatusResolver.java @@ -75,7 +75,7 @@ public interface EffectiveCredentialStatusResolver { * @param credential credential to resolve * @return effective runtime status * @throws NullPointerException if {@code credential} is {@code null} - * @throws PkiException if current status cannot be resolved safely + * @throws PkiException if current status cannot be resolved safely */ EffectiveCredentialStatus resolve(Credential credential); @@ -83,11 +83,11 @@ public interface EffectiveCredentialStatusResolver { * Requires a credential to be usable for the supplied operation category. * * @param credential credential to check - * @param use trust-decision category + * @param use trust-decision category * @return the supplied credential when usable * @throws NullPointerException if an argument is {@code null} - * @throws PkiException if status resolution fails or the credential is not - * usable + * @throws PkiException if status resolution fails or the credential is + * not usable */ Credential requireUsable(Credential credential, CredentialUse use); } diff --git a/pki/src/main/java/zeroecho/pki/api/issuance/VerificationPolicy.java b/pki/src/main/java/zeroecho/pki/api/issuance/VerificationPolicy.java index 5020cc8..6be792b 100644 --- a/pki/src/main/java/zeroecho/pki/api/issuance/VerificationPolicy.java +++ b/pki/src/main/java/zeroecho/pki/api/issuance/VerificationPolicy.java @@ -44,9 +44,10 @@ import java.util.Optional; *

    * *

    - * Setting {@code requireProofOfPossession} to {@code false} is suitable only for - * diagnostic or policy-evaluation workflows. It cannot authorize credential - * issuance; the issuance boundary always applies its own mandatory proof policy. + * Setting {@code requireProofOfPossession} to {@code false} is suitable only + * for diagnostic or policy-evaluation workflows. It cannot authorize credential + * issuance; the issuance boundary always applies its own mandatory proof + * policy. *

    * * @param requireProofOfPossession whether proof-of-possession is required diff --git a/pki/src/main/java/zeroecho/pki/api/orch/SigningSubmissionId.java b/pki/src/main/java/zeroecho/pki/api/orch/SigningSubmissionId.java index 76d2a06..216ed06 100644 --- a/pki/src/main/java/zeroecho/pki/api/orch/SigningSubmissionId.java +++ b/pki/src/main/java/zeroecho/pki/api/orch/SigningSubmissionId.java @@ -44,11 +44,14 @@ import zeroecho.pki.api.PkiId; /** * Versioned ZeroEcho signing-submission identifier. * - *

    The format is {@code zsign:v1:::<128-bit-random>}. - * Creation time remains store-authoritative: stores must validate the embedded - * time against their own clock and persist it with the intent.

    + *

    + * The format is + * {@code zsign:v1:::<128-bit-random>}. Creation + * time remains store-authoritative: stores must validate the embedded time + * against their own clock and persist it with the intent. + *

    * - * @param id identifier value + * @param id identifier value * @param namespace provider/store namespace * @param createdAt embedded creation time with millisecond precision */ @@ -67,8 +70,8 @@ public record SigningSubmissionId(PkiId id, String namespace, Instant createdAt) * Creates a new identifier using a cryptographically strong random source. * * @param namespace provider/store namespace - * @param now trusted caller creation time - * @param random secure random source + * @param now trusted caller creation time + * @param random secure random source * @return new identifier */ public static SigningSubmissionId create(String namespace, Instant now, SecureRandom random) { @@ -117,9 +120,9 @@ public record SigningSubmissionId(PkiId id, String namespace, Instant createdAt) * Validates namespace and lifetime against store-authoritative time. * * @param requiredNamespace expected namespace - * @param now store-authoritative time - * @param horizon maximum accepted age - * @param permittedSkew allowed future clock skew + * @param now store-authoritative time + * @param horizon maximum accepted age + * @param permittedSkew allowed future clock skew * @throws IllegalArgumentException if namespace or lifetime is invalid */ public void validate(String requiredNamespace, Instant now, Duration horizon, Duration permittedSkew) { @@ -147,8 +150,8 @@ public record SigningSubmissionId(PkiId id, String namespace, Instant createdAt) } for (int index = 0; index < namespace.length(); index++) { char value = namespace.charAt(index); - boolean valid = value >= 'a' && value <= 'z' || value >= '0' && value <= '9' - || value == '.' || value == '_' || value == '-'; + boolean valid = value >= 'a' && value <= 'z' || value >= '0' && value <= '9' || value == '.' || value == '_' + || value == '-'; if (!valid) { throw new IllegalArgumentException("namespace contains an unsupported character"); } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/ActiveCertificateProfile.java b/pki/src/main/java/zeroecho/pki/api/profile/ActiveCertificateProfile.java index 1035d77..413cf63 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/ActiveCertificateProfile.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/ActiveCertificateProfile.java @@ -11,11 +11,10 @@ import java.util.Objects; /** * Validated active profile resolution result used by end-entity issuance. * - * @param reference exact active version reference + * @param reference exact active version reference * @param definition authoritative immutable definition */ -public record ActiveCertificateProfile(CertificateProfileRef reference, - CertificateProfileDefinition definition) { +public record ActiveCertificateProfile(CertificateProfileRef reference, CertificateProfileDefinition definition) { /** Creates an active profile result. */ public ActiveCertificateProfile { Objects.requireNonNull(reference, "reference"); diff --git a/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalog.java b/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalog.java index 0953fce..e4bf1f0 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalog.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalog.java @@ -50,12 +50,10 @@ import zeroecho.pki.api.PkiException; public final class BuiltInCertificateProfileCatalog { /** Fixed packaged catalogue manifest resource. */ - public static final String MANIFEST_RESOURCE = - "zeroecho/pki/profiles/v1/catalog.json"; + public static final String MANIFEST_RESOURCE = "zeroecho/pki/profiles/v1/catalog.json"; private static final String PROFILE_RESOURCE_ROOT = "zeroecho/pki/profiles/v1/"; - private static final int MAXIMUM_RESOURCE_BYTES = - CertificateProfileDocumentCodec.MAXIMUM_DOCUMENT_BYTES; + private static final int MAXIMUM_RESOURCE_BYTES = CertificateProfileDocumentCodec.MAXIMUM_DOCUMENT_BYTES; private static final int MAXIMUM_MANIFEST_ENTRIES = 128; private static final int MAXIMUM_MANIFEST_DEPTH = 16; private static final int MAXIMUM_MANIFEST_STRING_BYTES = 4_096; @@ -65,11 +63,10 @@ public final class BuiltInCertificateProfileCatalog { private static final String FIELD_SCHEMA_VERSION = "schemaVersion"; private static final String FIELD_PROFILES = "profiles"; private static final String FIELD_RESOURCE = "resource"; - private static final String ERROR_PREFIX = - "Built-in certificate profile catalogue rejected: code="; - private static final Set EXPECTED_PROFILE_IDS = - Set.of("server-tls", "vpn-server", "vpn-client", "email-signing", - "root-ca", "intermediate-ca"); + private static final String ERROR_PREFIX = "Built-in certificate profile catalogue rejected: code="; + private static final Set EXPECTED_PROFILE_IDS = Set.of("server-tls", "vpn-server", "vpn-client", + "email-signing", "root-ca", "intermediate-ca"); + private BuiltInCertificateProfileCatalog() { } @@ -98,15 +95,13 @@ public final class BuiltInCertificateProfileCatalog { } } - private static BuiltInCertificateProfileTemplate loadTemplate(ClassLoader classLoader, - String resourceName) { + private static BuiltInCertificateProfileTemplate loadTemplate(ClassLoader classLoader, String resourceName) { byte[] resourceBytes = readUniqueResource(classLoader, resourceName); CertificateProfileDefinition definition = parseProfile(resourceBytes); byte[] canonicalJson = canonicalize(definition); requireCanonicalResource(resourceBytes, canonicalJson); requireCanonicalRoundTrip(definition, canonicalJson); - return new BuiltInCertificateProfileTemplate(definition, canonicalJson, - sha256(canonicalJson), resourceName); + return new BuiltInCertificateProfileTemplate(definition, canonicalJson, sha256(canonicalJson), resourceName); } private static void requireCanonicalResource(byte[] resourceBytes, byte[] canonicalJson) { @@ -115,17 +110,15 @@ public final class BuiltInCertificateProfileCatalog { } } - private static void requireCanonicalRoundTrip(CertificateProfileDefinition definition, - byte[] canonicalJson) { + private static void requireCanonicalRoundTrip(CertificateProfileDefinition definition, byte[] canonicalJson) { if (!definition.equals(parseProfile(canonicalJson))) { throw failure("PROFILE_CANONICALIZATION_INVALID"); } } private static void validateResourceName(String resourceName) { - if (resourceName == null || resourceName.isEmpty() - || resourceName.startsWith("/") || resourceName.contains("..") - || resourceName.indexOf('\\') >= 0 || resourceName.indexOf(':') >= 0 + if (resourceName == null || resourceName.isEmpty() || resourceName.startsWith("/") + || resourceName.contains("..") || resourceName.indexOf('\\') >= 0 || resourceName.indexOf(':') >= 0 || !resourceName.startsWith(PROFILE_RESOURCE_ROOT) || resourceName.length() <= PROFILE_RESOURCE_ROOT.length()) { throw failure("MANIFEST_RESOURCE_INVALID"); @@ -156,8 +149,7 @@ public final class BuiltInCertificateProfileCatalog { } private static byte[] readBounded(InputStream input) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream( - Math.min(READ_BUFFER_BYTES, MAXIMUM_RESOURCE_BYTES)); + ByteArrayOutputStream output = new ByteArrayOutputStream(Math.min(READ_BUFFER_BYTES, MAXIMUM_RESOURCE_BYTES)); byte[] buffer = new byte[READ_BUFFER_BYTES]; int total = 0; while (true) { @@ -236,12 +228,10 @@ public final class BuiltInCertificateProfileCatalog { if (!hashes.add(ByteBuffer.wrap(template.canonicalSha256()))) { throw failure("DUPLICATE_PROFILE_HASH"); } - if (!identities.add(new ProfileIdentity(definition.profileId(), - definition.profileVersion()))) { + if (!identities.add(new ProfileIdentity(definition.profileId(), definition.profileVersion()))) { throw failure("DUPLICATE_PROFILE_IDENTITY"); } - if (definition.profileVersion() != EXPECTED_PROFILE_VERSION - || !profileIds.add(definition.profileId())) { + if (definition.profileVersion() != EXPECTED_PROFILE_VERSION || !profileIds.add(definition.profileId())) { throw failure("BUILT_IN_PROFILE_SET_INVALID"); } switch (definition.certificateType()) { @@ -253,8 +243,7 @@ public final class BuiltInCertificateProfileCatalog { } private List finish() { - if (templates.size() != EXPECTED_PROFILE_IDS.size() - || !profileIds.equals(EXPECTED_PROFILE_IDS) + if (templates.size() != EXPECTED_PROFILE_IDS.size() || !profileIds.equals(EXPECTED_PROFILE_IDS) || endEntityCount != 4 || rootCount != 1 || intermediateCount != 1) { throw failure("BUILT_IN_PROFILE_SET_INVALID"); } @@ -271,32 +260,23 @@ public final class BuiltInCertificateProfileCatalog { if (encoded.length == 0) { throw failure("MANIFEST_EMPTY"); } - try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), - encoded, 0, encoded.length)) { + try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), encoded, 0, encoded.length)) { ManifestDocument document = readDocument(parser); requireEndOfInput(parser); return validate(document); } catch (StreamConstraintsException ex) { throw failure("MANIFEST_LIMIT_EXCEEDED"); } catch (JacksonException ex) { - throw failure(isDuplicateFailure(ex) - ? "MANIFEST_DUPLICATE_FIELD" : "MANIFEST_MALFORMED"); + throw failure(isDuplicateFailure(ex) ? "MANIFEST_DUPLICATE_FIELD" : "MANIFEST_MALFORMED"); } } private static JsonFactory createJsonFactory() { - StreamReadConstraints constraints = StreamReadConstraints.builder() - .maxNestingDepth(MAXIMUM_MANIFEST_DEPTH) - .maxDocumentLength(MAXIMUM_RESOURCE_BYTES) - .maxTokenCount(1_024) - .maxNumberLength(10) - .maxStringLength(MAXIMUM_MANIFEST_STRING_BYTES) - .maxNameLength(32) - .build(); - JsonFactoryBuilder builder = JsonFactory.builder() - .streamReadConstraints(constraints) - .enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION) - .disable(StreamReadFeature.AUTO_CLOSE_SOURCE); + StreamReadConstraints constraints = StreamReadConstraints.builder().maxNestingDepth(MAXIMUM_MANIFEST_DEPTH) + .maxDocumentLength(MAXIMUM_RESOURCE_BYTES).maxTokenCount(1_024).maxNumberLength(10) + .maxStringLength(MAXIMUM_MANIFEST_STRING_BYTES).maxNameLength(32).build(); + JsonFactoryBuilder builder = JsonFactory.builder().streamReadConstraints(constraints) + .enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION).disable(StreamReadFeature.AUTO_CLOSE_SOURCE); for (JsonReadFeature feature : JsonReadFeature.values()) { builder.disable(feature); } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileTemplate.java b/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileTemplate.java index d3b3091..cfc3db5 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileTemplate.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/BuiltInCertificateProfileTemplate.java @@ -81,12 +81,10 @@ public final class BuiltInCertificateProfileTemplate { @Override public boolean equals(Object other) { - return this == other - || other instanceof BuiltInCertificateProfileTemplate template - && definition.equals(template.definition) - && Arrays.equals(canonicalJson, template.canonicalJson) - && Arrays.equals(canonicalSha256, template.canonicalSha256) - && resourceName.equals(template.resourceName); + return this == other || other instanceof BuiltInCertificateProfileTemplate template + && definition.equals(template.definition) && Arrays.equals(canonicalJson, template.canonicalJson) + && Arrays.equals(canonicalSha256, template.canonicalSha256) + && resourceName.equals(template.resourceName); } @Override @@ -98,8 +96,7 @@ public final class BuiltInCertificateProfileTemplate { @Override public String toString() { - return "BuiltInCertificateProfileTemplate[profileId=" + definition.profileId() - + ", profileVersion=" + definition.profileVersion() - + ", resourceName=" + resourceName + "]"; + return "BuiltInCertificateProfileTemplate[profileId=" + definition.profileId() + ", profileVersion=" + + definition.profileVersion() + ", resourceName=" + resourceName + "]"; } } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/CaCertificatePolicy.java b/pki/src/main/java/zeroecho/pki/api/profile/CaCertificatePolicy.java index 684c078..e24598b 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/CaCertificatePolicy.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/CaCertificatePolicy.java @@ -25,15 +25,13 @@ import java.util.Set; * @param allowedSubjectKeyAlgorithmIds exact canonical ZeroEcho subject-key * algorithm identifiers */ -public record CaCertificatePolicy(boolean basicConstraintsCritical, int pathLengthConstraint, - boolean keyUsageCritical, Set keyUsages, - Set allowedSubjectKeyAlgorithmIds) implements CertificatePolicy { +public record CaCertificatePolicy(boolean basicConstraintsCritical, int pathLengthConstraint, boolean keyUsageCritical, + Set keyUsages, Set allowedSubjectKeyAlgorithmIds) implements CertificatePolicy { /** Maximum supported path-length constraint. */ public static final int MAXIMUM_PATH_LENGTH = 32; - private static final Set SUPPORTED_SUBJECT_KEY_ALGORITHMS = - Set.of("RSA", "ECDSA", "Ed25519", "Ed448"); + private static final Set SUPPORTED_SUBJECT_KEY_ALGORITHMS = Set.of("RSA", "ECDSA", "Ed25519", "Ed448"); /** Validates and constructs the CA policy. */ public CaCertificatePolicy { @@ -51,8 +49,7 @@ public record CaCertificatePolicy(boolean basicConstraintsCritical, int pathLeng } keyUsages = Set.copyOf(keyUsages); allowedSubjectKeyAlgorithmIds = Set.copyOf(allowedSubjectKeyAlgorithmIds); - if (!keyUsages.contains(CaKeyUsage.KEY_CERT_SIGN) - || !keyUsages.contains(CaKeyUsage.CRL_SIGN)) { + if (!keyUsages.contains(CaKeyUsage.KEY_CERT_SIGN) || !keyUsages.contains(CaKeyUsage.CRL_SIGN)) { throw new IllegalArgumentException("CA Key Usage must permit certificate and CRL signing"); } if (allowedSubjectKeyAlgorithmIds.isEmpty() diff --git a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfile.java b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfile.java index f679d68..7085754 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfile.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfile.java @@ -41,8 +41,8 @@ import zeroecho.pki.api.FormatId; * Defines issuance constraints and mapping hints for a class of credentials. * *

    - * A profile is referenced by {@code profileId} during issuance. It defines - * the complete deny-by-default identity and extension policy for an end-entity + * A profile is referenced by {@code profileId} during issuance. It defines the + * complete deny-by-default identity and extension policy for an end-entity * credential. Requester fields absent from the profile are forbidden. *

    * @@ -50,15 +50,15 @@ import zeroecho.pki.api.FormatId; * Profiles must not include secrets. *

    * - * @param profileId stable profile identifier - * @param formatId framework/format supported by the profile - * @param displayName human-readable name - * @param maximumValidity positive maximum validity - * @param subjectPolicy complete end-entity subject policy - * @param leafPolicy complete end-entity extension policy + * @param profileId stable profile identifier + * @param formatId framework/format supported by the profile + * @param displayName human-readable name + * @param maximumValidity positive maximum validity + * @param subjectPolicy complete end-entity subject policy + * @param leafPolicy complete end-entity extension policy */ -public record CertificateProfile(String profileId, FormatId formatId, String displayName, - Duration maximumValidity, SubjectPolicy subjectPolicy, LeafCertificatePolicy leafPolicy) { +public record CertificateProfile(String profileId, FormatId formatId, String displayName, Duration maximumValidity, + SubjectPolicy subjectPolicy, LeafCertificatePolicy leafPolicy) { /** * Creates a certificate profile. @@ -76,8 +76,8 @@ public record CertificateProfile(String profileId, FormatId formatId, String dis if (displayName == null || displayName.isBlank()) { throw new IllegalArgumentException("displayName must not be null/blank"); } - if (maximumValidity == null || maximumValidity.isZero() || maximumValidity.isNegative() - || subjectPolicy == null || leafPolicy == null) { + if (maximumValidity == null || maximumValidity.isZero() || maximumValidity.isNegative() || subjectPolicy == null + || leafPolicy == null) { throw new IllegalArgumentException("profile policies and maximum validity must be valid"); } } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDefinition.java b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDefinition.java index c8cbcce..e3cfc5f 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDefinition.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDefinition.java @@ -17,19 +17,18 @@ import zeroecho.pki.api.FormatId; * Runtime activation state is deliberately excluded. *

    * - * @param certificateType kind of certificate governed by the profile - * @param profileId stable profile identifier - * @param profileVersion positive configuration version - * @param formatId framework/format identifier - * @param displayName human-readable profile name - * @param maximumValidity positive maximum validity - * @param subjectPolicy complete subject policy + * @param certificateType kind of certificate governed by the profile + * @param profileId stable profile identifier + * @param profileVersion positive configuration version + * @param formatId framework/format identifier + * @param displayName human-readable profile name + * @param maximumValidity positive maximum validity + * @param subjectPolicy complete subject policy * @param certificatePolicy closed certificate-specific policy variant */ -public record CertificateProfileDefinition(CertificateProfileKind certificateType, - String profileId, long profileVersion, FormatId formatId, String displayName, - Duration maximumValidity, SubjectPolicy subjectPolicy, - CertificatePolicy certificatePolicy) { +public record CertificateProfileDefinition(CertificateProfileKind certificateType, String profileId, + long profileVersion, FormatId formatId, String displayName, Duration maximumValidity, + SubjectPolicy subjectPolicy, CertificatePolicy certificatePolicy) { /** Current certificate-profile document schema version. */ public static final int SCHEMA_VERSION = 2; @@ -37,8 +36,8 @@ public record CertificateProfileDefinition(CertificateProfileKind certificateTyp /** * Creates a certificate-profile definition. * - * @throws IllegalArgumentException if a required value is absent or the - * profile version is not positive + * @throws IllegalArgumentException if a required value is absent or the profile + * version is not positive */ public CertificateProfileDefinition { if (certificateType == null) { diff --git a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodec.java b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodec.java index 3b59767..5f773c0 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodec.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodec.java @@ -44,8 +44,8 @@ import zeroecho.pki.api.PkiException; */ // The closed streaming grammar intentionally keeps all bounded field handlers in one codec. @SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "PMD.AvoidInstantiatingObjectsInLoops", - "PMD.AvoidUncheckedExceptionsInSignatures", "PMD.CyclomaticComplexity", - "PMD.PreserveStackTrace", "PMD.TooManyMethods" }) + "PMD.AvoidUncheckedExceptionsInSignatures", "PMD.CyclomaticComplexity", "PMD.PreserveStackTrace", + "PMD.TooManyMethods" }) public final class CertificateProfileDocumentCodec { /** Maximum accepted encoded document size. */ @@ -88,8 +88,8 @@ public final class CertificateProfileDocumentCodec { throw failure("MALFORMED_JSON", "$"); } preflight(encodedDocument); - try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), - encodedDocument, 0, encodedDocument.length)) { + try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), encodedDocument, 0, + encodedDocument.length)) { CertificateProfileDefinition definition = parseDocument(parser); if (parser.nextToken() != null) { throw failure("MALFORMED_JSON", "$"); @@ -106,9 +106,9 @@ public final class CertificateProfileDocumentCodec { * Reads and parses an encoded certificate-profile document. * *

    - * This method does not close {@code input}; ownership remains with the - * caller. It reads at most one byte beyond the document limit in order to - * detect oversize input without unbounded buffering. + * This method does not close {@code input}; ownership remains with the caller. + * It reads at most one byte beyond the document limit in order to detect + * oversize input without unbounded buffering. *

    * * @param input caller-owned stream containing UTF-8 JSON @@ -119,8 +119,7 @@ public final class CertificateProfileDocumentCodec { if (input == null) { throw failure("WRONG_TYPE", "$"); } - ByteArrayOutputStream output = new ByteArrayOutputStream( - Math.min(READ_BUFFER_BYTES, MAXIMUM_DOCUMENT_BYTES)); + ByteArrayOutputStream output = new ByteArrayOutputStream(Math.min(READ_BUFFER_BYTES, MAXIMUM_DOCUMENT_BYTES)); byte[] buffer = new byte[READ_BUFFER_BYTES]; int total = 0; try { @@ -163,8 +162,7 @@ public final class CertificateProfileDocumentCodec { public static byte[] writeCanonical(CertificateProfileDefinition definition) { validateDefinition(definition); ByteArrayOutputStream output = new ByteArrayOutputStream(2_048); - try (JsonGenerator generator = JSON_FACTORY.createGenerator(ObjectWriteContext.empty(), - output)) { + try (JsonGenerator generator = JSON_FACTORY.createGenerator(ObjectWriteContext.empty(), output)) { writeDocument(generator, definition); } catch (JacksonException ex) { throw failure("CANONICALIZATION_FAILED", "$"); @@ -177,18 +175,11 @@ public final class CertificateProfileDocumentCodec { } private static JsonFactory createJsonFactory() { - StreamReadConstraints constraints = StreamReadConstraints.builder() - .maxNestingDepth(MAXIMUM_DEPTH) - .maxDocumentLength(MAXIMUM_DOCUMENT_BYTES) - .maxTokenCount(8_192) - .maxNumberLength(20) - .maxStringLength(MAXIMUM_STRING_UTF8_BYTES) - .maxNameLength(64) - .build(); - JsonFactoryBuilder builder = JsonFactory.builder() - .streamReadConstraints(constraints) - .enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION) - .disable(StreamReadFeature.AUTO_CLOSE_SOURCE); + StreamReadConstraints constraints = StreamReadConstraints.builder().maxNestingDepth(MAXIMUM_DEPTH) + .maxDocumentLength(MAXIMUM_DOCUMENT_BYTES).maxTokenCount(8_192).maxNumberLength(20) + .maxStringLength(MAXIMUM_STRING_UTF8_BYTES).maxNameLength(64).build(); + JsonFactoryBuilder builder = JsonFactory.builder().streamReadConstraints(constraints) + .enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION).disable(StreamReadFeature.AUTO_CLOSE_SOURCE); for (JsonReadFeature feature : JsonReadFeature.values()) { builder.disable(feature); } @@ -196,8 +187,8 @@ public final class CertificateProfileDocumentCodec { } private static void preflight(byte[] encodedDocument) { - try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), - encodedDocument, 0, encodedDocument.length)) { + try (JsonParser parser = JSON_FACTORY.createParser(ObjectReadContext.empty(), encodedDocument, 0, + encodedDocument.length)) { while (parser.nextToken() != null) { parser.finishToken(); } @@ -224,12 +215,9 @@ public final class CertificateProfileDocumentCodec { if (document.profileVersion <= 0) { throw failure("PROFILE_VERSION_INVALID", "$.profileVersion"); } - validateProfileString(document.profileId, MAXIMUM_PROFILE_ID_UTF8_BYTES, "$.profileId", - "TOKEN_INVALID"); - validateProfileString(document.formatId, MAXIMUM_FORMAT_ID_UTF8_BYTES, "$.formatId", - "TOKEN_INVALID"); - validateProfileString(document.displayName, MAXIMUM_DISPLAY_NAME_UTF8_BYTES, "$.displayName", - "TOKEN_INVALID"); + validateProfileString(document.profileId, MAXIMUM_PROFILE_ID_UTF8_BYTES, "$.profileId", "TOKEN_INVALID"); + validateProfileString(document.formatId, MAXIMUM_FORMAT_ID_UTF8_BYTES, "$.formatId", "TOKEN_INVALID"); + validateProfileString(document.displayName, MAXIMUM_DISPLAY_NAME_UTF8_BYTES, "$.displayName", "TOKEN_INVALID"); requirePolicyFields(document.certificateType, document.seen); return constructDefinition(document); } @@ -259,8 +247,7 @@ public final class CertificateProfileDocumentCodec { } case "displayName" -> { document.seen = mark(document.seen, 5, "$.displayName"); - document.displayName = - readBoundedString(parser, "$.displayName", MAXIMUM_DISPLAY_NAME_UTF8_BYTES); + document.displayName = readBoundedString(parser, "$.displayName", MAXIMUM_DISPLAY_NAME_UTF8_BYTES); } case "maxValidity" -> { document.seen = mark(document.seen, 6, "$.maxValidity"); @@ -277,28 +264,23 @@ public final class CertificateProfileDocumentCodec { } } - private static void readDocumentSan(JsonParser parser, DocumentFields document) - throws JacksonException { - if (document.certificateType != null - && document.certificateType != CertificateProfileKind.END_ENTITY) { + private static void readDocumentSan(JsonParser parser, DocumentFields document) throws JacksonException { + if (document.certificateType != null && document.certificateType != CertificateProfileKind.END_ENTITY) { throw failure("FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE", "$.subjectAlternativeNames"); } document.seen = mark(document.seen, 8, "$.subjectAlternativeNames"); document.san = readSan(parser, "$.subjectAlternativeNames"); } - private static void readDocumentLeaf(JsonParser parser, DocumentFields document) - throws JacksonException { - if (document.certificateType != null - && document.certificateType != CertificateProfileKind.END_ENTITY) { + private static void readDocumentLeaf(JsonParser parser, DocumentFields document) throws JacksonException { + if (document.certificateType != null && document.certificateType != CertificateProfileKind.END_ENTITY) { throw failure("FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE", "$.leafCertificate"); } document.seen = mark(document.seen, 9, "$.leafCertificate"); document.leaf = readLeaf(parser, "$.leafCertificate"); } - private static void readDocumentCa(JsonParser parser, DocumentFields document) - throws JacksonException { + private static void readDocumentCa(JsonParser parser, DocumentFields document) throws JacksonException { if (document.certificateType == CertificateProfileKind.END_ENTITY) { throw failure("FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE", "$.caCertificate"); } @@ -328,33 +310,30 @@ public final class CertificateProfileDocumentCodec { } /* - * Typed policy constructors are the authoritative semantic validators, so - * their validation exceptions are deliberately normalized at this boundary. + * Typed policy constructors are the authoritative semantic validators, so their + * validation exceptions are deliberately normalized at this boundary. */ @SuppressWarnings("PMD.ExceptionAsFlowControl") private static CertificateProfileDefinition constructDefinition(DocumentFields document) { try { - SubjectPolicy subjectPolicy = new SubjectPolicy(document.subject.allowEmpty(), - document.subject.rules()); + SubjectPolicy subjectPolicy = new SubjectPolicy(document.subject.allowEmpty(), document.subject.rules()); CertificatePolicy policy; if (document.certificateType == CertificateProfileKind.END_ENTITY) { - SubjectAlternativeNamePolicy sanPolicy = new SubjectAlternativeNamePolicy( - document.san.minimumTotal(), document.san.maximumTotal(), document.san.rules(), - document.san.wildcardAllowed(), document.san.allowedSchemes(), - document.san.criticalWhenSubjectNonEmpty(), + SubjectAlternativeNamePolicy sanPolicy = new SubjectAlternativeNamePolicy(document.san.minimumTotal(), + document.san.maximumTotal(), document.san.rules(), document.san.wildcardAllowed(), + document.san.allowedSchemes(), document.san.criticalWhenSubjectNonEmpty(), document.san.serviceIdentityRequired(), document.san.emailIdentityRequired()); if (document.subject.allowEmpty() && document.san.minimumTotal() < 1) { throw new IllegalArgumentException("An empty subject requires at least one SAN"); } policy = new LeafCertificatePolicy(sanPolicy, document.leaf.keyUsage(), document.leaf.extendedKeyUsage(), document.leaf.keyUsageCritical(), - document.leaf.extendedKeyUsageCritical(), - document.leaf.basicConstraintsCritical(), + document.leaf.extendedKeyUsageCritical(), document.leaf.basicConstraintsCritical(), document.leaf.allowedKeyAlgorithms()); } else { policy = new CaCertificatePolicy(document.ca.basicConstraintsCritical(), - document.ca.pathLengthConstraint(), document.ca.keyUsageCritical(), - document.ca.keyUsages(), document.ca.allowedKeyAlgorithms()); + document.ca.pathLengthConstraint(), document.ca.keyUsageCritical(), document.ca.keyUsages(), + document.ca.allowedKeyAlgorithms()); } return new CertificateProfileDefinition(document.certificateType, document.profileId, document.profileVersion, new FormatId(document.formatId), document.displayName, @@ -500,8 +479,8 @@ public final class CertificateProfileDocumentCodec { } } requireAll(seen, 6, path); - return new SanSection(minimum, maximum, serviceRequired, emailRequired, critical, - rules.rules(), rules.wildcardAllowed(), rules.allowedSchemes()); + return new SanSection(minimum, maximum, serviceRequired, emailRequired, critical, rules.rules(), + rules.wildcardAllowed(), rules.allowedSchemes()); } private static SanRules readSanRules(JsonParser parser, String path) throws JacksonException { @@ -572,8 +551,7 @@ public final class CertificateProfileDocumentCodec { SubjectAlternativeNameType type = parseSanType(typeValue, path + ".type"); validateSanShape(type, seen, path); try { - SubjectAlternativeNameRule rule = new SubjectAlternativeNameRule(type, minimum, maximum, - ipv4, ipv6); + SubjectAlternativeNameRule rule = new SubjectAlternativeNameRule(type, minimum, maximum, ipv4, ipv6); return new ParsedSanRule(rule, wildcard, schemes == null ? Set.of() : schemes); } catch (IllegalArgumentException ex) { throw failure("SEMANTIC_INVALID", path); @@ -601,10 +579,8 @@ public final class CertificateProfileDocumentCodec { Set values = new LinkedHashSet<>(); while (parser.nextToken() != JsonToken.END_ARRAY) { checkArrayBound(values.size(), path); - String value = readBoundedString(parser, path + "[" + values.size() + "]", - MAXIMUM_URI_SCHEME_ASCII_BYTES); - if (!isAscii(value) || !value.equals(value.toLowerCase(java.util.Locale.ROOT)) - || !values.add(value)) { + String value = readBoundedString(parser, path + "[" + values.size() + "]", MAXIMUM_URI_SCHEME_ASCII_BYTES); + if (!isAscii(value) || !value.equals(value.toLowerCase(java.util.Locale.ROOT)) || !values.add(value)) { throw failure("TOKEN_INVALID", path); } } @@ -653,8 +629,7 @@ public final class CertificateProfileDocumentCodec { } } requireAll(seen, 6, path); - return new LeafSection(basicCritical, keyCritical, keyUsage, extendedCritical, extended, - algorithms); + return new LeafSection(basicCritical, keyCritical, keyUsage, extendedCritical, extended, algorithms); } private static Set readKeyUsages(JsonParser parser, String path) throws JacksonException { @@ -716,8 +691,7 @@ public final class CertificateProfileDocumentCodec { return new CaSection(basicCritical, pathLength, keyCritical, keyUsages, algorithms); } - private static Set readCaKeyUsages(JsonParser parser, String path) - throws JacksonException { + private static Set readCaKeyUsages(JsonParser parser, String path) throws JacksonException { requireToken(parser.currentToken(), JsonToken.START_ARRAY, path); Set values = EnumSet.noneOf(CaKeyUsage.class); while (parser.nextToken() != JsonToken.END_ARRAY) { @@ -793,16 +767,14 @@ public final class CertificateProfileDocumentCodec { generator.writeEndObject(); } - private static void writeSubject(JsonGenerator generator, SubjectPolicy policy) - throws JacksonException { + private static void writeSubject(JsonGenerator generator, SubjectPolicy policy) throws JacksonException { generator.writeObjectPropertyStart("subject"); generator.writeBooleanProperty("allowEmpty", policy.allowEmpty()); generator.writeArrayPropertyStart("rules"); for (SubjectRdnRule rule : policy.rules()) { generator.writeStartObject(); generator.writeStringProperty("oid", rule.type().oid()); - generator.writeStringProperty("source", - rule.requesterSupplied() ? REQUESTER_SOURCE : PROFILE_FIXED_SOURCE); + generator.writeStringProperty("source", rule.requesterSupplied() ? REQUESTER_SOURCE : PROFILE_FIXED_SOURCE); generator.writeNumberProperty("minimumOccurrences", rule.minimumOccurrences()); generator.writeNumberProperty("maximumOccurrences", rule.maximumOccurrences()); generator.writeNumberProperty("maximumUtf8Bytes", rule.maximumUtf8Bytes()); @@ -815,15 +787,13 @@ public final class CertificateProfileDocumentCodec { generator.writeEndObject(); } - private static void writeSan(JsonGenerator generator, SubjectAlternativeNamePolicy policy) - throws JacksonException { + private static void writeSan(JsonGenerator generator, SubjectAlternativeNamePolicy policy) throws JacksonException { generator.writeObjectPropertyStart("subjectAlternativeNames"); generator.writeNumberProperty("minimumTotal", policy.minimumTotal()); generator.writeNumberProperty("maximumTotal", policy.maximumTotal()); generator.writeBooleanProperty("serviceIdentityRequired", policy.requireServiceIdentity()); generator.writeBooleanProperty("emailIdentityRequired", policy.requireEmailIdentity()); - generator.writeBooleanProperty("criticalWhenSubjectNonEmpty", - policy.criticalWithNonemptySubject()); + generator.writeBooleanProperty("criticalWhenSubjectNonEmpty", policy.criticalWithNonemptySubject()); generator.writeArrayPropertyStart("rules"); List rules = policy.rules().stream() .sorted(Comparator.comparing(rule -> rule.type().name())).toList(); @@ -833,14 +803,12 @@ public final class CertificateProfileDocumentCodec { generator.writeNumberProperty("minimumOccurrences", rule.minimum()); generator.writeNumberProperty("maximumOccurrences", rule.maximum()); switch (rule.type()) { - case DNS_NAME -> generator.writeBooleanProperty("wildcardAllowed", - policy.allowDnsWildcard()); + case DNS_NAME -> generator.writeBooleanProperty("wildcardAllowed", policy.allowDnsWildcard()); case IP_ADDRESS -> { generator.writeBooleanProperty("ipv4Allowed", rule.allowIpv4()); generator.writeBooleanProperty("ipv6Allowed", rule.allowIpv6()); } - case URI -> writeSortedStrings(generator, "allowedSchemes", - policy.allowedUriSchemes()); + case URI -> writeSortedStrings(generator, "allowedSchemes", policy.allowedUriSchemes()); case RFC822_NAME -> { // No type-specific fields. } @@ -851,39 +819,30 @@ public final class CertificateProfileDocumentCodec { generator.writeEndObject(); } - private static void writeLeaf(JsonGenerator generator, LeafCertificatePolicy policy) - throws JacksonException { + private static void writeLeaf(JsonGenerator generator, LeafCertificatePolicy policy) throws JacksonException { generator.writeObjectPropertyStart("leafCertificate"); - generator.writeBooleanProperty("basicConstraintsCritical", - policy.basicConstraintsCritical()); + generator.writeBooleanProperty("basicConstraintsCritical", policy.basicConstraintsCritical()); generator.writeBooleanProperty("keyUsageCritical", policy.keyUsageCritical()); - writeSortedStrings(generator, "keyUsage", - policy.keyUsages().stream().map(Enum::name).toList()); - generator.writeBooleanProperty("extendedKeyUsageCritical", - policy.extendedKeyUsageCritical()); + writeSortedStrings(generator, "keyUsage", policy.keyUsages().stream().map(Enum::name).toList()); + generator.writeBooleanProperty("extendedKeyUsageCritical", policy.extendedKeyUsageCritical()); writeSortedStrings(generator, "extendedKeyUsage", policy.extendedKeyUsages().stream().map(ExtendedKeyUsageId::oid).toList()); - writeSortedStrings(generator, "allowedKeyAlgorithms", - policy.allowedSubjectKeyAlgorithmIds()); + writeSortedStrings(generator, "allowedKeyAlgorithms", policy.allowedSubjectKeyAlgorithmIds()); generator.writeEndObject(); } - private static void writeCa(JsonGenerator generator, CaCertificatePolicy policy) - throws JacksonException { + private static void writeCa(JsonGenerator generator, CaCertificatePolicy policy) throws JacksonException { generator.writeObjectPropertyStart("caCertificate"); - generator.writeBooleanProperty("basicConstraintsCritical", - policy.basicConstraintsCritical()); + generator.writeBooleanProperty("basicConstraintsCritical", policy.basicConstraintsCritical()); generator.writeNumberProperty("pathLengthConstraint", policy.pathLengthConstraint()); generator.writeBooleanProperty("keyUsageCritical", policy.keyUsageCritical()); - writeSortedStrings(generator, "keyUsages", - policy.keyUsages().stream().map(Enum::name).toList()); - writeSortedStrings(generator, "allowedSubjectKeyAlgorithms", - policy.allowedSubjectKeyAlgorithmIds()); + writeSortedStrings(generator, "keyUsages", policy.keyUsages().stream().map(Enum::name).toList()); + writeSortedStrings(generator, "allowedSubjectKeyAlgorithms", policy.allowedSubjectKeyAlgorithmIds()); generator.writeEndObject(); } - private static void writeSortedStrings(JsonGenerator generator, String field, - java.util.Collection values) throws JacksonException { + private static void writeSortedStrings(JsonGenerator generator, String field, java.util.Collection values) + throws JacksonException { generator.writeArrayPropertyStart(field); for (String value : values.stream().sorted().toList()) { generator.writeString(value); @@ -895,20 +854,19 @@ public final class CertificateProfileDocumentCodec { if (definition == null) { throw failure("CANONICALIZATION_FAILED", "$"); } - validateProfileString(definition.profileId(), MAXIMUM_PROFILE_ID_UTF8_BYTES, - "$.profileId", "CANONICALIZATION_FAILED"); - validateProfileString(definition.formatId().value(), MAXIMUM_FORMAT_ID_UTF8_BYTES, - "$.formatId", "CANONICALIZATION_FAILED"); - validateProfileString(definition.displayName(), MAXIMUM_DISPLAY_NAME_UTF8_BYTES, - "$.displayName", "CANONICALIZATION_FAILED"); - validateWritableString(definition.maximumValidity().toString(), MAXIMUM_STRING_UTF8_BYTES, - "$.maxValidity"); + validateProfileString(definition.profileId(), MAXIMUM_PROFILE_ID_UTF8_BYTES, "$.profileId", + "CANONICALIZATION_FAILED"); + validateProfileString(definition.formatId().value(), MAXIMUM_FORMAT_ID_UTF8_BYTES, "$.formatId", + "CANONICALIZATION_FAILED"); + validateProfileString(definition.displayName(), MAXIMUM_DISPLAY_NAME_UTF8_BYTES, "$.displayName", + "CANONICALIZATION_FAILED"); + validateWritableString(definition.maximumValidity().toString(), MAXIMUM_STRING_UTF8_BYTES, "$.maxValidity"); if (definition.subjectPolicy().rules().size() > MAXIMUM_ARRAY_ELEMENTS) { throw failure("CANONICALIZATION_FAILED", "$"); } for (SubjectRdnRule rule : definition.subjectPolicy().rules()) { - rule.fixedValue().ifPresent(value -> validateWritableString(value, - MAXIMUM_STRING_UTF8_BYTES, "$.subject.rules.fixedValue")); + rule.fixedValue().ifPresent( + value -> validateWritableString(value, MAXIMUM_STRING_UTF8_BYTES, "$.subject.rules.fixedValue")); } if (definition.certificateType() != CertificateProfileKind.END_ENTITY) { CaCertificatePolicy ca = definition.caPolicy(); @@ -920,8 +878,7 @@ public final class CertificateProfileDocumentCodec { } LeafCertificatePolicy leaf = definition.leafPolicy(); if (leaf.subjectAlternativeNamePolicy().rules().size() > MAXIMUM_ARRAY_ELEMENTS - || leaf.subjectAlternativeNamePolicy().allowedUriSchemes().size() - > MAXIMUM_ARRAY_ELEMENTS + || leaf.subjectAlternativeNamePolicy().allowedUriSchemes().size() > MAXIMUM_ARRAY_ELEMENTS || leaf.keyUsages().size() > MAXIMUM_ARRAY_ELEMENTS || leaf.extendedKeyUsages().size() > MAXIMUM_ARRAY_ELEMENTS || leaf.allowedSubjectKeyAlgorithmIds().size() > MAXIMUM_ARRAY_ELEMENTS) { @@ -931,13 +888,11 @@ public final class CertificateProfileDocumentCodec { validateWritableString(scheme, MAXIMUM_URI_SCHEME_ASCII_BYTES, "$.subjectAlternativeNames.rules.allowedSchemes"); if (!isAscii(scheme)) { - throw failure("CANONICALIZATION_FAILED", - "$.subjectAlternativeNames.rules.allowedSchemes"); + throw failure("CANONICALIZATION_FAILED", "$.subjectAlternativeNames.rules.allowedSchemes"); } } for (ExtendedKeyUsageId usage : leaf.extendedKeyUsages()) { - validateWritableString(usage.oid(), MAXIMUM_STRING_UTF8_BYTES, - "$.leafCertificate.extendedKeyUsage"); + validateWritableString(usage.oid(), MAXIMUM_STRING_UTF8_BYTES, "$.leafCertificate.extendedKeyUsage"); } } @@ -947,10 +902,9 @@ public final class CertificateProfileDocumentCodec { } } - private static void validateProfileString(String value, int maximum, String path, - String failureCode) { - if (value == null || value.isBlank() || !value.equals(value.strip()) - || hasUnpairedSurrogate(value) || utf8Length(value) > maximum) { + private static void validateProfileString(String value, int maximum, String path, String failureCode) { + if (value == null || value.isBlank() || !value.equals(value.strip()) || hasUnpairedSurrogate(value) + || utf8Length(value) > maximum) { throw failure(failureCode, path); } } @@ -1021,8 +975,7 @@ public final class CertificateProfileDocumentCodec { } } - private static CertificateProfileKind readCertificateType(JsonParser parser, String path) - throws JacksonException { + private static CertificateProfileKind readCertificateType(JsonParser parser, String path) throws JacksonException { String token = readString(parser, path); try { return CertificateProfileKind.valueOf(token); @@ -1102,8 +1055,8 @@ public final class CertificateProfileDocumentCodec { } private static boolean hasUnpairedSurrogate(String value) { - return value.codePoints().anyMatch(codePoint -> - codePoint >= Character.MIN_SURROGATE && codePoint <= Character.MAX_SURROGATE); + return value.codePoints() + .anyMatch(codePoint -> codePoint >= Character.MIN_SURROGATE && codePoint <= Character.MAX_SURROGATE); } private static boolean isAscii(String value) { @@ -1116,13 +1069,11 @@ public final class CertificateProfileDocumentCodec { } private static boolean isAllowedAlgorithm(String value) { - return "RSA".equals(value) || "ECDSA".equals(value) - || "Ed25519".equals(value) || "Ed448".equals(value); + return "RSA".equals(value) || "ECDSA".equals(value) || "Ed25519".equals(value) || "Ed448".equals(value); } private static boolean hasUtf8Bom(byte[] value) { - return value.length >= 3 && value[0] == (byte) 0xef - && value[1] == (byte) 0xbb && value[2] == (byte) 0xbf; + return value.length >= 3 && value[0] == (byte) 0xef && value[1] == (byte) 0xbb && value[2] == (byte) 0xbf; } private static boolean isDuplicateFailure(JacksonException exception) { @@ -1154,26 +1105,23 @@ public final class CertificateProfileDocumentCodec { } private record SanSection(int minimumTotal, int maximumTotal, boolean serviceIdentityRequired, - boolean emailIdentityRequired, boolean criticalWhenSubjectNonEmpty, - List rules, boolean wildcardAllowed, - Set allowedSchemes) { + boolean emailIdentityRequired, boolean criticalWhenSubjectNonEmpty, List rules, + boolean wildcardAllowed, Set allowedSchemes) { } private record SanRules(List rules, boolean wildcardAllowed, Set allowedSchemes) { } - private record ParsedSanRule(SubjectAlternativeNameRule rule, boolean wildcardAllowed, - Set allowedSchemes) { + private record ParsedSanRule(SubjectAlternativeNameRule rule, boolean wildcardAllowed, Set allowedSchemes) { } - private record LeafSection(boolean basicConstraintsCritical, boolean keyUsageCritical, - Set keyUsage, boolean extendedKeyUsageCritical, - Set extendedKeyUsage, Set allowedKeyAlgorithms) { - } - - private record CaSection(boolean basicConstraintsCritical, int pathLengthConstraint, - boolean keyUsageCritical, Set keyUsages, + private record LeafSection(boolean basicConstraintsCritical, boolean keyUsageCritical, Set keyUsage, + boolean extendedKeyUsageCritical, Set extendedKeyUsage, Set allowedKeyAlgorithms) { } + + private record CaSection(boolean basicConstraintsCritical, int pathLengthConstraint, boolean keyUsageCritical, + Set keyUsages, Set allowedKeyAlgorithms) { + } } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileRef.java b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileRef.java index 6c9c8c4..a4b4aa9 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileRef.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/CertificateProfileRef.java @@ -23,8 +23,8 @@ public final class CertificateProfileRef { /** * Creates an exact profile reference. * - * @param profileId logical profile identifier - * @param profileVersion positive profile version + * @param profileId logical profile identifier + * @param profileVersion positive profile version * @param canonicalSha256 SHA-256 of the canonical JSON document */ public CertificateProfileRef(String profileId, long profileVersion, byte[] canonicalSha256) { @@ -69,10 +69,8 @@ public final class CertificateProfileRef { @Override public boolean equals(Object other) { - return this == other || other instanceof CertificateProfileRef ref - && profileVersion == ref.profileVersion - && profileId.equals(ref.profileId) - && MessageDigest.isEqual(canonicalSha256, ref.canonicalSha256); + return this == other || other instanceof CertificateProfileRef ref && profileVersion == ref.profileVersion + && profileId.equals(ref.profileId) && MessageDigest.isEqual(canonicalSha256, ref.canonicalSha256); } @Override @@ -85,7 +83,7 @@ public final class CertificateProfileRef { @Override public String toString() { - return "CertificateProfileRef[profileId=" + profileId + ", profileVersion=" + profileVersion - + ", fingerprint=" + shortFingerprint() + "]"; + return "CertificateProfileRef[profileId=" + profileId + ", profileVersion=" + profileVersion + ", fingerprint=" + + shortFingerprint() + "]"; } } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/ImportedCertificateProfileVersion.java b/pki/src/main/java/zeroecho/pki/api/profile/ImportedCertificateProfileVersion.java index e8c1b19..428e2d2 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/ImportedCertificateProfileVersion.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/ImportedCertificateProfileVersion.java @@ -21,11 +21,11 @@ public final class ImportedCertificateProfileVersion { /** * Creates an immutable imported version. * - * @param reference exact version reference + * @param reference exact version reference * @param schemaVersion document schema version - * @param definition validated semantic definition + * @param definition validated semantic definition * @param canonicalJson canonical JSON document - * @param importedAt authoritative import time + * @param importedAt authoritative import time */ public ImportedCertificateProfileVersion(CertificateProfileRef reference, int schemaVersion, CertificateProfileDefinition definition, byte[] canonicalJson, Instant importedAt) { @@ -74,12 +74,10 @@ public final class ImportedCertificateProfileVersion { @Override public boolean equals(Object other) { - return this == other || other instanceof ImportedCertificateProfileVersion version - && schemaVersion == version.schemaVersion - && reference.equals(version.reference) - && definition.equals(version.definition) - && importedAt.equals(version.importedAt) - && Arrays.equals(canonicalJson, version.canonicalJson); + return this == other + || other instanceof ImportedCertificateProfileVersion version && schemaVersion == version.schemaVersion + && reference.equals(version.reference) && definition.equals(version.definition) + && importedAt.equals(version.importedAt) && Arrays.equals(canonicalJson, version.canonicalJson); } @Override diff --git a/pki/src/main/java/zeroecho/pki/api/profile/LeafCertificatePolicy.java b/pki/src/main/java/zeroecho/pki/api/profile/LeafCertificatePolicy.java index 0e5021f..181e114 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/LeafCertificatePolicy.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/LeafCertificatePolicy.java @@ -9,29 +9,28 @@ import java.util.Set; /** * Complete issuer-controlled leaf certificate extension and identity policy. * - * @param subjectAlternativeNamePolicy SAN policy + * @param subjectAlternativeNamePolicy SAN policy * @param keyUsages exact key-usage set * @param extendedKeyUsages exact extended-key-usage set * @param keyUsageCritical key-usage criticality * @param extendedKeyUsageCritical extended-key-usage criticality * @param basicConstraintsCritical BasicConstraints criticality - * @param allowedSubjectKeyAlgorithmIds exact canonical ZeroEcho key algorithm identifiers + * @param allowedSubjectKeyAlgorithmIds exact canonical ZeroEcho key algorithm + * identifiers */ public record LeafCertificatePolicy(SubjectAlternativeNamePolicy subjectAlternativeNamePolicy, - Set keyUsages, - Set extendedKeyUsages, boolean keyUsageCritical, boolean extendedKeyUsageCritical, - boolean basicConstraintsCritical, - Set allowedSubjectKeyAlgorithmIds) implements CertificatePolicy { + Set keyUsages, Set extendedKeyUsages, boolean keyUsageCritical, + boolean extendedKeyUsageCritical, boolean basicConstraintsCritical, Set allowedSubjectKeyAlgorithmIds) + implements CertificatePolicy { - private static final Set SUPPORTED_SUBJECT_KEY_ALGORITHMS = - Set.of("RSA", "ECDSA", "Ed25519", "Ed448"); + private static final Set SUPPORTED_SUBJECT_KEY_ALGORITHMS = Set.of("RSA", "ECDSA", "Ed25519", "Ed448"); /** * Validates and constructs the policy. */ public LeafCertificatePolicy { - if (subjectAlternativeNamePolicy == null || keyUsages == null - || extendedKeyUsages == null || allowedSubjectKeyAlgorithmIds == null) { + if (subjectAlternativeNamePolicy == null || keyUsages == null || extendedKeyUsages == null + || allowedSubjectKeyAlgorithmIds == null) { throw new IllegalArgumentException("Leaf certificate policy values must not be null"); } keyUsages = Set.copyOf(keyUsages); diff --git a/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNamePolicy.java b/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNamePolicy.java index db78980..f8b8890 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNamePolicy.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNamePolicy.java @@ -11,18 +11,20 @@ import java.util.Set; /** * Deny-by-default Subject Alternative Name policy. * - * @param minimumTotal minimum total SAN count - * @param maximumTotal maximum total SAN count - * @param rules permitted SAN type rules - * @param allowDnsWildcard whether a complete leftmost DNS wildcard is permitted - * @param allowedUriSchemes exact lowercase allowed URI schemes - * @param criticalWithNonemptySubject SAN criticality for a nonempty subject - * @param requireServiceIdentity whether DNS, IP, or URI identity is required - * @param requireEmailIdentity whether an RFC822 identity is required + * @param minimumTotal minimum total SAN count + * @param maximumTotal maximum total SAN count + * @param rules permitted SAN type rules + * @param allowDnsWildcard whether a complete leftmost DNS wildcard + * is permitted + * @param allowedUriSchemes exact lowercase allowed URI schemes + * @param criticalWithNonemptySubject SAN criticality for a nonempty subject + * @param requireServiceIdentity whether DNS, IP, or URI identity is + * required + * @param requireEmailIdentity whether an RFC822 identity is required */ -public record SubjectAlternativeNamePolicy(int minimumTotal, int maximumTotal, - List rules, boolean allowDnsWildcard, Set allowedUriSchemes, - boolean criticalWithNonemptySubject, boolean requireServiceIdentity, boolean requireEmailIdentity) { +public record SubjectAlternativeNamePolicy(int minimumTotal, int maximumTotal, List rules, + boolean allowDnsWildcard, Set allowedUriSchemes, boolean criticalWithNonemptySubject, + boolean requireServiceIdentity, boolean requireEmailIdentity) { /** Maximum number of SAN entries. */ public static final int HARD_MAXIMUM_COUNT = 64; @@ -53,8 +55,7 @@ public record SubjectAlternativeNamePolicy(int minimumTotal, int maximumTotal, serviceIdentityPossible |= rule.maximum() > 0 && (rule.type() == SubjectAlternativeNameType.DNS_NAME || rule.type() == SubjectAlternativeNameType.IP_ADDRESS || rule.type() == SubjectAlternativeNameType.URI); - emailIdentityPossible |= rule.maximum() > 0 - && rule.type() == SubjectAlternativeNameType.RFC822_NAME; + emailIdentityPossible |= rule.maximum() > 0 && rule.type() == SubjectAlternativeNameType.RFC822_NAME; uriPossible |= rule.maximum() > 0 && rule.type() == SubjectAlternativeNameType.URI; } Set schemes = new java.util.LinkedHashSet<>(); @@ -69,8 +70,7 @@ public record SubjectAlternativeNamePolicy(int minimumTotal, int maximumTotal, } allowedUriSchemes = Set.copyOf(schemes); if (configuredMinimum > maximumTotal || configuredMaximum < minimumTotal - || requireServiceIdentity && !serviceIdentityPossible - || requireEmailIdentity && !emailIdentityPossible + || requireServiceIdentity && !serviceIdentityPossible || requireEmailIdentity && !emailIdentityPossible || uriPossible != !allowedUriSchemes.isEmpty()) { throw new IllegalArgumentException("SAN policy requirements are not satisfiable"); } diff --git a/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNameRule.java b/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNameRule.java index daa493c..b42ea74 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNameRule.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/SubjectAlternativeNameRule.java @@ -7,20 +7,23 @@ package zeroecho.pki.api.profile; /** * Occurrence and IP-family rule for one supported SAN type. * - * @param type SAN type - * @param minimum minimum occurrences - * @param maximum maximum occurrences - * @param allowIpv4 whether IPv4 is allowed for {@link SubjectAlternativeNameType#IP_ADDRESS} - * @param allowIpv6 whether IPv6 is allowed for {@link SubjectAlternativeNameType#IP_ADDRESS} + * @param type SAN type + * @param minimum minimum occurrences + * @param maximum maximum occurrences + * @param allowIpv4 whether IPv4 is allowed for + * {@link SubjectAlternativeNameType#IP_ADDRESS} + * @param allowIpv6 whether IPv6 is allowed for + * {@link SubjectAlternativeNameType#IP_ADDRESS} */ -public record SubjectAlternativeNameRule(SubjectAlternativeNameType type, int minimum, int maximum, - boolean allowIpv4, boolean allowIpv6) { +public record SubjectAlternativeNameRule(SubjectAlternativeNameType type, int minimum, int maximum, boolean allowIpv4, + boolean allowIpv6) { /** * Validates and constructs the rule. */ public SubjectAlternativeNameRule { - if (type == null || minimum < 0 || maximum < minimum || maximum > SubjectAlternativeNamePolicy.HARD_MAXIMUM_COUNT) { + if (type == null || minimum < 0 || maximum < minimum + || maximum > SubjectAlternativeNamePolicy.HARD_MAXIMUM_COUNT) { throw new IllegalArgumentException("Invalid SAN type rule"); } if (type == SubjectAlternativeNameType.IP_ADDRESS) { diff --git a/pki/src/main/java/zeroecho/pki/api/profile/SubjectRdnRule.java b/pki/src/main/java/zeroecho/pki/api/profile/SubjectRdnRule.java index 8542828..79aa3e6 100644 --- a/pki/src/main/java/zeroecho/pki/api/profile/SubjectRdnRule.java +++ b/pki/src/main/java/zeroecho/pki/api/profile/SubjectRdnRule.java @@ -10,15 +10,15 @@ import java.util.Optional; /** * Immutable occurrence and ownership rule for one supported subject RDN type. * - * @param type exact supported RDN type - * @param minimumOccurrences minimum number of occurrences - * @param maximumOccurrences maximum number of occurrences - * @param maximumUtf8Bytes maximum encoded value length, at most 256 bytes - * @param fixedValue optional profile-fixed value - * @param requesterSupplied whether the requester may supply the value + * @param type exact supported RDN type + * @param minimumOccurrences minimum number of occurrences + * @param maximumOccurrences maximum number of occurrences + * @param maximumUtf8Bytes maximum encoded value length, at most 256 bytes + * @param fixedValue optional profile-fixed value + * @param requesterSupplied whether the requester may supply the value */ -public record SubjectRdnRule(SubjectRdnType type, int minimumOccurrences, int maximumOccurrences, - int maximumUtf8Bytes, Optional fixedValue, boolean requesterSupplied) { +public record SubjectRdnRule(SubjectRdnType type, int minimumOccurrences, int maximumOccurrences, int maximumUtf8Bytes, + Optional fixedValue, boolean requesterSupplied) { /** Maximum hard value size. */ public static final int HARD_MAXIMUM_UTF8_BYTES = 256; diff --git a/pki/src/main/java/zeroecho/pki/api/request/ParsedCertificationRequest.java b/pki/src/main/java/zeroecho/pki/api/request/ParsedCertificationRequest.java index daa30c3..b4935f9 100644 --- a/pki/src/main/java/zeroecho/pki/api/request/ParsedCertificationRequest.java +++ b/pki/src/main/java/zeroecho/pki/api/request/ParsedCertificationRequest.java @@ -58,21 +58,27 @@ import zeroecho.pki.api.attr.AttributeSet; * issuance. *

    * - * @param requestId stable identifier for the request (typically - * derived from the request payload fingerprint) - * @param formatId framework/format identifier - * @param subjectRef normalized subject reference for policy and - * inventory correlation - * @param publicKeyInfo requested public key information (SPKI DER - * preferred where applicable) - * @param requestedValidity optional validity requested by the subject; policy - * may override or deny - * @param requestedProfileId optional profile hint; policy may override or deny - * @param subjectRdns ordered typed single-valued subject components - * @param subjectAlternativeNames ordered typed SAN entries - * @param subjectAlternativeNamePresent whether one valid SAN extension was present - * @param attributes proof-carrying parser attributes; issuance policy rejects all - * attributes except the canonical CSR transport attribute + * @param requestId stable identifier for the request + * (typically derived from the request + * payload fingerprint) + * @param formatId framework/format identifier + * @param subjectRef normalized subject reference for policy + * and inventory correlation + * @param publicKeyInfo requested public key information (SPKI + * DER preferred where applicable) + * @param requestedValidity optional validity requested by the + * subject; policy may override or deny + * @param requestedProfileId optional profile hint; policy may + * override or deny + * @param subjectRdns ordered typed single-valued subject + * components + * @param subjectAlternativeNames ordered typed SAN entries + * @param subjectAlternativeNamePresent whether one valid SAN extension was + * present + * @param attributes proof-carrying parser attributes; + * issuance policy rejects all attributes + * except the canonical CSR transport + * attribute */ public record ParsedCertificationRequest(PkiId requestId, FormatId formatId, SubjectRef subjectRef, EncodedObject publicKeyInfo, Optional requestedValidity, Optional requestedProfileId, @@ -88,13 +94,13 @@ public record ParsedCertificationRequest(PkiId requestId, FormatId formatId, Sub * constructor cannot bypass typed subject or SAN validation. *

    * - * @param requestId request identifier - * @param formatId format identifier - * @param subjectRef diagnostic subject reference - * @param publicKeyInfo public-key information - * @param requestedValidity requested validity + * @param requestId request identifier + * @param formatId format identifier + * @param subjectRef diagnostic subject reference + * @param publicKeyInfo public-key information + * @param requestedValidity requested validity * @param requestedProfileId requested profile - * @param attributes diagnostic attributes + * @param attributes diagnostic attributes */ public ParsedCertificationRequest(PkiId requestId, FormatId formatId, SubjectRef subjectRef, EncodedObject publicKeyInfo, Optional requestedValidity, Optional requestedProfileId, diff --git a/pki/src/main/java/zeroecho/pki/api/request/SubjectAlternativeName.java b/pki/src/main/java/zeroecho/pki/api/request/SubjectAlternativeName.java index 2df2ca0..571219f 100644 --- a/pki/src/main/java/zeroecho/pki/api/request/SubjectAlternativeName.java +++ b/pki/src/main/java/zeroecho/pki/api/request/SubjectAlternativeName.java @@ -19,8 +19,8 @@ import zeroecho.pki.api.profile.SubjectAlternativeNameType; /** * Closed immutable canonical Subject Alternative Name representation. */ -public sealed interface SubjectAlternativeName permits SubjectAlternativeName.DnsName, - SubjectAlternativeName.IpAddress, SubjectAlternativeName.UriName, SubjectAlternativeName.Rfc822Name { +public sealed interface SubjectAlternativeName permits SubjectAlternativeName.DnsName, SubjectAlternativeName.IpAddress, + SubjectAlternativeName.UriName, SubjectAlternativeName.Rfc822Name { /** IPv4 octet count. */ int IPV4_OCTET_COUNT = 4; /** Maximum IPv4 octet value. */ @@ -36,7 +36,8 @@ public sealed interface SubjectAlternativeName permits SubjectAlternativeName.Dn /** * Canonical DNS A-label name. * - * @param value lowercase ASCII DNS name, optionally with a complete leftmost wildcard + * @param value lowercase ASCII DNS name, optionally with a complete leftmost + * wildcard */ record DnsName(String value) implements SubjectAlternativeName { /** Maximum encoded DNS name length. */ diff --git a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationCommand.java b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationCommand.java index f063e5d..8cf241d 100644 --- a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationCommand.java +++ b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationCommand.java @@ -33,7 +33,7 @@ public sealed interface RevocationCommand * Places a credential on hold. * * @param credentialId credential identifier - * @param attributes safe administrative metadata + * @param attributes safe administrative metadata */ record Hold(PkiId credentialId, AttributeSet attributes) implements RevocationCommand { /** Validates and snapshots the command. */ @@ -47,7 +47,7 @@ public sealed interface RevocationCommand * Removes an existing hold. * * @param credentialId credential identifier - * @param attributes safe administrative metadata + * @param attributes safe administrative metadata */ record Unhold(PkiId credentialId, AttributeSet attributes) implements RevocationCommand { /** Validates and snapshots the command. */ @@ -61,8 +61,8 @@ public sealed interface RevocationCommand * Permanently revokes a credential. * * @param credentialId credential identifier - * @param reason permanent revocation reason - * @param attributes safe administrative metadata + * @param reason permanent revocation reason + * @param attributes safe administrative metadata */ record RevokePermanently(PkiId credentialId, RevocationReason reason, AttributeSet attributes) implements RevocationCommand { diff --git a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationJournal.java b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationJournal.java index 536a88c..9b9298a 100644 --- a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationJournal.java +++ b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationJournal.java @@ -21,7 +21,7 @@ import zeroecho.pki.api.attr.AttributeValue; * Immutable single-file authority for one credential's revocation lifecycle. * * @param credentialId credential namespace identity - * @param transitions ordered committed transitions + * @param transitions ordered committed transitions */ public record RevocationJournal(PkiId credentialId, List transitions) { @@ -69,8 +69,8 @@ public record RevocationJournal(PkiId credentialId, List t || transition.state() == RevocationState.PERMANENTLY_REVOKED; } return switch (previous) { - case CLEAR -> transition.state() == RevocationState.HELD - || transition.state() == RevocationState.PERMANENTLY_REVOKED; + case CLEAR -> + transition.state() == RevocationState.HELD || transition.state() == RevocationState.PERMANENTLY_REVOKED; case HELD -> transition.state() == RevocationState.CLEAR || transition.state() == RevocationState.PERMANENTLY_REVOKED; case PERMANENTLY_REVOKED -> false; diff --git a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationTransition.java b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationTransition.java index e9fcbab..d0bf318 100644 --- a/pki/src/main/java/zeroecho/pki/api/revocation/RevocationTransition.java +++ b/pki/src/main/java/zeroecho/pki/api/revocation/RevocationTransition.java @@ -12,11 +12,11 @@ import zeroecho.pki.api.attr.AttributeSet; /** * One committed transition in an authoritative revocation journal. * - * @param revision positive contiguous revision - * @param state resulting state - * @param time authoritative transition time + * @param revision positive contiguous revision + * @param state resulting state + * @param time authoritative transition time * @param permanentReason permanent reason only for permanent state - * @param attributes safe administrative metadata + * @param attributes safe administrative metadata */ public record RevocationTransition(long revision, RevocationState state, Instant time, Optional permanentReason, AttributeSet attributes) { diff --git a/pki/src/main/java/zeroecho/pki/impl/ProfileLifecycleFailure.java b/pki/src/main/java/zeroecho/pki/impl/ProfileLifecycleFailure.java index 196be45..6aff2dc 100644 --- a/pki/src/main/java/zeroecho/pki/impl/ProfileLifecycleFailure.java +++ b/pki/src/main/java/zeroecho/pki/impl/ProfileLifecycleFailure.java @@ -14,21 +14,10 @@ public final class ProfileLifecycleFailure extends PkiException { /** Closed profile lifecycle failure codes. */ public enum Code { - PROFILE_DOCUMENT_INVALID, - PROFILE_IMPORT_VALIDATION_FAILED, - BUILT_IN_PROFILE_INVALID, - PROFILE_VERSION_CONFLICT, - PROFILE_KIND_CONFLICT, - PROFILE_VERSION_CORRUPT, - PROFILE_ACTIVE_POINTER_CORRUPT, - PROFILE_HASH_MISMATCH, - PROFILE_STORE_FAILURE, - PROFILE_DURABILITY_UNCONFIRMED, - PROFILE_IMPORT_FAILED, - PROFILE_STATE_CORRUPT, - PROFILE_VERSION_NOT_FOUND, - PROFILE_ACTIVATION_FAILED, - PROFILE_NOT_ACTIVE, + PROFILE_DOCUMENT_INVALID, PROFILE_IMPORT_VALIDATION_FAILED, BUILT_IN_PROFILE_INVALID, PROFILE_VERSION_CONFLICT, + PROFILE_KIND_CONFLICT, PROFILE_VERSION_CORRUPT, PROFILE_ACTIVE_POINTER_CORRUPT, PROFILE_HASH_MISMATCH, + PROFILE_STORE_FAILURE, PROFILE_DURABILITY_UNCONFIRMED, PROFILE_IMPORT_FAILED, PROFILE_STATE_CORRUPT, + PROFILE_VERSION_NOT_FOUND, PROFILE_ACTIVATION_FAILED, PROFILE_NOT_ACTIVE, // Stable public code is intentionally longer than PMD's naming threshold. @SuppressWarnings("PMD.LongVariable") PROFILE_ACTIVATION_HISTORY_UNAVAILABLE diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CaCertificateProfileValidator.java b/pki/src/main/java/zeroecho/pki/impl/core/CaCertificateProfileValidator.java index 34b668a..edc3376 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CaCertificateProfileValidator.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CaCertificateProfileValidator.java @@ -38,8 +38,8 @@ import zeroecho.pki.impl.framework.x509.bc.BcX509ProfileSupport; */ // The closed gate deliberately keeps cohesive profile inputs together and // redacts attacker-controlled parser failures rather than retaining their causes. -@SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.ExcessiveParameterList", - "PMD.PreserveStackTrace", "PMD.AvoidRethrowingException" }) +@SuppressWarnings({ "PMD.CyclomaticComplexity", "PMD.ExcessiveParameterList", "PMD.PreserveStackTrace", + "PMD.AvoidRethrowingException" }) final class CaCertificateProfileValidator { private static final int MAXIMUM_SUBJECT_DER_BYTES = 16 * 1024; private static final int SINGLE_VALUE = 1; @@ -47,11 +47,11 @@ final class CaCertificateProfileValidator { private CaCertificateProfileValidator() { } - /* package */ static ValidatedCaCertificateRequest validate( - ValidatedCaCertificateRequest.Operation operation, ActiveCertificateProfile activeProfile, - CertificateProfileKind expectedKind, FormatId formatId, PkiId issuerCaId, PkiId subjectCaId, - SubjectRef requestedSubject, EncodedObject exactPublicKey, Optional requestedValidity, - Instant evaluationTime, Optional issuerNotAfter, BigInteger serial) { + /* package */ static ValidatedCaCertificateRequest validate(ValidatedCaCertificateRequest.Operation operation, + ActiveCertificateProfile activeProfile, CertificateProfileKind expectedKind, FormatId formatId, + PkiId issuerCaId, PkiId subjectCaId, SubjectRef requestedSubject, EncodedObject exactPublicKey, + Optional requestedValidity, Instant evaluationTime, Optional issuerNotAfter, + BigInteger serial) { CertificateProfileDefinition definition = activeProfile.definition(); requireProfileShape(definition, expectedKind, formatId); List approvedSubject = validateSubject(requestedSubject, definition); @@ -78,8 +78,8 @@ final class CaCertificateProfileValidator { operation == ValidatedCaCertificateRequest.Operation.IMPORT_ROOT); SubjectRef canonicalSubject = new SubjectRef(BcX509ProfileSupport.subject(subjectSnapshot).toString()); return new ValidatedCaCertificateRequest(operation, formatId, issuerCaId, subjectCaId, - activeProfile.reference(), expectedKind, canonicalSubject, subjectSnapshot, exactPublicKey, - validity, serial, definition.caPolicy()); + activeProfile.reference(), expectedKind, canonicalSubject, subjectSnapshot, exactPublicKey, validity, + serial, definition.caPolicy()); } /* package */ static void requireProfileShape(ActiveCertificateProfile activeProfile, @@ -175,8 +175,7 @@ final class CaCertificateProfileValidator { counts.put(rdn.type(), count); if (rule.requesterSupplied()) { requesterValues.add(rdn); - } else if (rule.fixedValue().isEmpty() - || !rule.fixedValue().orElseThrow().equals(rdn.value())) { + } else if (rule.fixedValue().isEmpty() || !rule.fixedValue().orElseThrow().equals(rdn.value())) { throw reject("CA_SUBJECT_FIXED_VALUE_MISMATCH"); } } @@ -230,16 +229,14 @@ final class CaCertificateProfileValidator { } } - private static Validity approvedValidity(Optional requested, - CertificateProfileDefinition definition, Instant evaluationTime, Optional issuerNotAfter, - boolean imported) { + private static Validity approvedValidity(Optional requested, CertificateProfileDefinition definition, + Instant evaluationTime, Optional issuerNotAfter, boolean imported) { try { Instant ceiling = evaluationTime.plus(definition.maximumValidity()); if (issuerNotAfter.isPresent() && issuerNotAfter.orElseThrow().isBefore(ceiling)) { ceiling = issuerNotAfter.orElseThrow(); } - Validity validity = requested.isPresent() ? requested.orElseThrow() - : new Validity(evaluationTime, ceiling); + Validity validity = requested.isPresent() ? requested.orElseThrow() : new Validity(evaluationTime, ceiling); boolean invalidStart = imported ? validity.notBefore().isAfter(evaluationTime) : !validity.notBefore().equals(evaluationTime); if (invalidStart || !validity.notAfter().isAfter(evaluationTime) diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CaProofGate.java b/pki/src/main/java/zeroecho/pki/impl/core/CaProofGate.java index 47856f4..6313ffe 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CaProofGate.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CaProofGate.java @@ -77,8 +77,8 @@ final class CaProofGate { private static final Principal SYSTEM_PKI = new Principal("SYSTEM", "pki"); private static final Purpose CA_ISSUANCE_PURPOSE = new Purpose("CA_ISSUANCE"); - private static final byte[] MANAGED_KEY_CHALLENGE_DOMAIN = - "ZeroEcho/PKI/managed-key-possession/v1\0".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + private static final byte[] MANAGED_KEY_CHALLENGE_DOMAIN = "ZeroEcho/PKI/managed-key-possession/v1\0" + .getBytes(java.nio.charset.StandardCharsets.US_ASCII); private static final int CHALLENGE_NONCE_BYTES = 32; private static final SecureRandom CHALLENGE_RANDOM = new SecureRandom(); @@ -265,8 +265,7 @@ final class CaProofGate { PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(accessContext, algorithmId, payload, keyRef, Encoding.BINARY, Optional.empty()); try { - bus.submitSign(opId, owner, keyRef, algorithmId, payload, ttl, - Optional.of(continuation.encode())); + bus.submitSign(opId, owner, keyRef, algorithmId, payload, ttl, Optional.of(continuation.encode())); } catch (RuntimeException failure) { // NOPMD - delete state if submission partially persisted it deletePreservingFailure(opId); throw failure; @@ -330,7 +329,8 @@ final class CaProofGate { try { bus.retireSignOperation(opId, "failed-or-expired"); } catch (RuntimeException cleanupFailure) { // NOPMD - preserve primary failure - // The primary safe failure remains authoritative; provider diagnostics are discarded. + // The primary safe failure remains authoritative; provider diagnostics are + // discarded. } } diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CertificateProfileValidator.java b/pki/src/main/java/zeroecho/pki/impl/core/CertificateProfileValidator.java index 460d048..bfd0b86 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CertificateProfileValidator.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CertificateProfileValidator.java @@ -58,8 +58,8 @@ final class CertificateProfileValidator { } /* package */ static ValidatedCertificateRequest validate(VerifiedIssuanceCandidate candidate, - CertificateProfile profile, CertificateProfileRef profileReference, - Credential issuerCredential, Instant evaluationTime) { + CertificateProfile profile, CertificateProfileRef profileReference, Credential issuerCredential, + Instant evaluationTime) { ParsedCertificationRequest request = candidate.request(); LeafCertificatePolicy policy = profile.leafPolicy(); requireCanonicalRequestAttributes(request); @@ -69,8 +69,8 @@ final class CertificateProfileValidator { Validity validity = approvedValidity(candidate, request, profile, issuerCredential, evaluationTime); boolean sanCritical = approvedSubject.isEmpty() || policy.subjectAlternativeNamePolicy().criticalWithNonemptySubject(); - SubjectRef approvedSubjectRef = new SubjectRef(approvedSubject.isEmpty() - ? "x509:empty-subject" : BcX509ProfileSupport.subject(approvedSubject).toString()); + SubjectRef approvedSubjectRef = new SubjectRef(approvedSubject.isEmpty() ? "x509:empty-subject" + : BcX509ProfileSupport.subject(approvedSubject).toString()); return new ValidatedCertificateRequest(candidate.issuerCaId(), profileReference, approvedSubjectRef, approvedSubject, approvedSans, sanCritical, candidate.exactPublicKey(), validity, policy.keyUsages(), policy.extendedKeyUsages(), policy.keyUsageCritical(), policy.extendedKeyUsageCritical(), @@ -78,16 +78,15 @@ final class CertificateProfileValidator { } private static void requireCanonicalRequestAttributes(ParsedCertificationRequest request) { - if (request.attributes().ids().size() != 1 - || !request.attributes().ids().contains(BcX509Attributes.CSR_DER) - || request.attributes().getAll(BcX509Attributes.CSR_DER).size() != 1 - || !(request.attributes().get(BcX509Attributes.CSR_DER).orElse(null) - instanceof AttributeValue.BytesValue)) { + if (request.attributes().ids().size() != 1 || !request.attributes().ids().contains(BcX509Attributes.CSR_DER) + || request.attributes().getAll(BcX509Attributes.CSR_DER).size() != 1 || !(request.attributes() + .get(BcX509Attributes.CSR_DER).orElse(null) instanceof AttributeValue.BytesValue)) { throw reject("REQUEST_ATTRIBUTE_UNSUPPORTED"); } } - // The branches preserve the deny-by-default RDN ownership and cardinality rules. + // The branches preserve the deny-by-default RDN ownership and cardinality + // rules. @SuppressWarnings("PMD.CyclomaticComplexity") private static List validateSubject(ParsedCertificationRequest request, CertificateProfile profile) { Map rules = new EnumMap<>(SubjectRdnType.class); @@ -140,8 +139,8 @@ final class CertificateProfileValidator { if (sans.size() < sanPolicy.minimumTotal() || sans.size() > sanPolicy.maximumTotal()) { throw reject("SAN_COUNT_INVALID"); } - Map rules = - new EnumMap<>(SubjectAlternativeNameType.class); + Map rules = new EnumMap<>( + SubjectAlternativeNameType.class); for (SubjectAlternativeNameRule rule : sanPolicy.rules()) { rules.put(rule.type(), rule); } @@ -263,8 +262,8 @@ final class CertificateProfileValidator { @SuppressWarnings("PMD.PreserveStackTrace") private static Validity approvedValidity(VerifiedIssuanceCandidate candidate, ParsedCertificationRequest request, CertificateProfile profile, Credential issuerCredential, Instant evaluationTime) { - Optional supplied = candidate.validityOverride().isPresent() - ? candidate.validityOverride() : request.requestedValidity(); + Optional supplied = candidate.validityOverride().isPresent() ? candidate.validityOverride() + : request.requestedValidity(); Duration duration = supplied.map(value -> Duration.between(value.notBefore(), value.notAfter())) .orElse(profile.maximumValidity()); if (duration.isZero() || duration.isNegative() || duration.compareTo(profile.maximumValidity()) > 0) { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CredentialProfileBindings.java b/pki/src/main/java/zeroecho/pki/impl/core/CredentialProfileBindings.java index e9d3478..fbfb6cd 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CredentialProfileBindings.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CredentialProfileBindings.java @@ -23,15 +23,13 @@ final class CredentialProfileBindings { /* default */ static void requireEndEntityBinding(CredentialProfileBinding binding, CertificateProfileRef expected) { - if (!(binding instanceof EndEntityProfileBinding endEntity) - || !endEntity.reference().equals(expected)) { + if (!(binding instanceof EndEntityProfileBinding endEntity) || !endEntity.reference().equals(expected)) { throw mismatch(); } } /* default */ static void requireCaBinding(CredentialProfileBinding binding, CertificateProfileRef expected) { - if (!(binding instanceof CaProfileBinding ca) - || !ca.reference().equals(expected)) { + if (!(binding instanceof CaProfileBinding ca) || !ca.reference().equals(expected)) { throw mismatch(); } } @@ -41,7 +39,8 @@ final class CredentialProfileBindings { } /** - * Internal typed mismatch used to preserve stable caller-specific audit mapping. + * Internal typed mismatch used to preserve stable caller-specific audit + * mapping. */ // Construction and use are restricted to trusted profile-binding callers. /* default */ static final class Mismatch extends PkiException { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CredentialSnapshots.java b/pki/src/main/java/zeroecho/pki/impl/core/CredentialSnapshots.java index aa33471..bf44de5 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CredentialSnapshots.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CredentialSnapshots.java @@ -61,8 +61,7 @@ final class CredentialSnapshots { /* default */ static Credential copy(Credential source) { return new Credential(source.credentialId(), source.formatId(), source.issuerRef(), source.subjectRef(), source.validity(), source.serialOrUniqueId(), source.publicKeyId(), source.profileBinding(), - source.status(), - copy(source.encoded()), copy(source.attributes())); + source.status(), copy(source.encoded()), copy(source.attributes())); } private static EncodedObject copy(EncodedObject source) { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/CredentialTrustAudit.java b/pki/src/main/java/zeroecho/pki/impl/core/CredentialTrustAudit.java index aed9ae6..3a6c19a 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/CredentialTrustAudit.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/CredentialTrustAudit.java @@ -56,13 +56,13 @@ final class CredentialTrustAudit { // Utility class. } - // Best-effort audit callbacks are untrusted; broad catch and empty handling preserve + // Best-effort audit callbacks are untrusted; broad catch and empty handling + // preserve // the authoritative trust rejection without exposing listener diagnostics. @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.EmptyCatchBlock" }) /* default */ static void rejected(AuditSink sink, Instant time, Credential credential, CredentialUse use, String code, EffectiveCredentialStatus status) { - Map details = status == null - ? Map.of("code", code, "operation", use.name()) + Map details = status == null ? Map.of("code", code, "operation", use.name()) : Map.of("code", code, "operation", use.name(), "effectiveStatus", status.name()); try { sink.record(new AuditEvent(time, "CREDENTIAL_TRUST", "CREDENTIAL_REJECTED", SYSTEM_PKI, TRUST_PURPOSE, diff --git a/pki/src/main/java/zeroecho/pki/impl/core/DefaultCaService.java b/pki/src/main/java/zeroecho/pki/impl/core/DefaultCaService.java index 23cf5f9..924109d 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/DefaultCaService.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/DefaultCaService.java @@ -151,8 +151,8 @@ import zeroecho.pki.spi.store.PkiStore; *

    */ // PMD cannot infer that retaining boundary causes would violate the redaction contract. -@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity", - "PMD.ExcessiveParameterList", "PMD.PreserveStackTrace" }) +@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity", "PMD.ExcessiveParameterList", + "PMD.PreserveStackTrace" }) public final class DefaultCaService implements CaService { private static final Logger LOG = Logger.getLogger(DefaultCaService.class.getName()); @@ -288,19 +288,17 @@ public final class DefaultCaService implements CaService { throw new PkiException("Unsupported formatId for this runtime"); } KeyRef keyRef = command.keyRef().get(); - EncodedObject spki = proofGate.resolveManagedKey(keyRef, command.formatId(), - CREATE_ROOT_REJECTED, Optional.empty()); + EncodedObject spki = proofGate.resolveManagedKey(keyRef, command.formatId(), CREATE_ROOT_REJECTED, + Optional.empty()); BigInteger serial = CertificateSerialAllocator.allocate(); ValidatedCaCertificateRequest request = CaCertificateProfileValidator.validate( - ValidatedCaCertificateRequest.Operation.CREATE_ROOT, activeProfile, - CertificateProfileKind.ROOT_CA, - command.formatId(), new PkiId("ca:pending-root"), new PkiId("ca:pending-root"), - command.subjectRef(), spki, Optional.empty(), evaluationTime, Optional.empty(), serial); + ValidatedCaCertificateRequest.Operation.CREATE_ROOT, activeProfile, CertificateProfileKind.ROOT_CA, + command.formatId(), new PkiId("ca:pending-root"), new PkiId("ca:pending-root"), command.subjectRef(), + spki, Optional.empty(), evaluationTime, Optional.empty(), serial); SubjectPublicKeyInfo rootPublicKeyInfo = proofGate.parseRootSpki(spki, command.formatId()); - CaProofGate.ManagedKeyProof proof = proofGate.proveManagedKey(keyRef, command.formatId(), - CREATE_ROOT_REJECTED, Optional.empty()); - requireSameManagedKey(spki, proof.exactPublicKey(), CREATE_ROOT_REJECTED, - command.formatId(), Optional.empty()); + CaProofGate.ManagedKeyProof proof = proofGate.proveManagedKey(keyRef, command.formatId(), CREATE_ROOT_REJECTED, + Optional.empty()); + requireSameManagedKey(spki, proof.exactPublicKey(), CREATE_ROOT_REJECTED, command.formatId(), Optional.empty()); Validity validity = request.validity(); X500Name dn = zeroecho.pki.impl.framework.x509.bc.BcX509ProfileSupport.subject(request.subjectRdns()); @@ -348,8 +346,7 @@ public final class DefaultCaService implements CaService { SimpleAttributeSet.builder().build()); CredentialProfileBindings.requireCaBinding(credential.profileBinding(), request.profileReference()); - requireCaCertificateMatches(credential, credential, request, caId, CREATE_ROOT_REJECTED, - BACKEND_CRED_MISMATCH); + requireCaCertificateMatches(credential, credential, request, caId, CREATE_ROOT_REJECTED, BACKEND_CRED_MISMATCH); CaRecord ca = new CaRecord(caId, CaKind.ROOT, CaState.ACTIVE, keyRef, request.subjectRef(), List.of(credential)); store.putCa(ca); @@ -371,8 +368,8 @@ public final class DefaultCaService implements CaService { *

    * The imported certificate must be a self-issued, validly self-signed CA * certificate whose subject matches the command. The command's managed - * {@link KeyRef} must also complete a signing challenge and resolve to the exact - * certificate SPKI before either record is persisted. + * {@link KeyRef} must also complete a signing challenge and resolve to the + * exact certificate SPKI before either record is persisted. *

    * * @param command root CA import command; must not be {@code null} @@ -424,14 +421,13 @@ public final class DefaultCaService implements CaService { BigInteger serial = holder.getSerialNumber(); EncodedObject spki = new EncodedObject(Encoding.DER, spkiDer); ValidatedCaCertificateRequest request = CaCertificateProfileValidator.validate( - ValidatedCaCertificateRequest.Operation.IMPORT_ROOT, activeProfile, - CertificateProfileKind.ROOT_CA, - command.formatId(), caId, caId, command.subjectRef(), spki, Optional.of(validity), - evaluationTime, Optional.empty(), serial); + ValidatedCaCertificateRequest.Operation.IMPORT_ROOT, activeProfile, CertificateProfileKind.ROOT_CA, + command.formatId(), caId, caId, command.subjectRef(), spki, Optional.of(validity), evaluationTime, + Optional.empty(), serial); Credential credential = new Credential(credId, command.formatId(), new IssuerRef(caId), request.subjectRef(), validity, serial.toString(), publicKeyId, new CaProfileBinding(request.profileReference()), - CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, certDer), SimpleAttributeSet.builder().build()); + CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, certDer), + SimpleAttributeSet.builder().build()); CredentialProfileBindings.requireCaBinding(credential.profileBinding(), request.profileReference()); requireCaCertificateMatches(credential, credential, request, caId, IMPORT_ROOT_REJECTED, ROOT_CREDENTIAL_INVALID); @@ -494,36 +490,33 @@ public final class DefaultCaService implements CaService { throw new PkiException("Issuer CA has no credentials"); } if (!framework.formatId().equals(command.formatId())) { - throw proofGate.rejection(CREATE_INT_REJECTED, command.formatId(), Optional.empty(), - "FORMAT_UNSUPPORTED"); + throw proofGate.rejection(CREATE_INT_REJECTED, command.formatId(), Optional.empty(), "FORMAT_UNSUPPORTED"); } EffectiveCredentialStatusResolver.Evaluation statusEvaluation = statusResolver.beginEvaluation(); Credential issuerCredential = CredentialSnapshots.copy(selectIssuerCredential(issuer, command.formatId(), CredentialUse.INTERMEDIATE_ISSUER, statusEvaluation)); requireHistoricalCaProfile(issuerCredential, - issuer.kind() == CaKind.ROOT ? CertificateProfileKind.ROOT_CA - : CertificateProfileKind.INTERMEDIATE_CA); + issuer.kind() == CaKind.ROOT ? CertificateProfileKind.ROOT_CA : CertificateProfileKind.INTERMEDIATE_CA); - List approvedSubject = - CaCertificateProfileValidator.validateSubject(command.subjectRef(), activeProfile.definition()); + List approvedSubject = CaCertificateProfileValidator + .validateSubject(command.subjectRef(), activeProfile.definition()); SubjectRef canonicalSubject = new SubjectRef( zeroecho.pki.impl.framework.x509.bc.BcX509ProfileSupport.subject(approvedSubject).toString()); PkiId caId = new PkiId("ca:" + sha256Hex((issuer.caId().value() + "\n" + canonicalSubject.value()) .getBytes(java.nio.charset.StandardCharsets.UTF_8)).substring(0, 16)); - EncodedObject subjectSpki = proofGate.resolveManagedKey(command.keyRef().get(), - command.formatId(), CREATE_INT_REJECTED, Optional.of(caId)); + EncodedObject subjectSpki = proofGate.resolveManagedKey(command.keyRef().get(), command.formatId(), + CREATE_INT_REJECTED, Optional.of(caId)); ValidatedCaCertificateRequest issue = CaCertificateProfileValidator.validateApprovedSubject( ValidatedCaCertificateRequest.Operation.CREATE_INTERMEDIATE, activeProfile, - CertificateProfileKind.INTERMEDIATE_CA, command.formatId(), command.issuerCaId(), caId, - approvedSubject, subjectSpki, Optional.empty(), evaluationTime, - Optional.of(issuerCredential.validity().notAfter()), CertificateSerialAllocator.allocate()); - CaProofGate.ManagedKeyProof subjectProof = proofGate.proveManagedKey(command.keyRef().get(), - command.formatId(), CREATE_INT_REJECTED, Optional.of(caId)); - requireSameManagedKey(subjectSpki, subjectProof.exactPublicKey(), CREATE_INT_REJECTED, - command.formatId(), Optional.of(caId)); - requireIssuerKeyBinding(issuer, issuerCredential, command.formatId(), CREATE_INT_REJECTED, + CertificateProfileKind.INTERMEDIATE_CA, command.formatId(), command.issuerCaId(), caId, approvedSubject, + subjectSpki, Optional.empty(), evaluationTime, Optional.of(issuerCredential.validity().notAfter()), + CertificateSerialAllocator.allocate()); + CaProofGate.ManagedKeyProof subjectProof = proofGate.proveManagedKey(command.keyRef().get(), command.formatId(), + CREATE_INT_REJECTED, Optional.of(caId)); + requireSameManagedKey(subjectSpki, subjectProof.exactPublicKey(), CREATE_INT_REJECTED, command.formatId(), Optional.of(caId)); + requireIssuerKeyBinding(issuer, issuerCredential, command.formatId(), CREATE_INT_REJECTED, Optional.of(caId)); Credential backendCredential; try { @@ -533,8 +526,8 @@ public final class DefaultCaService implements CaService { throw proofGate.rejection(CREATE_INT_REJECTED, command.formatId(), Optional.of(caId), BACKEND_CRED_MISMATCH); } - requireCaBinding(backendCredential, issue.profileReference(), CREATE_INT_REJECTED, - command.formatId(), Optional.of(caId)); + requireCaBinding(backendCredential, issue.profileReference(), CREATE_INT_REJECTED, command.formatId(), + Optional.of(caId)); Credential cred; try { cred = CredentialSnapshots.copy(backendCredential); @@ -542,8 +535,7 @@ public final class DefaultCaService implements CaService { throw proofGate.rejection(CREATE_INT_REJECTED, command.formatId(), Optional.of(caId), BACKEND_CRED_MISMATCH); } - requireCaCertificateMatches(cred, issuerCredential, issue, caId, CREATE_INT_REJECTED, - BACKEND_CRED_MISMATCH); + requireCaCertificateMatches(cred, issuerCredential, issue, caId, CREATE_INT_REJECTED, BACKEND_CRED_MISMATCH); store.putCredential(cred); CaRecord subject = new CaRecord(caId, CaKind.INTERMEDIATE, CaState.ACTIVE, command.keyRef().get(), @@ -589,36 +581,32 @@ public final class DefaultCaService implements CaService { CaRecord subject = getCa(command.subjectCaId()); ensureActive(subject, "subject"); if (subject.kind() != CaKind.INTERMEDIATE) { - throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), - Optional.of(subject.caId()), "CA_SUBJECT_KIND_INVALID"); + throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), Optional.of(subject.caId()), + "CA_SUBJECT_KIND_INVALID"); } if (!framework.formatId().equals(command.formatId())) { - throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), - Optional.of(subject.caId()), + throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), Optional.of(subject.caId()), "FORMAT_UNSUPPORTED"); } EffectiveCredentialStatusResolver.Evaluation statusEvaluation = statusResolver.beginEvaluation(); Credential issuerCredential = CredentialSnapshots.copy(selectIssuerCredential(issuer, command.formatId(), CredentialUse.INTERMEDIATE_ISSUER, statusEvaluation)); requireHistoricalCaProfile(issuerCredential, - issuer.kind() == CaKind.ROOT ? CertificateProfileKind.ROOT_CA - : CertificateProfileKind.INTERMEDIATE_CA); + issuer.kind() == CaKind.ROOT ? CertificateProfileKind.ROOT_CA : CertificateProfileKind.INTERMEDIATE_CA); - List approvedSubject = - CaCertificateProfileValidator.validateTrustedSubject(subject.subjectRef(), - activeProfile.definition()); - EncodedObject subjectSpki = proofGate.resolveManagedKey(subject.issuerKeyRef(), - command.formatId(), ISSUE_INT_REJECTED, Optional.of(subject.caId())); + List approvedSubject = CaCertificateProfileValidator + .validateTrustedSubject(subject.subjectRef(), activeProfile.definition()); + EncodedObject subjectSpki = proofGate.resolveManagedKey(subject.issuerKeyRef(), command.formatId(), + ISSUE_INT_REJECTED, Optional.of(subject.caId())); ValidatedCaCertificateRequest gated = CaCertificateProfileValidator.validateApprovedSubject( ValidatedCaCertificateRequest.Operation.ISSUE_INTERMEDIATE, activeProfile, - CertificateProfileKind.INTERMEDIATE_CA, command.formatId(), command.issuerCaId(), - command.subjectCaId(), approvedSubject, subjectSpki, command.requestedValidity(), - evaluationTime, Optional.of(issuerCredential.validity().notAfter()), - CertificateSerialAllocator.allocate()); - CaProofGate.ManagedKeyProof subjectProof = proofGate.proveManagedKey(subject.issuerKeyRef(), - command.formatId(), ISSUE_INT_REJECTED, Optional.of(subject.caId())); - requireSameManagedKey(subjectSpki, subjectProof.exactPublicKey(), ISSUE_INT_REJECTED, - command.formatId(), Optional.of(subject.caId())); + CertificateProfileKind.INTERMEDIATE_CA, command.formatId(), command.issuerCaId(), command.subjectCaId(), + approvedSubject, subjectSpki, command.requestedValidity(), evaluationTime, + Optional.of(issuerCredential.validity().notAfter()), CertificateSerialAllocator.allocate()); + CaProofGate.ManagedKeyProof subjectProof = proofGate.proveManagedKey(subject.issuerKeyRef(), command.formatId(), + ISSUE_INT_REJECTED, Optional.of(subject.caId())); + requireSameManagedKey(subjectSpki, subjectProof.exactPublicKey(), ISSUE_INT_REJECTED, command.formatId(), + Optional.of(subject.caId())); requireIssuerKeyBinding(issuer, issuerCredential, command.formatId(), ISSUE_INT_REJECTED, Optional.of(subject.caId())); @@ -627,17 +615,17 @@ public final class DefaultCaService implements CaService { backendCredential = issuerBackend.issueIntermediateCertificate(gated, issuerCredential.encoded(), issuer.issuerKeyRef()); } catch (RuntimeException ex) { // NOPMD - reject malformed or mutable framework output - throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), - Optional.of(subject.caId()), BACKEND_CRED_MISMATCH); + throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), Optional.of(subject.caId()), + BACKEND_CRED_MISMATCH); } - requireCaBinding(backendCredential, gated.profileReference(), ISSUE_INT_REJECTED, - command.formatId(), Optional.of(subject.caId())); + requireCaBinding(backendCredential, gated.profileReference(), ISSUE_INT_REJECTED, command.formatId(), + Optional.of(subject.caId())); Credential cred; try { cred = CredentialSnapshots.copy(backendCredential); } catch (RuntimeException ex) { // NOPMD - reject malformed or mutable framework output - throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), - Optional.of(subject.caId()), BACKEND_CRED_MISMATCH); + throw proofGate.rejection(ISSUE_INT_REJECTED, command.formatId(), Optional.of(subject.caId()), + BACKEND_CRED_MISMATCH); } requireCaCertificateMatches(cred, issuerCredential, gated, subject.caId(), ISSUE_INT_REJECTED, BACKEND_CRED_MISMATCH); @@ -851,8 +839,7 @@ public final class DefaultCaService implements CaService { X509CertificateHolder holder = new X509CertificateHolder(credential.encoded().bytes()); CaProofGate.ManagedKeyProof proof = proofGate.proveManagedKey(issuer.issuerKeyRef(), formatId, action, objectId); - if (!MessageDigest.isEqual(proof.exactPublicKey().bytes(), - holder.getSubjectPublicKeyInfo().getEncoded())) { + if (!MessageDigest.isEqual(proof.exactPublicKey().bytes(), holder.getSubjectPublicKeyInfo().getEncoded())) { throw proofGate.rejection(action, formatId, objectId, "ISSUER_MANAGED_KEY_MISMATCH"); } } catch (PkiException ex) { @@ -866,8 +853,7 @@ public final class DefaultCaService implements CaService { ValidatedCaCertificateRequest request, PkiId subjectCaId, String action, String mismatchCode) { try { if (!matchesCaCredentialEnvelope(credential, request, subjectCaId)) { - throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), - mismatchCode); + throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), mismatchCode); } X509CertificateHolder holder = new X509CertificateHolder(credential.encoded().bytes()); X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCredential.encoded().bytes()); @@ -878,42 +864,38 @@ public final class DefaultCaService implements CaService { Extension keyUsageExtension = holder.getExtension(Extension.keyUsage); KeyUsage keyUsage = keyUsageExtension == null ? null : KeyUsage.getInstance(keyUsageExtension.getParsedValue()); - X500Name expectedSubject = - zeroecho.pki.impl.framework.x509.bc.BcX509ProfileSupport.subject(request.subjectRdns()); - X500Name expectedIssuer = request.certificateType() == CertificateProfileKind.ROOT_CA - ? expectedSubject : issuerHolder.getSubject(); + X500Name expectedSubject = zeroecho.pki.impl.framework.x509.bc.BcX509ProfileSupport + .subject(request.subjectRdns()); + X500Name expectedIssuer = request.certificateType() == CertificateProfileKind.ROOT_CA ? expectedSubject + : issuerHolder.getSubject(); if (!matchesCaCertificateIdentity(holder, issuerHolder, request, expectedSubject, expectedIssuer, actualSpki) || !matchesCaCertificatePolicy(holder, request, constraintsExtension, constraints, keyUsageExtension, keyUsage) || !matchesCaCredentialMetadata(credential, holder, request, actualSpki)) { - throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), - mismatchCode); + throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), mismatchCode); } CredentialProfileBindings.requireCaBinding(credential.profileBinding(), request.profileReference()); } catch (PkiException ex) { throw ex; } catch (Exception ex) { - throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), - mismatchCode); + throw proofGate.rejection(action, framework.formatId(), Optional.of(subjectCaId), mismatchCode); } } private boolean matchesCaCredentialEnvelope(Credential credential, ValidatedCaCertificateRequest request, PkiId subjectCaId) { - return framework.formatId().equals(credential.formatId()) - && credential.encoded().encoding() == Encoding.DER + return framework.formatId().equals(credential.formatId()) && credential.encoded().encoding() == Encoding.DER && credential.status() == CredentialStatus.ISSUED && credential.subjectRef().equals(request.subjectRef()) - && credential.issuerRef().equals(new IssuerRef( - request.certificateType() == CertificateProfileKind.ROOT_CA - ? subjectCaId : request.issuerCaId())); + && credential.issuerRef() + .equals(new IssuerRef(request.certificateType() == CertificateProfileKind.ROOT_CA ? subjectCaId + : request.issuerCaId())); } private static boolean matchesCaCertificateIdentity(X509CertificateHolder holder, X509CertificateHolder issuerHolder, ValidatedCaCertificateRequest request, X500Name expectedSubject, - X500Name expectedIssuer, byte[] actualSpki) - throws IOException, OperatorCreationException, CertException { + X500Name expectedIssuer, byte[] actualSpki) throws IOException, OperatorCreationException, CertException { return MessageDigest.isEqual(request.exactPublicKey().bytes(), actualSpki) && MessageDigest.isEqual(holder.getSubject().getEncoded(), expectedSubject.getEncoded()) && MessageDigest.isEqual(holder.getIssuer().getEncoded(), expectedIssuer.getEncoded()) @@ -924,20 +906,18 @@ public final class DefaultCaService implements CaService { private static boolean matchesCaCertificatePolicy(X509CertificateHolder holder, ValidatedCaCertificateRequest request, Extension constraintsExtension, BasicConstraints constraints, Extension keyUsageExtension, KeyUsage keyUsage) { - return constraintsExtension != null && constraintsExtension.isCritical() - && constraints != null && constraints.isCA() + return constraintsExtension != null && constraintsExtension.isCritical() && constraints != null + && constraints.isCA() && BigInteger.valueOf(request.policy().pathLengthConstraint()) .equals(constraints.getPathLenConstraint()) - && keyUsageExtension != null && keyUsageExtension.isCritical() - && hasIntermediateKeyUsage(keyUsage) + && keyUsageExtension != null && keyUsageExtension.isCritical() && hasIntermediateKeyUsage(keyUsage) && holder.getExtensions().getExtensionOIDs().length == 2; } private static boolean matchesCaCredentialMetadata(Credential credential, X509CertificateHolder holder, ValidatedCaCertificateRequest request, byte[] actualSpki) { return credential.publicKeyId().equals(new PkiId("spki:" + sha256Hex(actualSpki))) - && credential.credentialId() - .equals(new PkiId("x509:" + sha256Hex(credential.encoded().bytes()))) + && credential.credentialId().equals(new PkiId("x509:" + sha256Hex(credential.encoded().bytes()))) && credential.serialOrUniqueId().equals(holder.getSerialNumber().toString()) && credential.validity().notBefore().getEpochSecond() == holder.getNotBefore().toInstant() .getEpochSecond() @@ -949,11 +929,11 @@ public final class DefaultCaService implements CaService { } private void requireCaBinding(Credential credential, - zeroecho.pki.api.profile.CertificateProfileRef expectedCaProfile, String action, - FormatId formatId, Optional objectId) { + zeroecho.pki.api.profile.CertificateProfileRef expectedCaProfile, String action, FormatId formatId, + Optional objectId) { try { - CredentialProfileBindings.requireCaBinding( - credential == null ? null : credential.profileBinding(), expectedCaProfile); + CredentialProfileBindings.requireCaBinding(credential == null ? null : credential.profileBinding(), + expectedCaProfile); } catch (PkiException mismatch) { throw proofGate.rejection(action, formatId, objectId, CredentialProfileBindings.MISMATCH_CODE); } @@ -963,12 +943,9 @@ public final class DefaultCaService implements CaService { if (keyUsage == null || !keyUsage.hasUsages(KeyUsage.keyCertSign | KeyUsage.cRLSign)) { return false; } - return !keyUsage.hasUsages(KeyUsage.digitalSignature) - && !keyUsage.hasUsages(KeyUsage.nonRepudiation) - && !keyUsage.hasUsages(KeyUsage.keyEncipherment) - && !keyUsage.hasUsages(KeyUsage.dataEncipherment) - && !keyUsage.hasUsages(KeyUsage.keyAgreement) - && !keyUsage.hasUsages(KeyUsage.encipherOnly) + return !keyUsage.hasUsages(KeyUsage.digitalSignature) && !keyUsage.hasUsages(KeyUsage.nonRepudiation) + && !keyUsage.hasUsages(KeyUsage.keyEncipherment) && !keyUsage.hasUsages(KeyUsage.dataEncipherment) + && !keyUsage.hasUsages(KeyUsage.keyAgreement) && !keyUsage.hasUsages(KeyUsage.encipherOnly) && !keyUsage.hasUsages(KeyUsage.decipherOnly); } @@ -993,10 +970,9 @@ public final class DefaultCaService implements CaService { } } - private void requireSameManagedKey(EncodedObject expected, EncodedObject actual, String action, - FormatId formatId, Optional objectId) { - if (expected.encoding() != actual.encoding() - || !MessageDigest.isEqual(expected.bytes(), actual.bytes())) { + private void requireSameManagedKey(EncodedObject expected, EncodedObject actual, String action, FormatId formatId, + Optional objectId) { + if (expected.encoding() != actual.encoding() || !MessageDigest.isEqual(expected.bytes(), actual.bytes())) { throw proofGate.rejection(action, formatId, objectId, "MANAGED_KEY_CHANGED"); } } diff --git a/pki/src/main/java/zeroecho/pki/impl/core/DefaultIssuanceService.java b/pki/src/main/java/zeroecho/pki/impl/core/DefaultIssuanceService.java index 9e8cf8b..d21aa40 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/DefaultIssuanceService.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/DefaultIssuanceService.java @@ -162,21 +162,22 @@ public final class DefaultIssuanceService implements IssuanceService { * Creates the issuance service bound to the supplied persistence and framework * collaborators. * - * @param store PKI store used for issuer CA lookup and credential - * persistence; must not be {@code null} - * @param framework credential framework providing format-specific issuance - * parsing and proof verification; must not be {@code null} - * @param issuerBackend privileged issuer implementation that accepts only - * gate-produced candidates; must not be {@code null} - * @param auditSink required sink for safe rejection audit events; must not be - * {@code null} + * @param store PKI store used for issuer CA lookup and credential + * persistence; must not be {@code null} + * @param framework credential framework providing format-specific issuance + * parsing and proof verification; must not be + * {@code null} + * @param issuerBackend privileged issuer implementation that accepts only + * gate-produced candidates; must not be {@code null} + * @param auditSink required sink for safe rejection audit events; must not + * be {@code null} * @param statusResolver authoritative runtime credential-status resolver; must * not be {@code null} * @throws NullPointerException if an argument is {@code null} */ - public DefaultIssuanceService(PkiStore store, CredentialFramework framework, - CredentialIssuerBackend issuerBackend, AuditSink auditSink, - EffectiveCredentialStatusResolver statusResolver, ProfileService profileService, Clock clock) { + public DefaultIssuanceService(PkiStore store, CredentialFramework framework, CredentialIssuerBackend issuerBackend, + AuditSink auditSink, EffectiveCredentialStatusResolver statusResolver, ProfileService profileService, + Clock clock) { this.store = Objects.requireNonNull(store, "store"); this.framework = Objects.requireNonNull(framework, "framework"); this.issuerBackend = Objects.requireNonNull(issuerBackend, "issuerBackend"); @@ -257,8 +258,8 @@ public final class DefaultIssuanceService implements IssuanceService { CredentialBundle bundle; try { - bundle = CredentialSnapshots.copy(issuerBackend.issueEndEntity(validated, issuerCred.encoded(), - issuer.issuerKeyRef(), serial)); + bundle = CredentialSnapshots + .copy(issuerBackend.issueEndEntity(validated, issuerCred.encoded(), issuer.issuerKeyRef(), serial)); } catch (RuntimeException ex) { // NOPMD - framework output must cross the snapshot boundary throw rejection(candidate.request(), "BACKEND_CREDENTIAL_MISMATCH"); } @@ -272,10 +273,9 @@ public final class DefaultIssuanceService implements IssuanceService { * format. * *

    - * The selected credential must match the requested {@link FormatId} and - * resolve as effectively usable against current revocation state and the - * operation's captured evaluation instant. Resolution failures abort - * selection. + * The selected credential must match the requested {@link FormatId} and resolve + * as effectively usable against current revocation state and the operation's + * captured evaluation instant. Resolution failures abort selection. *

    * * @param issuer issuer CA record containing candidate credentials; must not @@ -425,8 +425,7 @@ public final class DefaultIssuanceService implements IssuanceService { PkiException rejection = new PkiException("End-entity issuance rejected: " + code); try { auditSink.record(new AuditEvent(clock.instant(), "ISSUANCE", "ISSUE_END_ENTITY_REJECTED", SYSTEM_PKI, - ISSUANCE_PURPOSE, Optional.empty(), Optional.of(request.formatId()), - Map.of("code", code))); + ISSUANCE_PURPOSE, Optional.empty(), Optional.of(request.formatId()), Map.of("code", code))); } catch (RuntimeException auditFailure) { // NOPMD - preserve stable rejection and fail closed // Best-effort auditing must not expose listener-controlled diagnostics. } @@ -442,8 +441,7 @@ public final class DefaultIssuanceService implements IssuanceService { try { CredentialProfileBindings.requireEndEntityBinding(credential.profileBinding(), validated.profileReference()); - if (!framework.formatId().equals(credential.formatId()) - || credential.encoded().encoding() != Encoding.DER + if (!framework.formatId().equals(credential.formatId()) || credential.encoded().encoding() != Encoding.DER || !credential.subjectRef().equals(validated.subjectRef()) || !credential.issuerRef().equals(new zeroecho.pki.api.IssuerRef(validated.issuerCaId())) || credential.status() != CredentialStatus.ISSUED) { @@ -459,8 +457,7 @@ public final class DefaultIssuanceService implements IssuanceService { new JcaContentVerifierProviderBuilder().build(issuerHolder.getSubjectPublicKeyInfo())) || !holder.getSerialNumber().equals(allocatedSerial) || !credential.publicKeyId().equals(new PkiId("spki:" + sha256Hex(actualSpki))) - || !credential.credentialId() - .equals(new PkiId("x509:" + sha256Hex(credential.encoded().bytes()))) + || !credential.credentialId().equals(new PkiId("x509:" + sha256Hex(credential.encoded().bytes()))) || !credential.serialOrUniqueId().equals(holder.getSerialNumber().toString()) || !credential.validity().equals(validated.validity()) || validated.validity().notBefore().getEpochSecond() != holder.getNotBefore().toInstant() @@ -591,17 +588,15 @@ public final class DefaultIssuanceService implements IssuanceService { try { status = evaluation.resolve(leaf); } catch (PkiException exception) { - CredentialTrustAudit.rejected(auditSink, evaluation.evaluationTime(), leaf, - CredentialUse.BUNDLE_DELIVERY, + CredentialTrustAudit.rejected(auditSink, evaluation.evaluationTime(), leaf, CredentialUse.BUNDLE_DELIVERY, StoreBackedEffectiveCredentialStatusResolver.RESOLUTION_FAILED_CODE, null); throw exception; } if (status != EffectiveCredentialStatus.USABLE) { - CredentialTrustAudit.rejected(auditSink, evaluation.evaluationTime(), leaf, - CredentialUse.BUNDLE_DELIVERY, + CredentialTrustAudit.rejected(auditSink, evaluation.evaluationTime(), leaf, CredentialUse.BUNDLE_DELIVERY, StoreBackedEffectiveCredentialStatusResolver.NOT_USABLE_CODE, status); - throw new PkiException("Credential trust rejected: code=" - + StoreBackedEffectiveCredentialStatusResolver.NOT_USABLE_CODE); + throw new PkiException( + "Credential trust rejected: code=" + StoreBackedEffectiveCredentialStatusResolver.NOT_USABLE_CODE); } // Minimal bundle: leaf only. Chain selection and publication are higher-layer // concerns. diff --git a/pki/src/main/java/zeroecho/pki/impl/core/DefaultProfileService.java b/pki/src/main/java/zeroecho/pki/impl/core/DefaultProfileService.java index 6197b12..7e9bf31 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/DefaultProfileService.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/DefaultProfileService.java @@ -76,11 +76,10 @@ public final class DefaultProfileService implements ProfileService { return executeSanitized(() -> { CertificateProfileDefinition parsed = CertificateProfileDocumentCodec.parse(canonical); byte[] reserialized = CertificateProfileDocumentCodec.writeCanonical(parsed); - if (!Arrays.equals(canonical, reserialized) - || !parsed.equals(template.definition()) + if (!Arrays.equals(canonical, reserialized) || !parsed.equals(template.definition()) || parsed.profileVersion() != template.definition().profileVersion() || !parsed.profileId().equals(template.definition().profileId()) - || !MessageDigest.isEqual(sha256(canonical), template.canonicalSha256())) { + || !MessageDigest.isEqual(sha256(canonical), template.canonicalSha256())) { throw new ProfileLifecycleFailure(Code.BUILT_IN_PROFILE_INVALID); } return importProfile(canonical); @@ -93,8 +92,7 @@ public final class DefaultProfileService implements ProfileService { if (profileVersion <= 0) { throw new IllegalArgumentException("profileVersion must be positive"); } - return executeAudited("PROFILE_ACTIVATE", profileId, profileVersion, - Code.PROFILE_ACTIVATION_FAILED, () -> { + return executeAudited("PROFILE_ACTIVATE", profileId, profileVersion, Code.PROFILE_ACTIVATION_FAILED, () -> { CertificateProfileRef result = store.activateProfile(profileId, profileVersion); audit("PROFILE_ACTIVATE", result, "SUCCESS"); return result; @@ -104,8 +102,8 @@ public final class DefaultProfileService implements ProfileService { @Override public ActiveCertificateProfile requireActiveProfile(String profileId) { requireProfileId(profileId); - return executeAudited("PROFILE_RESOLVE", profileId, 0L, - Code.PROFILE_STORE_FAILURE, () -> store.requireActiveProfile(profileId)); + return executeAudited("PROFILE_RESOLVE", profileId, 0L, Code.PROFILE_STORE_FAILURE, + () -> store.requireActiveProfile(profileId)); } @Override @@ -114,8 +112,7 @@ public final class DefaultProfileService implements ProfileService { if (profileVersion <= 0) { throw new IllegalArgumentException("profileVersion must be positive"); } - return executeSanitized(() -> store.getProfileVersion(profileId, profileVersion), - Code.PROFILE_STORE_FAILURE); + return executeSanitized(() -> store.getProfileVersion(profileId, profileVersion), Code.PROFILE_STORE_FAILURE); } @Override @@ -132,23 +129,23 @@ public final class DefaultProfileService implements ProfileService { private CertificateProfileRef importCanonical(CertificateProfileDefinition definition, byte[] canonical, Instant importedAt) { - CertificateProfileRef reference = new CertificateProfileRef(definition.profileId(), - definition.profileVersion(), sha256(canonical)); + CertificateProfileRef reference = new CertificateProfileRef(definition.profileId(), definition.profileVersion(), + sha256(canonical)); ImportedCertificateProfileVersion version = new ImportedCertificateProfileVersion(reference, CertificateProfileDefinition.SCHEMA_VERSION, definition, canonical, importedAt); - return executeAudited("PROFILE_IMPORT", definition.profileId(), - definition.profileVersion(), Code.PROFILE_IMPORT_FAILED, () -> { - ImportedCertificateProfileVersion committed = store.importProfileVersion(version); - String result = committed.importedAt().equals(importedAt) ? "SUCCESS" : "UNCHANGED"; - audit("PROFILE_IMPORT", committed.reference(), result); - return committed.reference(); - }); + return executeAudited("PROFILE_IMPORT", definition.profileId(), definition.profileVersion(), + Code.PROFILE_IMPORT_FAILED, () -> { + ImportedCertificateProfileVersion committed = store.importProfileVersion(version); + String result = committed.importedAt().equals(importedAt) ? "SUCCESS" : "UNCHANGED"; + audit("PROFILE_IMPORT", committed.reference(), result); + return committed.reference(); + }); } private void audit(String operation, CertificateProfileRef reference, String result) { - auditSafe(new AuditEvent(clock.instant(), "PROFILE", operation, SYSTEM_PKI, PROFILE_PURPOSE, - Optional.empty(), Optional.empty(), Map.of("profileId", reference.profileId(), - "profileVersion", Long.toString(reference.profileVersion()), + auditSafe(new AuditEvent(clock.instant(), "PROFILE", operation, SYSTEM_PKI, PROFILE_PURPOSE, Optional.empty(), + Optional.empty(), + Map.of("profileId", reference.profileId(), "profileVersion", Long.toString(reference.profileVersion()), "fingerprint", reference.shortFingerprint(), "result", result))); } @@ -205,12 +202,12 @@ public final class DefaultProfileService implements ProfileService { } /* - * This is the audited counterpart of executeSanitized; audit runs only - * after store coordination has returned. + * This is the audited counterpart of executeSanitized; audit runs only after + * store coordination has returned. */ @SuppressWarnings("PMD.AvoidCatchingGenericException") - private T executeAudited(String operation, String profileId, long profileVersion, - Code fallback, Supplier action) { + private T executeAudited(String operation, String profileId, long profileVersion, Code fallback, + Supplier action) { try { return action.get(); } catch (RuntimeException failure) { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/DefaultRevocationService.java b/pki/src/main/java/zeroecho/pki/impl/core/DefaultRevocationService.java index aef8ed7..4809352 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/DefaultRevocationService.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/DefaultRevocationService.java @@ -31,10 +31,10 @@ public final class DefaultRevocationService implements RevocationService { private static final Instant UNKNOWN_AUDIT_TIME = Instant.EPOCH; private static final Principal SYSTEM_PKI = new Principal("SYSTEM", "pki"); private static final Purpose REVOCATION_PURPOSE = new Purpose("REVOCATION"); - private static final List SAFE_STORE_CODES = List.of( - "REVOCATION_CREDENTIAL_NOT_FOUND", "REVOCATION_TRANSITION_ILLEGAL", "REVOCATION_TERMINAL", - "REVOCATION_TRANSITION_CONFLICT", "REVOCATION_STATE_CORRUPT", "REVOCATION_PERSIST_FAILED", - "REVOCATION_DURABILITY_UNCONFIRMED", "STORE_DURABILITY_UNCONFIRMED"); + private static final List SAFE_STORE_CODES = List.of("REVOCATION_CREDENTIAL_NOT_FOUND", + "REVOCATION_TRANSITION_ILLEGAL", "REVOCATION_TERMINAL", "REVOCATION_TRANSITION_CONFLICT", + "REVOCATION_STATE_CORRUPT", "REVOCATION_PERSIST_FAILED", "REVOCATION_DURABILITY_UNCONFIRMED", + "STORE_DURABILITY_UNCONFIRMED"); private final PkiStore store; private final Clock clock; @@ -43,8 +43,8 @@ public final class DefaultRevocationService implements RevocationService { /** * Creates the service with one authoritative clock. * - * @param store authoritative store - * @param clock authoritative clock + * @param store authoritative store + * @param clock authoritative clock * @param auditSink best-effort audit sink */ public DefaultRevocationService(PkiStore store, Clock clock, AuditSink auditSink) { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/DefaultStatusObjectService.java b/pki/src/main/java/zeroecho/pki/impl/core/DefaultStatusObjectService.java index 9a96b89..f8dcce1 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/DefaultStatusObjectService.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/DefaultStatusObjectService.java @@ -131,12 +131,12 @@ public final class DefaultStatusObjectService implements StatusObjectService { * Creates a status object service bound to the supplied persistence and * framework collaborators. * - * @param store PKI store used for issuer lookup, revocation enumeration, - * status object persistence, and status object listing; must - * not be {@code null} - * @param framework credential framework providing the format-specific status - * object generator; must not be {@code null} - * @param auditSink required sink for safe trust-rejection audit events + * @param store PKI store used for issuer lookup, revocation + * enumeration, status object persistence, and status + * object listing; must not be {@code null} + * @param framework credential framework providing the format-specific + * status object generator; must not be {@code null} + * @param auditSink required sink for safe trust-rejection audit events * @param statusResolver authoritative runtime credential-status resolver * @throws NullPointerException if an argument is {@code null} */ @@ -154,9 +154,9 @@ public final class DefaultStatusObjectService implements StatusObjectService { * *

    * The issuer CA must already exist, must be in {@link CaState#ACTIVE} state, - * and must expose at least one usable credential matching the requested - * format. Candidates are evaluated newest-first against current revocation - * state and one authoritative evaluation time. + * and must expose at least one usable credential matching the requested format. + * Candidates are evaluated newest-first against current revocation state and + * one authoritative evaluation time. *

    * *

    @@ -244,8 +244,8 @@ public final class DefaultStatusObjectService implements StatusObjectService { @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) private List collectCrlEntries(PkiId issuerCaId, Instant evaluationTime) { try { - List journals = - Objects.requireNonNull(store.listRevocationJournals(), "revocation journals"); + List journals = Objects.requireNonNull(store.listRevocationJournals(), + "revocation journals"); List entries = new java.util.ArrayList<>(); Set serials = new HashSet<>(); for (RevocationJournal journal : journals) { @@ -257,8 +257,8 @@ public final class DefaultStatusObjectService implements StatusObjectService { } } - private Optional collectCrlEntry(PkiId issuerCaId, Instant evaluationTime, - RevocationJournal journal, Set serials) { + private Optional collectCrlEntry(PkiId issuerCaId, Instant evaluationTime, RevocationJournal journal, + Set serials) { Objects.requireNonNull(journal, "journal"); RevocationTransition latest = Objects.requireNonNull(journal.latest(), "latest transition"); if (latest.time().isAfter(evaluationTime)) { @@ -267,8 +267,8 @@ public final class DefaultStatusObjectService implements StatusObjectService { if (latest.state() == RevocationState.CLEAR) { return Optional.empty(); } - Credential credential = store.getCredential(journal.credentialId()).orElseThrow( - DefaultStatusObjectService::crlGenerationFailure); + Credential credential = store.getCredential(journal.credentialId()) + .orElseThrow(DefaultStatusObjectService::crlGenerationFailure); if (!issuerCaId.equals(credential.issuerRef().caId())) { return Optional.empty(); } @@ -282,8 +282,8 @@ public final class DefaultStatusObjectService implements StatusObjectService { } RevocationReason reason = switch (latest.state()) { case HELD -> RevocationReason.CERTIFICATE_HOLD; - case PERMANENTLY_REVOKED -> latest.permanentReason().orElseThrow( - DefaultStatusObjectService::crlGenerationFailure); + case PERMANENTLY_REVOKED -> + latest.permanentReason().orElseThrow(DefaultStatusObjectService::crlGenerationFailure); case CLEAR -> throw crlGenerationFailure(); }; return Optional.of(new CrlEntry(serial, latest.time(), reason)); diff --git a/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCaCertificateRequest.java b/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCaCertificateRequest.java index c14a9a5..23f58d3 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCaCertificateRequest.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCaCertificateRequest.java @@ -19,11 +19,14 @@ import zeroecho.pki.api.profile.CertificateProfileRef; import zeroecho.pki.api.request.SubjectRdn; /** - * Immutable gate-produced CA certificate request accepted by the issuer backend. + * Immutable gate-produced CA certificate request accepted by the issuer + * backend. * - *

    The request contains only values approved against one exact active profile + *

    + * The request contains only values approved against one exact active profile * version. It carries no generic attributes, raw profile document, or caller - * selected extension material.

    + * selected extension material. + *

    */ @SuppressWarnings("PMD.DataClass") public final class ValidatedCaCertificateRequest { @@ -54,10 +57,10 @@ public final class ValidatedCaCertificateRequest { // The constructor is the single cohesive gate output boundary. @SuppressWarnings("PMD.ExcessiveParameterList") - /* package */ ValidatedCaCertificateRequest(Operation operation, FormatId formatId, - PkiId issuerCaId, PkiId subjectCaId, CertificateProfileRef profileReference, - CertificateProfileKind certificateType, SubjectRef subjectRef, List subjectRdns, - EncodedObject exactPublicKey, Validity validity, BigInteger serial, CaCertificatePolicy policy) { + /* package */ ValidatedCaCertificateRequest(Operation operation, FormatId formatId, PkiId issuerCaId, + PkiId subjectCaId, CertificateProfileRef profileReference, CertificateProfileKind certificateType, + SubjectRef subjectRef, List subjectRdns, EncodedObject exactPublicKey, Validity validity, + BigInteger serial, CaCertificatePolicy policy) { this.operation = Objects.requireNonNull(operation, "operation"); this.formatId = Objects.requireNonNull(formatId, "formatId"); this.issuerCaId = Objects.requireNonNull(issuerCaId, "issuerCaId"); diff --git a/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCertificateRequest.java b/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCertificateRequest.java index c633203..12e4e86 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCertificateRequest.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/ValidatedCertificateRequest.java @@ -44,8 +44,7 @@ public final class ValidatedCertificateRequest { private final boolean basicConstraintsCritical; /* default */ ValidatedCertificateRequest(PkiId issuerCaId, CertificateProfileRef profileReference, - SubjectRef subjectRef, - List subjectRdns, List subjectAlternativeNames, + SubjectRef subjectRef, List subjectRdns, List subjectAlternativeNames, boolean subjectAlternativeNameCritical, EncodedObject exactPublicKey, Validity validity, Set keyUsages, Set extendedKeyUsages, boolean keyUsageCritical, boolean extendedKeyUsageCritical, boolean basicConstraintsCritical) { diff --git a/pki/src/main/java/zeroecho/pki/impl/core/async/PkiSigningBus.java b/pki/src/main/java/zeroecho/pki/impl/core/async/PkiSigningBus.java index 02952f2..f253a6e 100644 --- a/pki/src/main/java/zeroecho/pki/impl/core/async/PkiSigningBus.java +++ b/pki/src/main/java/zeroecho/pki/impl/core/async/PkiSigningBus.java @@ -84,8 +84,7 @@ import zeroecho.pki.util.async.impl.DurableAsyncBus; */ // The collaborators counted here form one durable signing lifecycle; splitting // them would obscure the coordinator/reservation boundary that protects it. -@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity", - "PMD.PreserveStackTrace" }) +@SuppressWarnings({ "PMD.CouplingBetweenObjects", "PMD.CyclomaticComplexity", "PMD.PreserveStackTrace" }) public final class PkiSigningBus implements AutoCloseable { private static final Logger LOG = Logger.getLogger(PkiSigningBus.class.getName()); @@ -200,8 +199,7 @@ public final class PkiSigningBus implements AutoCloseable { this.bus.registerEndpoint(ENDPOINT_SIGNER, endpoint); this.signerRegistration = signer.register(endpoint::onProviderStatusChanged); for (SignWorkflowStore.Record record : store.listSignRecords()) { - if (record.state() == SignWorkflowStore.State.INTENT - || record.state() == SignWorkflowStore.State.DISPATCHED + if (record.state() == SignWorkflowStore.State.INTENT || record.state() == SignWorkflowStore.State.DISPATCHED || record.state() == SignWorkflowStore.State.CANCELLING) { project(record); } @@ -334,8 +332,7 @@ public final class PkiSigningBus implements AutoCloseable { return store.getSignRecord(normalized) .filter(record -> record.state() == SignWorkflowStore.State.SUCCEEDED || record.state() == SignWorkflowStore.State.RETIRED && record.result().isPresent()) - .filter(PkiSigningBus::hasTrustworthyOnTimeCompletion) - .flatMap(SignWorkflowStore.Record::result) + .filter(PkiSigningBus::hasTrustworthyOnTimeCompletion).flatMap(SignWorkflowStore.Record::result) .map(result -> new EncodedObject(result.encoding(), result.bytes())); } } @@ -366,17 +363,17 @@ public final class PkiSigningBus implements AutoCloseable { * The operation is first reconciled with the provider. If it remains active, * the store durably enters non-terminal * {@link SignWorkflowStore.State#CANCELLING} before the provider is called. - * Regardless of whether cancellation is accepted, the provider is read again. - * A still-running operation remains durably {@code CANCELLING} for a later - * retry. Only an observed immutable provider terminal state is changed to + * Regardless of whether cancellation is accepted, the provider is read again. A + * still-running operation remains durably {@code CANCELLING} for a later retry. + * Only an observed immutable provider terminal state is changed to * {@code RETIRED}, and an on-time successful result is preserved. *

    * - * @param opId operation identifier; must not be {@code null} + * @param opId operation identifier; must not be {@code null} * @param reason non-sensitive cancellation reason; must not be blank - * @throws NullPointerException if {@code opId} is {@code null} + * @throws NullPointerException if {@code opId} is {@code null} * @throws IllegalArgumentException if {@code reason} is {@code null} or blank - * @throws PkiException if locally owned cleanup fails + * @throws PkiException if locally owned cleanup fails */ public void retireSignOperation(PkiId opId, String reason) { PkiId baseOpId = normalizeBaseOperationId(opId); @@ -474,8 +471,8 @@ public final class PkiSigningBus implements AutoCloseable { return; } SignWorkflowStore.Record current = currentOptional.get(); - if (current.state() == SignWorkflowStore.State.CANCELLING - && current.revision() == expected.revision() && current.fence() == expected.fence()) { + if (current.state() == SignWorkflowStore.State.CANCELLING && current.revision() == expected.revision() + && current.fence() == expected.fence()) { store.transitionSign(operationId, current.revision(), current.fence(), SignWorkflowStore.State.CANCELLING, Optional.of("CANCEL_SUBMITTED"), Optional.empty(), Optional.empty()); @@ -539,8 +536,7 @@ public final class PkiSigningBus implements AutoCloseable { private SignWorkflowStore.Record requireSignRecordDuringCancellation(PkiId operationId) { return store.getSignRecord(operationId) - .orElseThrow(() -> new PkiException( - "Authoritative signing record disappeared during cancellation")); + .orElseThrow(() -> new PkiException("Authoritative signing record disappeared during cancellation")); } private static boolean isTerminalSignState(SignWorkflowStore.State state) { @@ -550,8 +546,7 @@ public final class PkiSigningBus implements AutoCloseable { } private static boolean hasTrustworthyOnTimeCompletion(SignWorkflowStore.Record record) { - return record.providerUpdatedAt().isPresent() - && record.providerUpdatedAt().get().isBefore(record.deadline()); + return record.providerUpdatedAt().isPresent() && record.providerUpdatedAt().get().isBefore(record.deadline()); } private SignWorkflowStore.Record confirmRetirement(PkiId operationId, SignWorkflowStore.Record initial) { @@ -716,14 +711,14 @@ public final class PkiSigningBus implements AutoCloseable { } /** - * Lifecycle-bounded, non-blocking single-flight reservations for provider calls. + * Lifecycle-bounded, non-blocking single-flight reservations for provider + * calls. */ private static final class ExternalActionCoordinator { private final ConcurrentMap actions = new ConcurrentHashMap<>(); private Optional tryReserve(PkiId operationId, ExternalAction action) { - return actions.putIfAbsent(operationId, action) == null - ? Optional.of(new Reservation(operationId, action)) + return actions.putIfAbsent(operationId, action) == null ? Optional.of(new Reservation(operationId, action)) : Optional.empty(); } @@ -795,9 +790,9 @@ public final class PkiSigningBus implements AutoCloseable { * state is internally inconsistent. *
  • Downstream signer failure is reported through the mapped async status * returned by {@link #status(PkiId)}.
  • - *
  • If downstream submission succeeds but continuation persistence fails, - * the downstream handle is cancelled, the stale continuation is deleted, and - * the endpoint reports a terminal local failure.
  • + *
  • If downstream submission succeeds but continuation persistence fails, the + * downstream handle is cancelled, the stale continuation is deleted, and the + * endpoint reports a terminal local failure.
  • * * *

    Thread-safety

    @@ -830,8 +825,8 @@ public final class PkiSigningBus implements AutoCloseable { * @param signer downstream signature workflow used to execute the actual sign * operation and to query its status; must not be {@code null} */ - private SignatureWorkflowEndpoint(PkiStore store, SignatureWorkflow signer, - OperationCoordinator coordinator, ExternalActionCoordinator externalActions) { + private SignatureWorkflowEndpoint(PkiStore store, SignatureWorkflow signer, OperationCoordinator coordinator, + ExternalActionCoordinator externalActions) { this.store = store; this.signer = signer; this.coordinator = coordinator; @@ -902,9 +897,8 @@ public final class PkiSigningBus implements AutoCloseable { SignWorkflowStore.Record current = currentOptional.get(); if (!current.deadline().isAfter(store.signingNow())) { if (current.fence() == INITIAL_FENCE) { - store.transitionSign(opId, current.revision(), current.fence(), - SignWorkflowStore.State.EXPIRED, Optional.of("EXPIRED"), Optional.empty(), - Optional.empty()); + store.transitionSign(opId, current.revision(), current.fence(), SignWorkflowStore.State.EXPIRED, + Optional.of("EXPIRED"), Optional.empty(), Optional.empty()); } return Optional.empty(); } @@ -929,9 +923,8 @@ public final class PkiSigningBus implements AutoCloseable { continuation.algorithmId, continuation.payload, Optional.of(continuation.preferredSignatureEncoding), Optional.of(claimed.deadline())); if (!constantTimeAsciiEquals(claimed.fingerprint(), request.semanticFingerprint())) { - store.transitionSign(opId, claimed.revision(), claimed.fence(), - SignWorkflowStore.State.FAILED, Optional.of("REQUEST_INTEGRITY_FAILURE"), - Optional.empty(), Optional.empty()); + store.transitionSign(opId, claimed.revision(), claimed.fence(), SignWorkflowStore.State.FAILED, + Optional.of("REQUEST_INTEGRITY_FAILURE"), Optional.empty(), Optional.empty()); return Optional.empty(); } SubmissionCall call = new SubmissionCall(claimed, request, reservation); @@ -965,9 +958,9 @@ public final class PkiSigningBus implements AutoCloseable { return; } if (!call.record().submissionId().equals(returned)) { - store.transitionSign(call.record().submissionId(), call.record().revision(), - call.record().fence(), SignWorkflowStore.State.FAILED, - Optional.of("PROVIDER_ID_MISMATCH"), Optional.empty(), Optional.empty()); + store.transitionSign(call.record().submissionId(), call.record().revision(), call.record().fence(), + SignWorkflowStore.State.FAILED, Optional.of("PROVIDER_ID_MISMATCH"), Optional.empty(), + Optional.empty()); return; } store.transitionSign(call.record().submissionId(), call.record().revision(), call.record().fence(), @@ -1050,9 +1043,8 @@ public final class PkiSigningBus implements AutoCloseable { reconcileProviderStatus(opId); record = store.getSignRecord(opId).orElse(record); } - if (!record.deadline().isAfter(now) - && (record.state() == SignWorkflowStore.State.DISPATCHED - || record.state() == SignWorkflowStore.State.CANCELLING)) { + if (!record.deadline().isAfter(now) && (record.state() == SignWorkflowStore.State.DISPATCHED + || record.state() == SignWorkflowStore.State.CANCELLING)) { return Optional.of(new AsyncStatus(AsyncState.EXPIRED, now, Optional.of("EXPIRED"), Map.of())); } return Optional.of(mapStoreStatus(record)); @@ -1084,7 +1076,8 @@ public final class PkiSigningBus implements AutoCloseable { } } - // Provider implementations are an untrusted boundary and may throw any runtime failure. + // Provider implementations are an untrusted boundary and may throw any runtime + // failure. @SuppressWarnings("PMD.AvoidCatchingGenericException") private void reconcileProviderStatus(PkiId operationId) { removeAdvisory(operationId); @@ -1143,14 +1136,12 @@ public final class PkiSigningBus implements AutoCloseable { detail = Optional.of("LATE_PROVIDER_SUCCESS"); result = Optional.empty(); } - store.transitionSign(call.record().submissionId(), - current.revision(), current.fence(), target, detail, result, - Optional.of(providerStatus.updatedAt())); + store.transitionSign(call.record().submissionId(), current.revision(), current.fence(), target, detail, + result, Optional.of(providerStatus.updatedAt())); } } - private record StatusCall(SignWorkflowStore.Record record, - ExternalActionCoordinator.Reservation reservation) { + private record StatusCall(SignWorkflowStore.Record record, ExternalActionCoordinator.Reservation reservation) { } private void removeAdvisory(PkiId operationId) { @@ -1203,8 +1194,7 @@ public final class PkiSigningBus implements AutoCloseable { try (OperationCoordinator.Lease ignored = coordinator.acquire(opId)) { return store.getSignRecord(opId) .filter(record -> record.state() == SignWorkflowStore.State.SUCCEEDED - || record.state() == SignWorkflowStore.State.RETIRED - && record.result().isPresent()) + || record.state() == SignWorkflowStore.State.RETIRED && record.result().isPresent()) .flatMap(SignWorkflowStore.Record::result); } } @@ -1384,7 +1374,7 @@ public final class PkiSigningBus implements AutoCloseable { * * @param namespace authoritative provider/store namespace; must not be * {@code null} - * @param deadline authoritative signing deadline; must not be {@code null} + * @param deadline authoritative signing deadline; must not be {@code null} * @return canonical versioned semantic fingerprint * @throws NullPointerException if either argument is {@code null} */ @@ -1399,7 +1389,7 @@ public final class PkiSigningBus implements AutoCloseable { * * @param submissionId authoritative submission identifier; must not be * {@code null} - * @param owner authoritative request owner; must not be {@code null} + * @param owner authoritative request owner; must not be {@code null} * @return {@code true} only when the stored provider operation identifier and * access principal exactly match the supplied identity * @throws NullPointerException if either argument is {@code null} diff --git a/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflow.java b/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflow.java index 7768f56..fc31977 100644 --- a/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflow.java +++ b/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflow.java @@ -109,8 +109,8 @@ import zeroecho.sdk.ZeroEchoSession; * layer and ZeroEcho-lib based key material handling backed by * {@link KeyringStore}. It resolves opaque {@link KeyRef} values to provider- * local keyring aliases, materializes the required key objects inside this - * boundary, and performs signing or verification through - * the explicit {@link ZeroEchoSession} and {@link SignatureContext}. + * boundary, and performs signing or verification through the explicit + * {@link ZeroEchoSession} and {@link SignatureContext}. *

    * *

    @@ -119,8 +119,8 @@ import zeroecho.sdk.ZeroEchoSession; * by returning an operation identifier and exposing the terminal outcome * through {@link #status(PkiId)}. Each submitted operation is executed * immediately in the caller thread. Signing requests and terminal outcomes are - * retained durably in the configured operation root for the configured operation - * horizon. + * retained durably in the configured operation root for the configured + * operation horizon. *

    * *

    Supported operations

    @@ -209,8 +209,8 @@ import zeroecho.sdk.ZeroEchoSession; * signature result are stored durably until the configured operation horizon. * Exact submissions attach to these records after restart. *
  • A signing record found in {@link State#RUNNING} during restart is changed - * durably to {@link State#FAILED} with detail - * {@code RECOVERY_INCOMPLETE}; signing is not replayed.
  • + * durably to {@link State#FAILED} with detail {@code RECOVERY_INCOMPLETE}; + * signing is not replayed. *
  • Verification statuses and notification registrations are memory-only and * are lost when the provider is closed or the process restarts.
  • *
  • The underlying {@link KeyringStore} is loaded lazily on first use and @@ -301,24 +301,21 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { Duration operationHorizon, String keyRefPrefix, boolean requireComponentSuffix, KeyringUnlockProvider keyringUnlockProvider) { this(id, keyringPath, operationRoot, clock, operationHorizon, keyRefPrefix, requireComponentSuffix, - keyringUnlockProvider, - (category, cleared) -> { + keyringUnlockProvider, (category, cleared) -> { }); } /* default */ ZeroEchoLibSignatureWorkflow(String id, Path keyringPath, Path operationRoot, Clock clock, Duration operationHorizon, String keyRefPrefix, boolean requireComponentSuffix, KeyringUnlockProvider keyringUnlockProvider, KeyringStore keyring) { - this(id, keyringPath, operationRoot, clock, operationHorizon, keyRefPrefix, - requireComponentSuffix, keyringUnlockProvider); - this.keyringOrNull = java.util.Objects.requireNonNull( - keyring, "keyring must not be null"); + this(id, keyringPath, operationRoot, clock, operationHorizon, keyRefPrefix, requireComponentSuffix, + keyringUnlockProvider); + this.keyringOrNull = java.util.Objects.requireNonNull(keyring, "keyring must not be null"); } /* default */ ZeroEchoLibSignatureWorkflow(String id, Path keyringPath, Path operationRoot, Clock clock, Duration operationHorizon, String keyRefPrefix, boolean requireComponentSuffix, - KeyringUnlockProvider keyringUnlockProvider, - BiConsumer cleanupObserver) { + KeyringUnlockProvider keyringUnlockProvider, BiConsumer cleanupObserver) { if (id == null || id.isBlank()) { throw new IllegalArgumentException("id must not be blank"); } @@ -384,8 +381,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { } this.timeWatermark = new AtomicLong(loadTimeWatermark()); Path domain = this.operationRoot.resolve("DOMAIN"); - this.boundNamespace = new AtomicReference<>(Files.exists(domain) - ? Files.readString(domain, StandardCharsets.US_ASCII).trim() : null); + this.boundNamespace = new AtomicReference<>( + Files.exists(domain) ? Files.readString(domain, StandardCharsets.US_ASCII).trim() : null); loadOperationRecords(); purgeExpiredOperations(); } catch (IOException ex) { @@ -529,8 +526,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { return opId; } OperationResult result = new OperationResult(Optional.of(signature), Optional.empty()); - completeSign(request, new OperationStatus(State.SUCCEEDED, completedAt, Optional.of(DC_SIGNED), - Optional.of(result))); + completeSign(request, + new OperationStatus(State.SUCCEEDED, completedAt, Optional.of(DC_SIGNED), Optional.of(result))); return opId; } catch (InvalidRequestException inv) { // NOPMD @@ -539,20 +536,20 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { return opId; } catch (IOException io) { - completeSign(request, new OperationStatus(State.FAILED, now(), - Optional.of(DC_KEYRING_IO_ERROR), Optional.empty())); + completeSign(request, + new OperationStatus(State.FAILED, now(), Optional.of(DC_KEYRING_IO_ERROR), Optional.empty())); logSafeFailure("SIGN", DC_KEYRING_IO_ERROR, io); return opId; } catch (GeneralSecurityException sec) { - completeSign(request, new OperationStatus(State.FAILED, now(), - Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); + completeSign(request, + new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); logSafeFailure("SIGN", DC_CRYPTO_FAILURE, sec); return opId; } catch (RuntimeException ex) { // NOPMD - completeSign(request, new OperationStatus(State.FAILED, now(), - Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); + completeSign(request, + new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); logSafeFailure("SIGN", DC_CRYPTO_FAILURE, ex); return opId; } finally { @@ -576,10 +573,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { throw new IllegalStateException("Stale signing fencing token"); } OperationStatus existing = statuses.get(operationId); - if (request.fencingToken() > existingFence && existing != null - && existing.state() == State.RUNNING) { - event = new OperationStatus(State.FAILED, now(), Optional.of("FENCE_SUPERSEDED"), - Optional.empty()); + if (request.fencingToken() > existingFence && existing != null && existing.state() == State.RUNNING) { + event = new OperationStatus(State.FAILED, now(), Optional.of("FENCE_SUPERSEDED"), Optional.empty()); fences.put(operationId, request.fencingToken()); statuses.put(operationId, event); persistOperationRecord(operationId, event); @@ -612,8 +607,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { && fences.getOrDefault(operationId, 0L) == request.fencingToken() && constantTimeEquals(fingerprints.get(operationId), request.semanticFingerprint())) { OperationStatus committedStatus = terminal; - if (terminal.state() == State.SUCCEEDED - && deadlineReached(request.deadline(), terminal.updatedAt())) { + if (terminal.state() == State.SUCCEEDED && deadlineReached(request.deadline(), terminal.updatedAt())) { committedStatus = expiredStatus(terminal.updatedAt()); } statuses.put(operationId, committedStatus); @@ -703,25 +697,22 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { return opId; } catch (InvalidRequestException inv) { // NOPMD - putStatus(opId, - new OperationStatus(State.FAILED, now(), Optional.of(inv.detailCode), Optional.empty())); + putStatus(opId, new OperationStatus(State.FAILED, now(), Optional.of(inv.detailCode), Optional.empty())); return opId; } catch (IOException io) { - putStatus(opId, new OperationStatus(State.FAILED, now(), Optional.of(DC_KEYRING_IO_ERROR), - Optional.empty())); + putStatus(opId, + new OperationStatus(State.FAILED, now(), Optional.of(DC_KEYRING_IO_ERROR), Optional.empty())); logSafeFailure("VERIFY", DC_KEYRING_IO_ERROR, io); return opId; } catch (GeneralSecurityException sec) { - putStatus(opId, - new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); + putStatus(opId, new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); logSafeFailure("VERIFY", DC_CRYPTO_FAILURE, sec); return opId; } catch (RuntimeException ex) { // NOPMD - putStatus(opId, - new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); + putStatus(opId, new OperationStatus(State.FAILED, now(), Optional.of(DC_CRYPTO_FAILURE), Optional.empty())); logSafeFailure("VERIFY", DC_CRYPTO_FAILURE, ex); return opId; } finally { @@ -774,8 +765,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { * *

    * Cancellation is serialized with completion for the same signing identifier, - * validates the fencing token, and durably records {@link State#CANCELLED}. - * It succeeds only while the retained operation is non-terminal. + * validates the fencing token, and durably records {@link State#CANCELLED}. It + * succeeds only while the retained operation is non-terminal. *

    * * @param operationId workflow operation identifier; must not be {@code null} @@ -809,8 +800,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { return false; } this.fences.put(operationId, fencingToken); - cancelled = new OperationStatus(State.CANCELLED, now(), Optional.of(DC_CANCELLED), - Optional.empty()); + cancelled = new OperationStatus(State.CANCELLED, now(), Optional.of(DC_CANCELLED), Optional.empty()); statuses.put(operationId, cancelled); persistOperationRecord(operationId, cancelled); } finally { @@ -1079,8 +1069,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { ContextSpec contextSpec = profile.map(SignatureInteropProfile::contextSpec).orElse(null); int sigLen; - try (SignatureContext verifier = session.createContext(contextAlgorithmId, KeyUsage.VERIFY, pub, - contextSpec)) { + try (SignatureContext verifier = session.createContext(contextAlgorithmId, KeyUsage.VERIFY, pub, contextSpec)) { sigLen = verifier.tagLength(); } @@ -1125,8 +1114,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { convertedSignature = true; } - try (SignatureContext verifier = session.createContext(contextAlgorithmId, KeyUsage.VERIFY, pub, - contextSpec)) { + try (SignatureContext verifier = session.createContext(contextAlgorithmId, KeyUsage.VERIFY, pub, contextSpec)) { verifier.setExpectedTag(internalSignature); try (InputStream in = verifier.wrap(new ByteArrayInputStream(msg))) { in.transferTo(OutputStream.nullOutputStream()); @@ -1141,8 +1129,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { } } - private EncodedObject encodeSignatureOrThrow(Encoding encoding, byte[] sigBytes) - throws InvalidRequestException { + private EncodedObject encodeSignatureOrThrow(Encoding encoding, byte[] sigBytes) throws InvalidRequestException { if (encoding == Encoding.BINARY || encoding == Encoding.DER) { return new EncodedObject(encoding, sigBytes); } @@ -1305,8 +1292,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { if (!current.isBefore(SigningSubmissionId.parse(operationId).createdAt().plus(operationHorizon))) { SignLockEntry lock = acquireOperationLock(operationId); try { - if (requests.containsKey(operationId) && !current.isBefore( - SigningSubmissionId.parse(operationId).createdAt().plus(operationHorizon))) { + if (requests.containsKey(operationId) && !current + .isBefore(SigningSubmissionId.parse(operationId).createdAt().plus(operationHorizon))) { requests.remove(operationId); fingerprints.remove(operationId); fences.remove(operationId); @@ -1349,8 +1336,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { private static void restrictPermissions(Path path, boolean directory) throws IOException { try { - Files.setPosixFilePermissions(path, PosixFilePermissions.fromString( - directory ? "rwx------" : "rw-------")); + Files.setPosixFilePermissions(path, PosixFilePermissions.fromString(directory ? "rwx------" : "rw-------")); } catch (UnsupportedOperationException ex) { // Non-POSIX platforms rely on their native access-control mechanism. if (LOG.isLoggable(Level.FINEST)) { @@ -1395,9 +1381,8 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { } OperationStatus loaded = new OperationStatus(state, updatedAt, detail, result); boolean repaired = false; - if (state == State.SUCCEEDED - && (updatedAt.isBefore(parsedId.createdAt()) - || deadlineReached(request.deadline(), updatedAt))) { + if (state == State.SUCCEEDED && (updatedAt.isBefore(parsedId.createdAt()) + || deadlineReached(request.deadline(), updatedAt))) { loaded = expiredStatus(updatedAt); repaired = true; } @@ -1541,8 +1526,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { throw new IllegalStateException("Truncated persisted signing payload"); } try { - Optional preferred = input.readBoolean() - ? Optional.of(encodingFromCode(input.readInt())) + Optional preferred = input.readBoolean() ? Optional.of(encodingFromCode(input.readInt())) : Optional.empty(); Optional deadline = input.readBoolean() ? Optional.of(Instant.ofEpochSecond(input.readLong(), input.readInt())) @@ -1596,8 +1580,7 @@ public final class ZeroEchoLibSignatureWorkflow implements SignatureWorkflow { }; } - private void writeOperationResult(DataOutputStream output, Optional result) - throws IOException { + private void writeOperationResult(DataOutputStream output, Optional result) throws IOException { output.writeBoolean(result.isPresent()); if (result.isEmpty()) { return; diff --git a/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowProvider.java b/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowProvider.java index ab33350..f1112ae 100644 --- a/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowProvider.java +++ b/pki/src/main/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowProvider.java @@ -77,11 +77,9 @@ import zeroecho.pki.spi.crypto.SignatureWorkflowRuntimeDependencies; */ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWorkflowProvider { /** Stable failure code for a missing explicit keyring unlock provider. */ - public static final String DC_KEYRING_UNLOCK_PROVIDER_REQUIRED = - "KEYRING_UNLOCK_PROVIDER_REQUIRED"; + public static final String DC_KEYRING_UNLOCK_PROVIDER_REQUIRED = "KEYRING_UNLOCK_PROVIDER_REQUIRED"; /** Stable failure code for an unlock-provider acquisition failure. */ - public static final String DC_KEYRING_UNLOCK_PROVIDER_FAILED = - "KEYRING_UNLOCK_PROVIDER_FAILED"; + public static final String DC_KEYRING_UNLOCK_PROVIDER_FAILED = "KEYRING_UNLOCK_PROVIDER_FAILED"; /** Stable failure code for an I/O failure while opening the keyring. */ public static final String DC_KEYRING_OPEN_FAILED = "KEYRING_OPEN_FAILED"; @@ -97,9 +95,11 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork /** * Creates a service-loadable provider without unlock material. * - *

    {@link #allocate(ProviderConfig)} fails until an explicitly injected - * provider instance is used. Service configuration text can never contain - * an unlock secret.

    + *

    + * {@link #allocate(ProviderConfig)} fails until an explicitly injected provider + * instance is used. Service configuration text can never contain an unlock + * secret. + *

    */ public ZeroEchoLibSignatureWorkflowProvider() { this.keyringUnlockProvider = null; @@ -108,12 +108,12 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork /** * Creates a provider with an explicit headless unlock source. * - * @param keyringUnlockProvider provider returning a fresh destroyable - * password for each keyring open + * @param keyringUnlockProvider provider returning a fresh destroyable password + * for each keyring open */ public ZeroEchoLibSignatureWorkflowProvider(KeyringUnlockProvider keyringUnlockProvider) { - this.keyringUnlockProvider = java.util.Objects.requireNonNull( - keyringUnlockProvider, "keyringUnlockProvider must not be null"); + this.keyringUnlockProvider = java.util.Objects.requireNonNull(keyringUnlockProvider, + "keyringUnlockProvider must not be null"); } @Override @@ -143,7 +143,8 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork config.get(KEY_OPERATION_HORIZON).ifPresent(value -> { Duration horizon = Duration.parse(value); if (horizon.isZero() || horizon.isNegative()) { - throw new IllegalArgumentException("Configuration key '" + KEY_OPERATION_HORIZON + "' must be positive."); + throw new IllegalArgumentException( + "Configuration key '" + KEY_OPERATION_HORIZON + "' must be positive."); } }); config.get(KEY_KEYREF_PREFIX).ifPresent(value -> { @@ -176,18 +177,16 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork /** * Allocates a workflow using explicit runtime dependencies. * - * @param config structural provider configuration + * @param config structural provider configuration * @param dependencies explicit process-local runtime dependencies * @return opened workflow owning an unlocked keyring - * @throws PkiException if the keyring unlock provider is absent or fails + * @throws PkiException if the keyring unlock provider is absent or fails * @throws RuntimeException if workflow allocation otherwise fails */ @Override - public SignatureWorkflow allocate(final ProviderConfig config, - SignatureWorkflowRuntimeDependencies dependencies) { + public SignatureWorkflow allocate(final ProviderConfig config, SignatureWorkflowRuntimeDependencies dependencies) { java.util.Objects.requireNonNull(dependencies, "dependencies must not be null"); - KeyringUnlockProvider provider = dependencies.keyringUnlockProvider() - .orElse(keyringUnlockProvider); + KeyringUnlockProvider provider = dependencies.keyringUnlockProvider().orElse(keyringUnlockProvider); if (provider == null) { throw new PkiException(DC_KEYRING_UNLOCK_PROVIDER_REQUIRED); } @@ -196,8 +195,7 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork // Cleanup must cover every constructor failure, including unchecked failures. @SuppressWarnings("PMD.AvoidCatchingGenericException") - private SignatureWorkflow allocate(final ProviderConfig config, - KeyringUnlockProvider unlockProvider) { + private SignatureWorkflow allocate(final ProviderConfig config, KeyringUnlockProvider unlockProvider) { validateConfig(config); String keyringPath = config.require(KEY_KEYRING_PATH); Path operationRoot = Path.of(config.require(KEY_OPERATION_ROOT)); @@ -207,9 +205,8 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork KeyringStore keyring = openKeyring(Path.of(keyringPath), unlockProvider); try { - return new ZeroEchoLibSignatureWorkflow(id(), Path.of(keyringPath), operationRoot, - Clock.systemUTC(), operationHorizon, prefix, requireSuffix, - unlockProvider, keyring); + return new ZeroEchoLibSignatureWorkflow(id(), Path.of(keyringPath), operationRoot, Clock.systemUTC(), + operationHorizon, prefix, requireSuffix, unlockProvider, keyring); } catch (RuntimeException | Error failure) { keyring.close(); throw failure; @@ -217,15 +214,11 @@ public final class ZeroEchoLibSignatureWorkflowProvider implements SignatureWork } /* - * The unlock provider is arbitrary application code. Its throwable message - * and cause are intentionally removed at this security boundary. + * The unlock provider is arbitrary application code. Its throwable message and + * cause are intentionally removed at this security boundary. */ - @SuppressWarnings({ - "PMD.AvoidCatchingGenericException", - "PMD.PreserveStackTrace" - }) - private static KeyringStore openKeyring(Path keyringPath, - KeyringUnlockProvider unlockProvider) { + @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) + private static KeyringStore openKeyring(Path keyringPath, KeyringUnlockProvider unlockProvider) { KeyringPassword password; try { password = unlockProvider.acquire(); diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CertificationRequestParser.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CertificationRequestParser.java index 41a8512..15f7bbb 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CertificationRequestParser.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CertificationRequestParser.java @@ -135,6 +135,7 @@ public final class BcX509CertificationRequestParser implements CertificationRequ private static final int MAXIMUM_SAN_DER_BYTES = 32 * 1024; private static final int MAXIMUM_SUBJECT_RDNS = 32; private static final int SINGLE_ATTRIBUTE_VALUE = 1; + /** * Parses a PKCS#10 certification request into the normalized PKI request * representation. @@ -321,8 +322,8 @@ public final class BcX509CertificationRequestParser implements CertificationRequ private static SubjectAlternativeName parseGeneralName(GeneralName name) { return switch (name.getTagNo()) { case GeneralName.dNSName -> new SubjectAlternativeName.DnsName(asAsciiString(name.getName())); - case GeneralName.iPAddress -> new SubjectAlternativeName.IpAddress( - ASN1OctetString.getInstance(name.getName()).getOctets()); + case GeneralName.iPAddress -> + new SubjectAlternativeName.IpAddress(ASN1OctetString.getInstance(name.getName()).getOctets()); case GeneralName.uniformResourceIdentifier -> new SubjectAlternativeName.UriName(asAsciiString(name.getName())); case GeneralName.rfc822Name -> new SubjectAlternativeName.Rfc822Name(asAsciiString(name.getName())); diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialFramework.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialFramework.java index 2f66fb2..666f6d0 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialFramework.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialFramework.java @@ -75,8 +75,8 @@ import zeroecho.pki.spi.framework.StatusObjectGenerator; *
      *
    • This class is an orchestration and component-aggregation object; it does * not itself process private key material.
    • - *
    • Credential issuance remains owned by proof-gated core services rather than - * this framework facade.
    • + *
    • Credential issuance remains owned by proof-gated core services rather + * than this framework facade.
    • *
    • The default instance intentionally fails fast for issuance and * status-object generation so that partially wired deployments do not silently * degrade into incomplete behavior.
    • @@ -120,8 +120,8 @@ public final class BcX509CredentialFramework implements CredentialFramework { *

      * Status-object generation is intentionally left unsupported in this default * configuration and may be supplied explicitly through - * {@link #wired(BcX509StatusObjectGenerator)}. Credential issuance is not exposed by the - * framework facade. + * {@link #wired(BcX509StatusObjectGenerator)}. Credential issuance is not + * exposed by the framework facade. *

      */ public BcX509CredentialFramework() { @@ -277,7 +277,7 @@ public final class BcX509CredentialFramework implements CredentialFramework { * Always rejects status-object generation because no concrete X.509 * status-object generator has been wired. * - * @param command ignored command parameter + * @param command ignored command parameter * @param crlEntries ignored structured CRL entries * @return never returns normally * @throws UnsupportedOperationException always diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialIssuerBackend.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialIssuerBackend.java index 110a275..ce5c2fb 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialIssuerBackend.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509CredentialIssuerBackend.java @@ -88,8 +88,8 @@ import zeroecho.pki.spi.framework.CredentialIssuerBackend; * *

      * End-entity issuance derives all requester-influenced certificate material - * exclusively from a profile-gated {@link ValidatedCertificateRequest}. - * CA issuance derives certificate content exclusively from a proof-bound, + * exclusively from a profile-gated {@link ValidatedCertificateRequest}. CA + * issuance derives certificate content exclusively from a proof-bound, * active-profile-validated request and separately supplied trusted issuer * material. *

      @@ -217,8 +217,7 @@ public final class BcX509CredentialIssuerBackend implements CredentialIssuerBack Credential credential = new Credential(credId, BcX509CredentialFramework.FORMAT_ID, new IssuerRef(request.issuerCaId()), request.subjectRef(), request.validity(), serial.toString(), publicKeyId, new EndEntityProfileBinding(request.profileReference()), CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, certDer), - SimpleAttributeSet.builder().build()); + new EncodedObject(Encoding.DER, certDer), SimpleAttributeSet.builder().build()); return new CredentialBundle(credential, java.util.List.of(issuerCertificate)); } finally { java.util.Arrays.fill(certDer, (byte) 0); @@ -274,8 +273,7 @@ public final class BcX509CredentialIssuerBackend implements CredentialIssuerBack new GeneralName(GeneralName.iPAddress, new DEROctetString(ip.bytes())); case SubjectAlternativeName.UriName uri -> new GeneralName(GeneralName.uniformResourceIdentifier, uri.value()); - case SubjectAlternativeName.Rfc822Name email -> - new GeneralName(GeneralName.rfc822Name, email.value()); + case SubjectAlternativeName.Rfc822Name email -> new GeneralName(GeneralName.rfc822Name, email.value()); }; } @@ -289,8 +287,8 @@ public final class BcX509CredentialIssuerBackend implements CredentialIssuerBack * resulting credential. *

      * - * @param request gate-produced validated CA certificate request; - * must not be {@code null} + * @param request gate-produced validated CA certificate request; must + * not be {@code null} * @param issuerCertificate trusted issuer certificate; must not be {@code null} * @param issuerKeyRef trusted issuer signing-key reference; must not be * {@code null} @@ -363,10 +361,10 @@ public final class BcX509CredentialIssuerBackend implements CredentialIssuerBack PkiId credId = new PkiId("x509:" + sha256Hex(certDer)); try { - return new Credential(credId, request.formatId(), new IssuerRef(request.issuerCaId()), subjectRef, - validity, serial.toString(), publicKeyId, new CaProfileBinding(request.profileReference()), - CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, certDer), SimpleAttributeSet.builder().build()); + return new Credential(credId, request.formatId(), new IssuerRef(request.issuerCaId()), subjectRef, validity, + serial.toString(), publicKeyId, new CaProfileBinding(request.profileReference()), + CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, certDer), + SimpleAttributeSet.builder().build()); } finally { java.util.Arrays.fill(certDer, (byte) 0); } diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509ProfileSupport.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509ProfileSupport.java index c8c7034..4678b41 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509ProfileSupport.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509ProfileSupport.java @@ -61,18 +61,20 @@ public final class BcX509ProfileSupport { case COUNTRY_NAME, SERIAL_NUMBER -> new DERPrintableString(rdn.value(), true); case EMAIL_ADDRESS -> new DERIA5String(rdn.value(), true); case COMMON_NAME, ORGANIZATION_NAME, ORGANIZATIONAL_UNIT_NAME, STATE_OR_PROVINCE_NAME, LOCALITY_NAME, - PSEUDONYM -> new DERUTF8String(rdn.value()); + PSEUDONYM -> + new DERUTF8String(rdn.value()); }; } /** * Verifies the complete allowed leaf extension set and exact values. * - * @param holder issued certificate + * @param holder issued certificate * @param request validated request * @return {@code true} only when every extension matches */ - // Malformed post-signing ASN.1 is a false postcondition, not an exposed parser failure. + // Malformed post-signing ASN.1 is a false postcondition, not an exposed parser + // failure. @SuppressWarnings("PMD.AvoidCatchingGenericException") public static boolean matchesLeafExtensions(X509CertificateHolder holder, ValidatedCertificateRequest request) { try { @@ -119,8 +121,7 @@ public final class BcX509ProfileSupport { return Arrays.equals(actual.getBytes(), expected.getBytes()) && actual.getPadBits() == expected.getPadBits(); } - private static boolean matchesExtendedKeyUsage(X509CertificateHolder holder, - ValidatedCertificateRequest request) { + private static boolean matchesExtendedKeyUsage(X509CertificateHolder holder, ValidatedCertificateRequest request) { Extension extension = holder.getExtension(Extension.extendedKeyUsage); if (request.extendedKeyUsages().isEmpty()) { return extension == null; @@ -162,8 +163,7 @@ public final class BcX509ProfileSupport { private static SubjectAlternativeName parse(GeneralName name) { return switch (name.getTagNo()) { - case GeneralName.dNSName -> - new SubjectAlternativeName.DnsName(((ASN1String) name.getName()).getString()); + case GeneralName.dNSName -> new SubjectAlternativeName.DnsName(((ASN1String) name.getName()).getString()); case GeneralName.iPAddress -> new SubjectAlternativeName.IpAddress(ASN1OctetString.getInstance(name.getName()).getOctets()); case GeneralName.uniformResourceIdentifier -> diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509StatusObjectGenerator.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509StatusObjectGenerator.java index 97e21a6..a5e77bb 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509StatusObjectGenerator.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/BcX509StatusObjectGenerator.java @@ -199,7 +199,7 @@ public final class BcX509StatusObjectGenerator implements StatusObjectGenerator *
    • the original command attributes unchanged.
    • *
    * - * @param command status object generation command; must not be {@code null} + * @param command status object generation command; must not be {@code null} * @param crlEntries structured current CRL entries; must not be {@code null} * @return generated CRL status object * @throws IllegalArgumentException if {@code command} is {@code null}, if the @@ -334,8 +334,7 @@ public final class BcX509StatusObjectGenerator implements StatusObjectGenerator case SUPERSEDED -> CRLReason.superseded; case CESSATION_OF_OPERATION -> CRLReason.cessationOfOperation; case CERTIFICATE_HOLD -> CRLReason.certificateHold; - case REMOVE_FROM_CRL -> throw new IllegalArgumentException( - "REMOVE_FROM_CRL is not an active CRL entry"); + case REMOVE_FROM_CRL -> throw new IllegalArgumentException("REMOVE_FROM_CRL is not an active CRL entry"); case PRIVILEGE_WITHDRAWN -> CRLReason.privilegeWithdrawn; case AA_COMPROMISE -> CRLReason.aACompromise; }; diff --git a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSigner.java b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSigner.java index c2f22b7..2b89060 100644 --- a/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSigner.java +++ b/pki/src/main/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSigner.java @@ -236,8 +236,7 @@ public final class PkiBusContentSigner implements ContentSigner { Principal owner = new Principal("SYSTEM", "pki"); opId = bus.newSubmissionId(); - AccessContext ac = new AccessContext(owner, new Purpose("X509_SIGN"), Optional.empty(), - Optional.empty()); + AccessContext ac = new AccessContext(owner, new Purpose("X509_SIGN"), Optional.empty(), Optional.empty()); SignContinuation cont = new SignContinuation(ac, algorithmId, payload, keyRef, Encoding.BINARY, Optional.empty()); diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FilesystemPkiStore.java b/pki/src/main/java/zeroecho/pki/impl/fs/FilesystemPkiStore.java index 659fd6c..83a8941 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/FilesystemPkiStore.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/FilesystemPkiStore.java @@ -154,8 +154,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { private static final String SIGN_FINGERPRINT_PREFIX = "signfp:v1:"; private static final int SIGN_FINGERPRINT_HEX_LENGTH = 64; private static final long INITIAL_FENCE = 0L; - private static final String STORE_OWNERSHIP_UNAVAILABLE = - "Filesystem store ownership unavailable: code=STORE_ALREADY_OPEN"; + private static final String STORE_OWNERSHIP_UNAVAILABLE = "Filesystem store ownership unavailable: code=STORE_ALREADY_OPEN"; private final FsPkiStoreOptions options; private final FsPaths paths; @@ -186,15 +185,17 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { /** * Opens or creates a filesystem store with an explicit authoritative clock. * - * @param root store root directory + * @param root store root directory * @param options store options - * @param clock authoritative signing workflow clock + * @param clock authoritative signing workflow clock * @throws IllegalArgumentException if an argument is {@code null} - * @throws IllegalStateException if the store cannot be opened or is already - * owned by another store instance or process + * @throws IllegalStateException if the store cannot be opened or is already + * owned by another store instance or process */ - // StoreOwnership transfers to this store on success and remains open until close(); - // try-with-resources here would release process ownership at constructor return. + // StoreOwnership transfers to this store on success and remains open until + // close(); + // try-with-resources here would release process ownership at constructor + // return. @SuppressWarnings("PMD.CloseResource") public FilesystemPkiStore(final Path root, final FsPkiStoreOptions options, final Clock clock) { this.options = Objects.requireNonNull(options, "options"); @@ -350,8 +351,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { FsCodec.encode(FsCodec.REVOCATION_JOURNAL, updated)); } catch (FsOperations.DurabilityUncertainException failure) { durabilityUncertain.set(true); - throw new PkiException( - "Revocation durability unconfirmed: code=REVOCATION_DURABILITY_UNCONFIRMED"); + throw new PkiException("Revocation durability unconfirmed: code=REVOCATION_DURABILITY_UNCONFIRMED"); } catch (IOException ex) { throw new PkiException("Revocation persistence failed: code=REVOCATION_PERSIST_FAILED"); } @@ -448,7 +448,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } @Override - // Persistence causes may contain filesystem data and are intentionally redacted. + // Persistence causes may contain filesystem data and are intentionally + // redacted. @SuppressWarnings("PMD.PreserveStackTrace") public ImportedCertificateProfileVersion importProfileVersion(final ImportedCertificateProfileVersion version) { requireStoreUsable(); @@ -489,10 +490,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } private void requireConsistentProfileKind(ImportedCertificateProfileVersion candidate) { - for (ImportedCertificateProfileVersion existing : - listProfileVersions(candidate.reference().profileId())) { - if (existing.definition().certificateType() - != candidate.definition().certificateType()) { + for (ImportedCertificateProfileVersion existing : listProfileVersions(candidate.reference().profileId())) { + if (existing.definition().certificateType() != candidate.definition().certificateType()) { throw new ProfileLifecycleFailure(Code.PROFILE_KIND_CONFLICT); } } @@ -532,7 +531,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } @Override - // Persistence causes may contain filesystem data and are intentionally redacted. + // Persistence causes may contain filesystem data and are intentionally + // redacted. @SuppressWarnings("PMD.PreserveStackTrace") public CertificateProfileRef activateProfile(final String profileId, final long profileVersion) { requireStoreUsable(); @@ -723,12 +723,9 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { return List.of(); } try (Stream directories = Files.list(root)) { - return directories.filter(Files::isDirectory) - .map(directory -> directory.resolve(FsPaths.CURRENT_FILE)) - .filter(Files::isRegularFile) - .sorted(Comparator.comparing(Path::toString)) - .map(this::readSignRecordFile) - .peek(record -> validateSignRecord(record.submissionId(), record)) + return directories.filter(Files::isDirectory).map(directory -> directory.resolve(FsPaths.CURRENT_FILE)) + .filter(Files::isRegularFile).sorted(Comparator.comparing(Path::toString)) + .map(this::readSignRecordFile).peek(record -> validateSignRecord(record.submissionId(), record)) .toList(); } catch (IOException ex) { throw new IllegalStateException("Failed to list authoritative signing records", ex); @@ -736,8 +733,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } @Override - public Optional tryClaimSign(PkiId submissionId, long expectedRevision, - Duration lease) { + public Optional tryClaimSign(PkiId submissionId, long expectedRevision, Duration lease) { requireStoreUsable(); requirePositive(lease, "lease"); SignLockEntry lock = acquireSignLock(submissionId); @@ -778,8 +774,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { if (current.revision() != expectedRevision || current.fence() != fence || current.leaseUntil().isEmpty()) { return Optional.empty(); } - SignWorkflowStore.Record renewed = copySignRecord(current, current.state(), current.revision() + 1L, - fence, Optional.of(signingNow().plus(lease)), current.detailCode(), current.result(), + SignWorkflowStore.Record renewed = copySignRecord(current, current.state(), current.revision() + 1L, fence, + Optional.of(signingNow().plus(lease)), current.detailCode(), current.result(), current.providerUpdatedAt()); writeSignRecord(renewed); return Optional.of(renewed); @@ -813,8 +809,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { throw new IllegalArgumentException("Signing result does not match target state"); } if (target == SignWorkflowStore.State.SUCCEEDED - && (providerUpdatedAt.isEmpty() - || !providerUpdatedAt.get().isBefore(current.deadline()))) { + && (providerUpdatedAt.isEmpty() || !providerUpdatedAt.get().isBefore(current.deadline()))) { throw new IllegalArgumentException("Successful signing completion timestamp is not trustworthy"); } SignWorkflowStore.Record transitioned = copySignRecord(current, target, current.revision() + 1L, fence, @@ -865,8 +860,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { try { Optional current = readSignRecord(record.submissionId()); if (current.isPresent() && current.get().state() == SignWorkflowStore.State.RETIRED - && !current.get().createdAt().plus(options.signingOperationHorizon()) - .isAfter(signingNow()) + && !current.get().createdAt().plus(options.signingOperationHorizon()).isAfter(signingNow()) && Files.deleteIfExists(paths.signWorkflowPath(record.submissionId()))) { purged++; } @@ -962,8 +956,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { return Optional.empty(); } try { - CertificateProfileRef reference = FsCodec.decode(FsCodec.ACTIVE_PROFILE_REF, - FsOperations.readAll(path)); + CertificateProfileRef reference = FsCodec.decode(FsCodec.ACTIVE_PROFILE_REF, FsOperations.readAll(path)); if (!profileId.equals(reference.profileId())) { throw new ProfileLifecycleFailure(Code.PROFILE_ACTIVE_POINTER_CORRUPT); } @@ -989,8 +982,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { if (!Files.isRegularFile(path)) { throw new ProfileLifecycleFailure(Code.PROFILE_ACTIVE_POINTER_CORRUPT); } - ValidatedImportedProfile validated = - decodeValidatedProfileVersion(path, profileId, reference.profileVersion()); + ValidatedImportedProfile validated = decodeValidatedProfileVersion(path, profileId, + reference.profileVersion()); if (!reference.equals(validated.version().reference())) { throw new ProfileLifecycleFailure(Code.PROFILE_HASH_MISMATCH); } @@ -1080,23 +1073,22 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { if (current.isPresent() && transitionTime.isBefore(current.get().latest().time())) { throw new PkiException("Revocation transition conflict: code=REVOCATION_TRANSITION_CONFLICT"); } - RevocationState previous = current.map(RevocationJournal::latest).map(RevocationTransition::state) - .orElse(null); + RevocationState previous = current.map(RevocationJournal::latest).map(RevocationTransition::state).orElse(null); RevocationState next = nextRevocationState(previous, command); - long revision = current.map(RevocationJournal::latest).map(RevocationTransition::revision) - .map(value -> { - try { - return Math.addExact(value, 1L); - } catch (ArithmeticException ex) { - throw corruptRevocationState(); - } - }).orElse(1L); + long revision = current.map(RevocationJournal::latest).map(RevocationTransition::revision).map(value -> { + try { + return Math.addExact(value, 1L); + } catch (ArithmeticException ex) { + throw corruptRevocationState(); + } + }).orElse(1L); List transitions = new ArrayList<>( current.map(RevocationJournal::transitions).orElseGet(List::of)); Optional permanentReason = command instanceof RevocationCommand.RevokePermanently revoke - ? Optional.of(revoke.reason()) : Optional.empty(); - transitions.add(new RevocationTransition(revision, next, transitionTime, - permanentReason, command.attributes())); + ? Optional.of(revoke.reason()) + : Optional.empty(); + transitions + .add(new RevocationTransition(revision, next, transitionTime, permanentReason, command.attributes())); RevocationJournal journal = new RevocationJournal(credentialId, transitions); validateRevocationJournal(credentialId, journal); return journal; @@ -1113,12 +1105,11 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { : hold ? RevocationState.HELD : RevocationState.PERMANENTLY_REVOKED; } return switch (previous) { - case CLEAR -> unhold ? throwIllegalTransition() - : hold ? RevocationState.HELD : RevocationState.PERMANENTLY_REVOKED; - case HELD -> hold ? throwIllegalTransition() - : unhold ? RevocationState.CLEAR : RevocationState.PERMANENTLY_REVOKED; - case PERMANENTLY_REVOKED -> throw new PkiException( - "Revocation is terminal: code=REVOCATION_TERMINAL"); + case CLEAR -> + unhold ? throwIllegalTransition() : hold ? RevocationState.HELD : RevocationState.PERMANENTLY_REVOKED; + case HELD -> + hold ? throwIllegalTransition() : unhold ? RevocationState.CLEAR : RevocationState.PERMANENTLY_REVOKED; + case PERMANENTLY_REVOKED -> throw new PkiException("Revocation is terminal: code=REVOCATION_TERMINAL"); }; } @@ -1172,7 +1163,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } // Parsing and canonicalization failures can carry persisted request material; - // validation deliberately replaces every such cause with a safe corruption code. + // validation deliberately replaces every such cause with a safe corruption + // code. @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) private void validateSignRecord(PkiId requestedId, SignWorkflowStore.Record record) { Objects.requireNonNull(requestedId, "requestedId"); @@ -1197,8 +1189,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { requireValidSignRecord(parsed.namespace().equals(record.namespace()), record, "ID_NAMESPACE_MISMATCH"); requireValidSignRecord(parsed.createdAt().equals(record.createdAt()), record, "ID_TIMESTAMP_MISMATCH"); requireValidSignRecord(!record.createdAt().isAfter(futureLimit), record, "CREATION_TIME_FUTURE"); - requireValidSignRecord(record.deadline().isAfter(record.createdAt()) - && !record.deadline().isAfter(horizonEnd), record, "DEADLINE_INVALID"); + requireValidSignRecord(record.deadline().isAfter(record.createdAt()) && !record.deadline().isAfter(horizonEnd), + record, "DEADLINE_INVALID"); requireValidSignRecord(continuation.isBoundTo(record.submissionId(), record.owner()), record, "CONTINUATION_IDENTITY_MISMATCH"); @@ -1275,9 +1267,9 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { || target == SignWorkflowStore.State.CANCELLING || target == SignWorkflowStore.State.EXPIRED; } if (source == SignWorkflowStore.State.CANCELLING) { - return target == SignWorkflowStore.State.CANCELLING - || target == SignWorkflowStore.State.SUCCEEDED || target == SignWorkflowStore.State.FAILED - || target == SignWorkflowStore.State.CANCELLED || target == SignWorkflowStore.State.EXPIRED; + return target == SignWorkflowStore.State.CANCELLING || target == SignWorkflowStore.State.SUCCEEDED + || target == SignWorkflowStore.State.FAILED || target == SignWorkflowStore.State.CANCELLED + || target == SignWorkflowStore.State.EXPIRED; } return false; } @@ -1297,8 +1289,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { requireValidSignRecord(record.revision() == 0L && record.leaseUntil().isEmpty(), record, "INTENT_INITIAL_CLAIM_INVALID"); } else { - requireValidSignRecord(record.revision() >= record.fence() - && record.leaseUntil().isPresent(), record, "INTENT_CLAIM_INVALID"); + requireValidSignRecord(record.revision() >= record.fence() && record.leaseUntil().isPresent(), + record, "INTENT_CLAIM_INVALID"); } } case DISPATCHED -> { @@ -1312,8 +1304,9 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { requireValidSignRecord(record.revision() > record.fence(), record, "CANCELLING_REVISION_INVALID"); requireValidSignRecord(record.leaseUntil().isEmpty() && record.result().isEmpty() && record.providerUpdatedAt().isEmpty(), record, "CANCELLING_TERMINAL_DATA_PRESENT"); - requireValidSignRecord(record.detailCode().filter(code -> "CANCEL_REQUESTED".equals(code) - || "CANCEL_SUBMITTED".equals(code)).isPresent(), record, "DETAIL_CODE_INVALID"); + requireValidSignRecord(record.detailCode() + .filter(code -> "CANCEL_REQUESTED".equals(code) || "CANCEL_SUBMITTED".equals(code)).isPresent(), + record, "DETAIL_CODE_INVALID"); } case SUCCEEDED -> { requireValidSignRecord(record.fence() > 0L && record.revision() - record.fence() >= 2L, record, @@ -1334,8 +1327,7 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { "FAILURE_DETAIL_CONTRADICTORY"); } case CANCELLED -> { - requireValidSignRecord(record.revision() - record.fence() >= 2L, record, - "CANCELLED_REVISION_INVALID"); + requireValidSignRecord(record.revision() - record.fence() >= 2L, record, "CANCELLED_REVISION_INVALID"); requireValidSignRecord(record.leaseUntil().isEmpty() && record.result().isEmpty(), record, "CANCELLED_RESULT_OR_LEASE_PRESENT"); requireValidSignRecord(!hasLifecycleCode(record.detailCode(), "SIGNED"), record, @@ -1352,10 +1344,11 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { requireValidSignRecord(record.leaseUntil().isEmpty(), record, "RETIRED_LEASE_PRESENT"); requireDetailCode(record, "RETIRED"); if (record.result().isPresent()) { - requireValidSignRecord(record.fence() > 0L && record.revision() - record.fence() >= 3L - && record.providerUpdatedAt().isPresent() - && record.providerUpdatedAt().get().isBefore(record.deadline()), record, - "PROVIDER_TIME_INVALID"); + requireValidSignRecord( + record.fence() > 0L && record.revision() - record.fence() >= 3L + && record.providerUpdatedAt().isPresent() + && record.providerUpdatedAt().get().isBefore(record.deadline()), + record, "PROVIDER_TIME_INVALID"); } else { requireValidSignRecord(record.revision() - record.fence() >= 2L, record, "RETIRED_TERMINAL_REVISION_INVALID"); @@ -1390,12 +1383,9 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } private static boolean hasContradictorySuccessCode(Optional detailCode) { - return hasLifecycleCode(detailCode, "INTENT") - || hasLifecycleCode(detailCode, "DISPATCHED") - || hasLifecycleCode(detailCode, "CANCEL_REQUESTED") - || hasLifecycleCode(detailCode, "CANCELLED") - || hasLifecycleCode(detailCode, "EXPIRED") - || hasLifecycleCode(detailCode, "RETIRED") + return hasLifecycleCode(detailCode, "INTENT") || hasLifecycleCode(detailCode, "DISPATCHED") + || hasLifecycleCode(detailCode, "CANCEL_REQUESTED") || hasLifecycleCode(detailCode, "CANCELLED") + || hasLifecycleCode(detailCode, "EXPIRED") || hasLifecycleCode(detailCode, "RETIRED") || hasLifecycleCode(detailCode, "REQUEST_INTEGRITY_FAILURE") || hasLifecycleCode(detailCode, "PROVIDER_ID_MISMATCH") || hasLifecycleCode(detailCode, "PROVIDER_SUBMISSION_FAILED") @@ -1473,7 +1463,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { this.lock = lock; } - // Raw lock exceptions are deliberately replaced by the stable redacted contention code. + // Raw lock exceptions are deliberately replaced by the stable redacted + // contention code. @SuppressWarnings("PMD.PreserveStackTrace") private static StoreOwnership acquire(Path lockFile) throws IOException { FileChannel channel = FileChannel.open(lockFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE); @@ -1534,8 +1525,8 @@ public final class FilesystemPkiStore implements PkiStore, Closeable { } closed = true; if (releaseFailed || closeFailed) { - throw new IOException("Filesystem store ownership release failed: " - + "code=STORE_OWNERSHIP_RELEASE_FAILED"); + throw new IOException( + "Filesystem store ownership release failed: " + "code=STORE_OWNERSHIP_RELEASE_FAILED"); } } finally { closeLock.unlock(); diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java b/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java index 997e43f..57cfcff 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/FsCodec.java @@ -183,8 +183,7 @@ final class FsCodec { private static final int MARKER_ABSENT = 0; private static final int MARKER_PRESENT = 1; - private static final ValueSchema STRING = valueSchema(TYPE_STRING, Writer::writeString, - Reader::readString); + private static final ValueSchema STRING = valueSchema(TYPE_STRING, Writer::writeString, Reader::readString); private static final ValueSchema BOOLEAN = valueSchema(TYPE_BOOLEAN, Writer::writeBoolean, Reader::readBoolean); private static final ValueSchema LONG = valueSchema(TYPE_LONG, Writer::writeLong, Reader::readLong); @@ -196,55 +195,47 @@ final class FsCodec { writer.writeValue(STRING, value.profileId()); writer.writeValue(LONG, value.profileVersion()); writer.writeValue(BYTES, value.canonicalSha256()); - }, - reader -> new CertificateProfileRef(reader.readValue(STRING), reader.readValue(LONG), + }, reader -> new CertificateProfileRef(reader.readValue(STRING), reader.readValue(LONG), reader.readValue(BYTES))); - private static final ValueSchema PROFILE_BINDING = - valueSchema(TYPE_PROFILE_BINDING, FsCodec::writeProfileBinding, FsCodec::readProfileBinding); + private static final ValueSchema PROFILE_BINDING = valueSchema(TYPE_PROFILE_BINDING, + FsCodec::writeProfileBinding, FsCodec::readProfileBinding); - private static final ValueSchema ENCODING = enumSchema(TYPE_ENCODING_ENUM, - value -> switch (value) { - case DER -> 1; - case PEM -> 2; - case BINARY -> 3; - }, - code -> switch (code) { - case 1 -> Encoding.DER; - case 2 -> Encoding.PEM; - case 3 -> Encoding.BINARY; - default -> throw unknownEnum("Encoding", code); - }); - private static final ValueSchema CA_KIND = enumSchema(TYPE_CA_KIND_ENUM, - value -> switch (value) { - case ROOT -> 1; - case INTERMEDIATE -> 2; - }, - code -> switch (code) { - case 1 -> CaKind.ROOT; - case 2 -> CaKind.INTERMEDIATE; - default -> throw unknownEnum("CaKind", code); - }); - private static final ValueSchema CA_STATE = enumSchema(TYPE_CA_STATE_ENUM, - value -> switch (value) { - case ACTIVE -> 1; - case RETIRED -> 2; - case COMPROMISED -> 3; - case DISABLED -> 4; - }, - code -> switch (code) { - case 1 -> CaState.ACTIVE; - case 2 -> CaState.RETIRED; - case 3 -> CaState.COMPROMISED; - case 4 -> CaState.DISABLED; - default -> throw unknownEnum("CaState", code); - }); + private static final ValueSchema ENCODING = enumSchema(TYPE_ENCODING_ENUM, value -> switch (value) { + case DER -> 1; + case PEM -> 2; + case BINARY -> 3; + }, code -> switch (code) { + case 1 -> Encoding.DER; + case 2 -> Encoding.PEM; + case 3 -> Encoding.BINARY; + default -> throw unknownEnum("Encoding", code); + }); + private static final ValueSchema CA_KIND = enumSchema(TYPE_CA_KIND_ENUM, value -> switch (value) { + case ROOT -> 1; + case INTERMEDIATE -> 2; + }, code -> switch (code) { + case 1 -> CaKind.ROOT; + case 2 -> CaKind.INTERMEDIATE; + default -> throw unknownEnum("CaKind", code); + }); + private static final ValueSchema CA_STATE = enumSchema(TYPE_CA_STATE_ENUM, value -> switch (value) { + case ACTIVE -> 1; + case RETIRED -> 2; + case COMPROMISED -> 3; + case DISABLED -> 4; + }, code -> switch (code) { + case 1 -> CaState.ACTIVE; + case 2 -> CaState.RETIRED; + case 3 -> CaState.COMPROMISED; + case 4 -> CaState.DISABLED; + default -> throw unknownEnum("CaState", code); + }); private static final ValueSchema CREDENTIAL_STATUS = enumSchema(TYPE_CREDENTIAL_STATUS_ENUM, value -> switch (value) { case ISSUED -> 1; case REVOKED -> 2; case EXPIRED -> 3; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> CredentialStatus.ISSUED; case 2 -> CredentialStatus.REVOKED; case 3 -> CredentialStatus.EXPIRED; @@ -262,8 +253,7 @@ final class FsCodec { case REMOVE_FROM_CRL -> 8; case PRIVILEGE_WITHDRAWN -> 9; case AA_COMPROMISE -> 10; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> RevocationReason.UNSPECIFIED; case 2 -> RevocationReason.KEY_COMPROMISE; case 3 -> RevocationReason.CA_COMPROMISE; @@ -281,8 +271,7 @@ final class FsCodec { case CLEAR -> 1; case HELD -> 2; case PERMANENTLY_REVOKED -> 3; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> RevocationState.CLEAR; case 2 -> RevocationState.HELD; case 3 -> RevocationState.PERMANENTLY_REVOKED; @@ -294,8 +283,7 @@ final class FsCodec { case DELTA_CRL -> 2; case OCSP -> 3; case REVOCATION_LIST -> 4; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> StatusObjectType.CRL; case 2 -> StatusObjectType.DELTA_CRL; case 3 -> StatusObjectType.OCSP; @@ -303,15 +291,13 @@ final class FsCodec { default -> throw unknownEnum("StatusObjectType", code); }); private static final ValueSchema PUBLICATION_TARGET_TYPE = enumSchema( - TYPE_PUBLICATION_TARGET_TYPE_ENUM, - value -> switch (value) { + TYPE_PUBLICATION_TARGET_TYPE_ENUM, value -> switch (value) { case FILESYSTEM -> 1; case LDAP -> 2; case HTTP -> 3; case OBJECT_STORE -> 4; case CUSTOM -> 5; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> PublicationTargetType.FILESYSTEM; case 2 -> PublicationTargetType.LDAP; case 3 -> PublicationTargetType.HTTP; @@ -324,29 +310,25 @@ final class FsCodec { case PUBLISHED -> 1; case SKIPPED -> 2; case FAILED -> 3; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> PublicationStatus.PUBLISHED; case 2 -> PublicationStatus.SKIPPED; case 3 -> PublicationStatus.FAILED; default -> throw unknownEnum("PublicationStatus", code); }); private static final ValueSchema DURABILITY_POLICY = enumSchema( - TYPE_DURABILITY_POLICY_ENUM, - value -> switch (value) { + TYPE_DURABILITY_POLICY_ENUM, value -> switch (value) { case STRICT_ABORT_ON_RESTART -> 1; case DURABLE_MIN_STATE -> 2; case DURABLE_ENCRYPTED_STATE -> 3; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> OrchestrationDurabilityPolicy.STRICT_ABORT_ON_RESTART; case 2 -> OrchestrationDurabilityPolicy.DURABLE_MIN_STATE; case 3 -> OrchestrationDurabilityPolicy.DURABLE_ENCRYPTED_STATE; default -> throw unknownEnum("OrchestrationDurabilityPolicy", code); }); private static final ValueSchema SIGN_STATE = valueSchema(TYPE_SIGN_STATE_ENUM, - (writer, value) -> writer.writeUnsignedByte(value.persistentCode()), - reader -> { + (writer, value) -> writer.writeUnsignedByte(value.persistentCode()), reader -> { int code = reader.readUnsignedByte(); try { return SignWorkflowStore.State.fromPersistentCode(code); @@ -365,8 +347,7 @@ final class FsCodec { case SERIAL_NUMBER -> 7; case EMAIL_ADDRESS -> 8; case PSEUDONYM -> 9; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> SubjectRdnType.COMMON_NAME; case 2 -> SubjectRdnType.ORGANIZATION_NAME; case 3 -> SubjectRdnType.ORGANIZATIONAL_UNIT_NAME; @@ -384,8 +365,7 @@ final class FsCodec { case IP_ADDRESS -> 2; case URI -> 3; case RFC822_NAME -> 4; - }, - code -> switch (code) { + }, code -> switch (code) { case 1 -> SubjectAlternativeNameType.DNS_NAME; case 2 -> SubjectAlternativeNameType.IP_ADDRESS; case 3 -> SubjectAlternativeNameType.URI; @@ -393,8 +373,7 @@ final class FsCodec { default -> throw unknownEnum("SubjectAlternativeNameType", code); }); private static final ValueSchema PKI_ID = valueSchema(TYPE_PKI_ID, - (writer, value) -> writer.writeValue(STRING, value.value()), - reader -> new PkiId(reader.readValue(STRING))); + (writer, value) -> writer.writeValue(STRING, value.value()), reader -> new PkiId(reader.readValue(STRING))); private static final ValueSchema KEY_REF = valueSchema(TYPE_KEY_REF, (writer, value) -> writer.writeValue(STRING, value.value()), reader -> new KeyRef(reader.readValue(STRING))); @@ -407,29 +386,23 @@ final class FsCodec { private static final ValueSchema FORMAT_ID = valueSchema(TYPE_FORMAT_ID, (writer, value) -> writer.writeValue(STRING, value.value()), reader -> new FormatId(reader.readValue(STRING))); - private static final ValueSchema VALIDITY = valueSchema(TYPE_VALIDITY, - (writer, value) -> { - writer.writeValue(INSTANT, value.notBefore()); - writer.writeValue(INSTANT, value.notAfter()); - }, - reader -> new Validity(reader.readValue(INSTANT), reader.readValue(INSTANT))); + private static final ValueSchema VALIDITY = valueSchema(TYPE_VALIDITY, (writer, value) -> { + writer.writeValue(INSTANT, value.notBefore()); + writer.writeValue(INSTANT, value.notAfter()); + }, reader -> new Validity(reader.readValue(INSTANT), reader.readValue(INSTANT))); private static final ValueSchema ENCODED_OBJECT = valueSchema(TYPE_ENCODED_OBJECT, FsCodec::writeEncodedObject, FsCodec::readEncodedObject); - private static final ValueSchema PRINCIPAL = valueSchema(TYPE_PRINCIPAL, - (writer, value) -> { - writer.writeValue(STRING, value.type()); - writer.writeValue(STRING, value.name()); - }, - reader -> new Principal(reader.readValue(STRING), reader.readValue(STRING))); + private static final ValueSchema PRINCIPAL = valueSchema(TYPE_PRINCIPAL, (writer, value) -> { + writer.writeValue(STRING, value.type()); + writer.writeValue(STRING, value.name()); + }, reader -> new Principal(reader.readValue(STRING), reader.readValue(STRING))); private static final ValueSchema ATTRIBUTE_ID = valueSchema(TYPE_ATTRIBUTE_ID, (writer, value) -> writer.writeValue(STRING, value.value()), reader -> new AttributeId(reader.readValue(STRING))); - private static final ValueSchema SUBJECT_RDN = valueSchema(TYPE_SUBJECT_RDN, - (writer, value) -> { - writer.writeValue(SUBJECT_RDN_TYPE, value.type()); - writer.writeValue(STRING, value.value()); - }, - reader -> new SubjectRdn(reader.readValue(SUBJECT_RDN_TYPE), reader.readValue(STRING))); + private static final ValueSchema SUBJECT_RDN = valueSchema(TYPE_SUBJECT_RDN, (writer, value) -> { + writer.writeValue(SUBJECT_RDN_TYPE, value.type()); + writer.writeValue(STRING, value.value()); + }, reader -> new SubjectRdn(reader.readValue(SUBJECT_RDN_TYPE), reader.readValue(STRING))); private static final ValueSchema> SUBJECT_RDNS = listOf(SUBJECT_RDN); private static final ValueSchema SUBJECT_ALT_NAME = valueSchema(TYPE_SAN, FsCodec::writeSubjectAlternativeName, FsCodec::readSubjectAlternativeName); @@ -446,8 +419,7 @@ final class FsCodec { writer.writeValue(STRING, value.ruleId()); writer.writeValue(STRING, value.outcome()); writer.writeValue(STRINGS, value.notes()); - }, - reader -> new PolicyTraceStep(reader.readValue(STRING), reader.readValue(STRING), + }, reader -> new PolicyTraceStep(reader.readValue(STRING), reader.readValue(STRING), reader.readValue(STRINGS))); private static final ValueSchema> POLICY_TRACE_STEPS = listOf(POLICY_TRACE_STEP); private static final ValueSchema PUBLICATION_TARGET = valueSchema(TYPE_PUBLICATION_TARGET, @@ -455,21 +427,18 @@ final class FsCodec { writer.writeValue(PUBLICATION_TARGET_TYPE, value.type()); writer.writeValue(STRING, value.targetId()); writer.writeValue(ATTRIBUTE_SET, value.attributes()); - }, - reader -> new PublicationTarget(reader.readValue(PUBLICATION_TARGET_TYPE), reader.readValue(STRING), + }, reader -> new PublicationTarget(reader.readValue(PUBLICATION_TARGET_TYPE), reader.readValue(STRING), reader.readValue(ATTRIBUTE_SET))); private static final ValueSchema> OPTIONAL_VALIDITY = optionalOf(VALIDITY); private static final ValueSchema> OPTIONAL_STRING = optionalOf(STRING); private static final ValueSchema> OPTIONAL_INSTANT = optionalOf(INSTANT); private static final ValueSchema> OPTIONAL_ENCODED_OBJECT = optionalOf(ENCODED_OBJECT); - private static final ValueSchema> OPTIONAL_REVOCATION_REASON = - optionalOf(REVOCATION_REASON); - private static final ValueSchema REVOCATION_TRANSITION = - valueSchema(TYPE_REVOCATION_TRANSITION, FsCodec::writeRevocationTransition, - FsCodec::readRevocationTransition); - private static final ValueSchema> REVOCATION_TRANSITIONS = - listOf(REVOCATION_TRANSITION); + private static final ValueSchema> OPTIONAL_REVOCATION_REASON = optionalOf( + REVOCATION_REASON); + private static final ValueSchema REVOCATION_TRANSITION = valueSchema( + TYPE_REVOCATION_TRANSITION, FsCodec::writeRevocationTransition, FsCodec::readRevocationTransition); + private static final ValueSchema> REVOCATION_TRANSITIONS = listOf(REVOCATION_TRANSITION); private static final ValueSchema CREDENTIAL_VALUE = valueSchema(TYPE_CREDENTIAL_RECORD, FsCodec::writeCredential, FsCodec::readCredential); @@ -477,13 +446,11 @@ final class FsCodec { /* package */ static final Schema CA_RECORD = topLevel(TOP_CA_RECORD, "CA_RECORD", valueSchema(100, FsCodec::writeCaRecord, FsCodec::readCaRecord)); - /* package */ static final Schema CREDENTIAL = topLevel(TOP_CREDENTIAL, "CREDENTIAL", - CREDENTIAL_VALUE); + /* package */ static final Schema CREDENTIAL = topLevel(TOP_CREDENTIAL, "CREDENTIAL", CREDENTIAL_VALUE); /* package */ static final Schema PARSED_REQUEST = topLevel(TOP_PARSED_REQUEST, "PARSED_REQUEST", valueSchema(101, FsCodec::writeParsedRequest, FsCodec::readParsedRequest)); - /* package */ static final Schema REVOCATION_JOURNAL = - topLevel(TOP_REVOCATION, "REVOCATION_JOURNAL", - valueSchema(102, FsCodec::writeRevocationJournal, FsCodec::readRevocationJournal)); + /* package */ static final Schema REVOCATION_JOURNAL = topLevel(TOP_REVOCATION, + "REVOCATION_JOURNAL", valueSchema(102, FsCodec::writeRevocationJournal, FsCodec::readRevocationJournal)); /* package */ static final Schema STATUS_OBJECT = topLevel(TOP_STATUS_OBJECT, "STATUS_OBJECT", valueSchema(103, FsCodec::writeStatusObject, FsCodec::readStatusObject)); /* package */ static final Schema PUBLICATION = topLevel(TOP_PUBLICATION, "PUBLICATION", @@ -495,24 +462,17 @@ final class FsCodec { /* package */ static final Schema SIGN_WORKFLOW_RECORD = topLevel( TOP_SIGN_WORKFLOW_RECORD, "SIGN_WORKFLOW_RECORD", valueSchema(108, FsCodec::writeSignWorkflowRecord, FsCodec::readSignWorkflowRecord)); - /* package */ static final Schema PROFILE_VERSION = - topLevel(TOP_PROFILE_VERSION, "PROFILE_VERSION", - valueSchema(109, FsCodec::writeProfileVersion, FsCodec::readProfileVersion)); - /* package */ static final Schema ACTIVE_PROFILE_REF = - topLevel(TOP_ACTIVE_PROFILE_REF, "ACTIVE_PROFILE_REF", PROFILE_REF); + /* package */ static final Schema PROFILE_VERSION = topLevel(TOP_PROFILE_VERSION, + "PROFILE_VERSION", valueSchema(109, FsCodec::writeProfileVersion, FsCodec::readProfileVersion)); + /* package */ static final Schema ACTIVE_PROFILE_REF = topLevel(TOP_ACTIVE_PROFILE_REF, + "ACTIVE_PROFILE_REF", PROFILE_REF); - private static final Map> TOP_LEVEL_SCHEMAS = Map.ofEntries( - Map.entry(TOP_CA_RECORD, CA_RECORD), - Map.entry(TOP_CREDENTIAL, CREDENTIAL), - Map.entry(TOP_PARSED_REQUEST, PARSED_REQUEST), - Map.entry(TOP_REVOCATION, REVOCATION_JOURNAL), - Map.entry(TOP_STATUS_OBJECT, STATUS_OBJECT), - Map.entry(TOP_PUBLICATION, PUBLICATION), - Map.entry(TOP_POLICY_TRACE, POLICY_TRACE), - Map.entry(TOP_WORKFLOW_STATE, WORKFLOW_STATE), - Map.entry(TOP_SIGN_WORKFLOW_RECORD, SIGN_WORKFLOW_RECORD), - Map.entry(TOP_PROFILE_VERSION, PROFILE_VERSION), - Map.entry(TOP_ACTIVE_PROFILE_REF, ACTIVE_PROFILE_REF)); + private static final Map> TOP_LEVEL_SCHEMAS = Map.ofEntries(Map.entry(TOP_CA_RECORD, CA_RECORD), + Map.entry(TOP_CREDENTIAL, CREDENTIAL), Map.entry(TOP_PARSED_REQUEST, PARSED_REQUEST), + Map.entry(TOP_REVOCATION, REVOCATION_JOURNAL), Map.entry(TOP_STATUS_OBJECT, STATUS_OBJECT), + Map.entry(TOP_PUBLICATION, PUBLICATION), Map.entry(TOP_POLICY_TRACE, POLICY_TRACE), + Map.entry(TOP_WORKFLOW_STATE, WORKFLOW_STATE), Map.entry(TOP_SIGN_WORKFLOW_RECORD, SIGN_WORKFLOW_RECORD), + Map.entry(TOP_PROFILE_VERSION, PROFILE_VERSION), Map.entry(TOP_ACTIVE_PROFILE_REF, ACTIVE_PROFILE_REF)); private FsCodec() { // utility @@ -540,8 +500,8 @@ final class FsCodec { try { return decodeCurrentPayload(schema, encoded); } catch (IOException | IllegalArgumentException ex) { - throw new IllegalStateException( - "Decoding failed: schema=" + schema.name + " code=INVALID_CURRENT_PAYLOAD", ex); + throw new IllegalStateException("Decoding failed: schema=" + schema.name + " code=INVALID_CURRENT_PAYLOAD", + ex); } } @@ -653,8 +613,8 @@ final class FsCodec { int count = reader.readCount(); List entries = new ArrayList<>(count); for (int index = 0; index < count; index++) { - entries.add(new SimpleAttributeSet.Entry(reader.readValue(ATTRIBUTE_ID), - reader.readValue(ATTRIBUTE_VALUES))); + entries.add( + new SimpleAttributeSet.Entry(reader.readValue(ATTRIBUTE_ID), reader.readValue(ATTRIBUTE_VALUES))); } return new SimpleAttributeSet(entries); } @@ -796,8 +756,7 @@ final class FsCodec { reader.readValue(PUBLICATION_STATUS)); } - private static void writeProfileVersion(Writer writer, ImportedCertificateProfileVersion value) - throws IOException { + private static void writeProfileVersion(Writer writer, ImportedCertificateProfileVersion value) throws IOException { writer.writeValue(PROFILE_REF, value.reference()); writer.writeValue(LONG, (long) value.schemaVersion()); writer.writeValue(BYTES, value.canonicalJson()); @@ -867,10 +826,9 @@ final class FsCodec { } private static WorkflowStateRecord readWorkflowState(Reader reader) throws IOException { - return new WorkflowStateRecord(reader.readValue(PKI_ID), reader.readValue(STRING), - reader.readValue(PRINCIPAL), reader.readValue(DURABILITY_POLICY), reader.readValue(INSTANT), - reader.readValue(INSTANT), reader.readValue(INSTANT), reader.readValue(ENCODING), - reader.readValue(OPTIONAL_ENCODED_OBJECT)); + return new WorkflowStateRecord(reader.readValue(PKI_ID), reader.readValue(STRING), reader.readValue(PRINCIPAL), + reader.readValue(DURABILITY_POLICY), reader.readValue(INSTANT), reader.readValue(INSTANT), + reader.readValue(INSTANT), reader.readValue(ENCODING), reader.readValue(OPTIONAL_ENCODED_OBJECT)); } private static void writeSignWorkflowRecord(Writer writer, SignWorkflowStore.Record value) throws IOException { @@ -993,7 +951,7 @@ final class FsCodec { * Encodes a value. * * @param writer destination writer - * @param value value to encode + * @param value value to encode * @throws IOException if encoding fails */ void encode(Writer writer, T value) throws IOException; diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FsOperations.java b/pki/src/main/java/zeroecho/pki/impl/fs/FsOperations.java index e4647c6..468a2bc 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/FsOperations.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/FsOperations.java @@ -161,16 +161,18 @@ final class FsOperations { /** * Strictly persists one authoritative revocation journal image. * - *

    The namespace commit point is an {@link StandardCopyOption#ATOMIC_MOVE} - * in the target directory. No non-atomic fallback is permitted. A directory - * force failure after that move is reported distinctly because the durable - * authoritative image is then uncertain.

    + *

    + * The namespace commit point is an {@link StandardCopyOption#ATOMIC_MOVE} in + * the target directory. No non-atomic fallback is permitted. A directory force + * failure after that move is reported distinctly because the durable + * authoritative image is then uncertain. + *

    * * @param target journal target - * @param data complete encoded journal - * @throws IOException on a pre-commit persistence failure + * @param data complete encoded journal + * @throws IOException on a pre-commit persistence failure * @throws DurabilityUncertainException after a committed move whose directory - * force failed + * force failed */ // The original directory-force cause is replaced by a marker that cannot expose // a filesystem path or operating-system diagnostic. @@ -210,10 +212,10 @@ final class FsOperations { * Atomically creates one immutable file and fails when the target exists. * * @param target immutable target - * @param data complete encoded image - * @throws IOException on persistence failure + * @param data complete encoded image + * @throws IOException on persistence failure * @throws DurabilityUncertainException after an atomic move whose directory - * force failed + * force failed */ // The OS failure is deliberately replaced by the cause-free durability state. @SuppressWarnings("PMD.PreserveStackTrace") diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FsPaths.java b/pki/src/main/java/zeroecho/pki/impl/fs/FsPaths.java index 8db3064..fdf000b 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/FsPaths.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/FsPaths.java @@ -140,8 +140,7 @@ final class FsPaths { /* default */ Path credentialPath(final PkiId credentialId) { Objects.requireNonNull(credentialId, "credentialId"); - return this.root.resolve("credentials").resolve(BY_ID) - .resolve(FsUtil.safeId(credentialId) + BINARY_EXTENSION); + return this.root.resolve("credentials").resolve(BY_ID).resolve(FsUtil.safeId(credentialId) + BINARY_EXTENSION); } // ------------------------------------------------------------------------- @@ -150,14 +149,13 @@ final class FsPaths { /* default */ Path requestPath(final PkiId requestId) { Objects.requireNonNull(requestId, "requestId"); - return this.root.resolve("requests").resolve(BY_ID) - .resolve(FsUtil.safeId(requestId) + BINARY_EXTENSION); + return this.root.resolve("requests").resolve(BY_ID).resolve(FsUtil.safeId(requestId) + BINARY_EXTENSION); } /* default */ Path signWorkflowPath(final PkiId submissionId) { Objects.requireNonNull(submissionId, "submissionId"); - return this.root.resolve("sign-workflows").resolve(BY_ID) - .resolve(FsUtil.safeId(submissionId)).resolve(CURRENT_FILE); + return this.root.resolve("sign-workflows").resolve(BY_ID).resolve(FsUtil.safeId(submissionId)) + .resolve(CURRENT_FILE); } /* default */ Path signWorkflowRoot() { diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/FsSnapshotExporter.java b/pki/src/main/java/zeroecho/pki/impl/fs/FsSnapshotExporter.java index a84d53b..d81252d 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/FsSnapshotExporter.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/FsSnapshotExporter.java @@ -98,8 +98,7 @@ final class FsSnapshotExporter { Objects.requireNonNull(at, "at"); try { - List profiles = - preflightImportedProfiles(sourceRoot.resolve("profiles"), at); + List profiles = preflightImportedProfiles(sourceRoot.resolve("profiles"), at); FsOperations.ensureDir(targetRoot); FsPaths dst = new FsPaths(targetRoot); @@ -143,8 +142,7 @@ final class FsSnapshotExporter { } List selected = new ArrayList<>(); try (java.util.stream.Stream files = Files.walk(profilesRoot)) { - for (Path path : files.filter(Files::isRegularFile) - .sorted(Comparator.comparing(Path::toString)).toList()) { + for (Path path : files.filter(Files::isRegularFile).sorted(Comparator.comparing(Path::toString)).toList()) { collectProfileArtifact(profilesRoot, path, at, selected); } } catch (SnapshotProfileFailure failure) { @@ -163,8 +161,7 @@ final class FsSnapshotExporter { if (!Files.exists(profilesRoot, LinkOption.NOFOLLOW_LINKS)) { return false; } - if (!Files.isDirectory(profilesRoot, LinkOption.NOFOLLOW_LINKS) - || !Files.isReadable(profilesRoot)) { + if (!Files.isDirectory(profilesRoot, LinkOption.NOFOLLOW_LINKS) || !Files.isReadable(profilesRoot)) { throw SnapshotProfileFailure.of(Code.PROFILE_STORE_FAILURE); } return true; diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/ValidatedImportedProfile.java b/pki/src/main/java/zeroecho/pki/impl/fs/ValidatedImportedProfile.java index 3d55bd5..435b18e 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/ValidatedImportedProfile.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/ValidatedImportedProfile.java @@ -28,10 +28,8 @@ record ValidatedImportedProfile(ImportedCertificateProfileVersion version) { @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) /* default */ static ValidatedImportedProfile decode(byte[] artifact) { try { - ImportedCertificateProfileVersion decoded = - FsCodec.decode(FsCodec.PROFILE_VERSION, artifact); - return validate(decoded, decoded.reference().profileId(), - decoded.reference().profileVersion()); + ImportedCertificateProfileVersion decoded = FsCodec.decode(FsCodec.PROFILE_VERSION, artifact); + return validate(decoded, decoded.reference().profileId(), decoded.reference().profileVersion()); } catch (ProfileLifecycleFailure failure) { throw failure; } catch (RuntimeException failure) { @@ -46,8 +44,7 @@ record ValidatedImportedProfile(ImportedCertificateProfileVersion version) { @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) /* default */ static ValidatedImportedProfile decode(byte[] artifact, String profileId, long profileVersion) { try { - ImportedCertificateProfileVersion decoded = - FsCodec.decode(FsCodec.PROFILE_VERSION, artifact); + ImportedCertificateProfileVersion decoded = FsCodec.decode(FsCodec.PROFILE_VERSION, artifact); return validate(decoded, profileId, profileVersion); } catch (ProfileLifecycleFailure failure) { throw failure; @@ -61,16 +58,15 @@ record ValidatedImportedProfile(ImportedCertificateProfileVersion version) { * failures, all deliberately normalized without input-bearing causes. */ @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.PreserveStackTrace" }) - /* default */ static ValidatedImportedProfile validate(ImportedCertificateProfileVersion version, - String profileId, long profileVersion) { + /* default */ static ValidatedImportedProfile validate(ImportedCertificateProfileVersion version, String profileId, + long profileVersion) { try { byte[] storedCanonical = version.canonicalJson(); requireDocumentBounds(storedCanonical); CertificateProfileDefinition parsed = CertificateProfileDocumentCodec.parse(storedCanonical); byte[] canonical = CertificateProfileDocumentCodec.writeCanonical(parsed); byte[] expectedHash = sha256(canonical); - requireCanonicalIdentity(version, profileId, profileVersion, storedCanonical, - parsed, canonical); + requireCanonicalIdentity(version, profileId, profileVersion, storedCanonical, parsed, canonical); if (!MessageDigest.isEqual(expectedHash, version.reference().canonicalSha256())) { throw hashMismatch(); } @@ -89,14 +85,11 @@ record ValidatedImportedProfile(ImportedCertificateProfileVersion version) { } } - private static void requireCanonicalIdentity(ImportedCertificateProfileVersion version, - String profileId, long profileVersion, byte[] storedCanonical, - CertificateProfileDefinition parsed, byte[] canonical) { - if (!Arrays.equals(canonical, storedCanonical) - || !parsed.equals(version.definition()) + private static void requireCanonicalIdentity(ImportedCertificateProfileVersion version, String profileId, + long profileVersion, byte[] storedCanonical, CertificateProfileDefinition parsed, byte[] canonical) { + if (!Arrays.equals(canonical, storedCanonical) || !parsed.equals(version.definition()) || !profileId.equals(version.reference().profileId()) - || profileVersion != version.reference().profileVersion() - || !profileId.equals(parsed.profileId()) + || profileVersion != version.reference().profileVersion() || !profileId.equals(parsed.profileId()) || profileVersion != parsed.profileVersion() || version.schemaVersion() != CertificateProfileDefinition.SCHEMA_VERSION) { throw versionCorrupt(); diff --git a/pki/src/main/java/zeroecho/pki/impl/fs/package-info.java b/pki/src/main/java/zeroecho/pki/impl/fs/package-info.java index 94a3666..4bc9de7 100644 --- a/pki/src/main/java/zeroecho/pki/impl/fs/package-info.java +++ b/pki/src/main/java/zeroecho/pki/impl/fs/package-info.java @@ -55,7 +55,8 @@ *
  • Closed current schema – persisted values use fixed type * identifiers and compile-time codecs. Java class names are never persisted or * resolved, nested collections have exact element schemas, and attribute sets - * are stored structurally and decoded to the canonical core implementation.
  • + * are stored structurally and decoded to the canonical core + * implementation. *
  • Strict snapshot semantics – snapshot export reconstructs * a complete store state for a given point in time and fails explicitly if no * valid history entry exists.
  • @@ -87,7 +88,8 @@ *
  • Logging uses {@code java.util.logging} exclusively and never includes * sensitive domain data.
  • *
  • The codec performs no runtime class loading or reflective construction. - * Earlier pre-release persistence formats are rejected rather than migrated.
  • + * Earlier pre-release persistence formats are rejected rather than + * migrated. * * *

    Scope

    diff --git a/pki/src/main/java/zeroecho/pki/spi/bootstrap/PkiBootstrap.java b/pki/src/main/java/zeroecho/pki/spi/bootstrap/PkiBootstrap.java index c2a02b6..5a04b47 100644 --- a/pki/src/main/java/zeroecho/pki/spi/bootstrap/PkiBootstrap.java +++ b/pki/src/main/java/zeroecho/pki/spi/bootstrap/PkiBootstrap.java @@ -204,18 +204,20 @@ public final class PkiBootstrap { * Opens a {@link SignatureWorkflow} using {@link SignatureWorkflowProvider} * discovered via ServiceLoader. * - *

    Runtime capabilities are supplied explicitly and are not represented - * in system properties or {@link ProviderConfig}. Providers that do not use - * a software keyring ignore an absent keyring dependency; a keyring-backed - * provider rejects it before allocating a workflow.

    + *

    + * Runtime capabilities are supplied explicitly and are not represented in + * system properties or {@link ProviderConfig}. Providers that do not use a + * software keyring ignore an absent keyring dependency; a keyring-backed + * provider rejects it before allocating a workflow. + *

    * * @param dependencies explicit process-local runtime dependencies * @return signature workflow (never {@code null}) * @throws NullPointerException if {@code dependencies} is {@code null} - * @throws RuntimeException if provider selection or workflow allocation fails + * @throws RuntimeException if provider selection or workflow allocation + * fails */ - public static SignatureWorkflow openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies dependencies) { + public static SignatureWorkflow openSignatureWorkflow(SignatureWorkflowRuntimeDependencies dependencies) { Objects.requireNonNull(dependencies, "dependencies must not be null"); String requestedId = System.getProperty(PROP_CRYPTO_WORKFLOW_BACKEND); diff --git a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflow.java b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflow.java index c708170..d60a4a8 100644 --- a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflow.java +++ b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflow.java @@ -111,12 +111,11 @@ public interface SignatureWorkflow extends Closeable { * namespace whose provider component is {@link #id()}. *

    * - * @param namespace combined stable store and provider namespace - * @param horizon authoritative operation horizon + * @param namespace combined stable store and provider namespace + * @param horizon authoritative operation horizon * @param permittedSkew authoritative future timestamp skew */ - default void validateSigningDomain(String namespace, java.time.Duration horizon, - java.time.Duration permittedSkew) { + default void validateSigningDomain(String namespace, java.time.Duration horizon, java.time.Duration permittedSkew) { Objects.requireNonNull(namespace, "namespace"); Objects.requireNonNull(horizon, "horizon"); Objects.requireNonNull(permittedSkew, "permittedSkew"); @@ -145,15 +144,16 @@ public interface SignatureWorkflow extends Closeable { *

    * *

    - * {@link SignRequest#fencingToken()} is monotonic. A stale token must not mutate - * state; a completion may commit only while its fingerprint, token, and running - * state remain current. Providers must check an optional request deadline - * before starting cryptography or approval execution and again before committing - * success. A completion at the deadline is late; it must become - * {@link State#EXPIRED} without exposing a result. Terminal states are immutable. At and after the - * configured horizon, submission is rejected and {@link #status(PkiId)} reports - * {@link State#EXPIRED}, including after payload/result purge and restart. - * Provider callbacks must run after operation state locks are released. + * {@link SignRequest#fencingToken()} is monotonic. A stale token must not + * mutate state; a completion may commit only while its fingerprint, token, and + * running state remain current. Providers must check an optional request + * deadline before starting cryptography or approval execution and again before + * committing success. A completion at the deadline is late; it must become + * {@link State#EXPIRED} without exposing a result. Terminal states are + * immutable. At and after the configured horizon, submission is rejected and + * {@link #status(PkiId)} reports {@link State#EXPIRED}, including after + * payload/result purge and restart. Provider callbacks must run after operation + * state locks are released. *

    * *

    Failure model (normative)

    @@ -213,8 +213,8 @@ public interface SignatureWorkflow extends Closeable { *

    * A {@code true} return value means only that the provider accepted the * cancellation request. It does not prove that the operation is terminal. - * Callers must re-read {@link #status(PkiId)} and may retire state only after an - * immutable terminal status is observed. + * Callers must re-read {@link #status(PkiId)} and may retire state only after + * an immutable terminal status is observed. *

    * * @param operationId operation id (never {@code null}) @@ -257,10 +257,12 @@ public interface SignatureWorkflow extends Closeable { * {@link #submitSign(SignRequest)}. *

    * - * @param submissionId stable caller-assigned submission identifier - * @param namespace provider/store namespace - * @param semanticFingerprint versioned request fingerprint excluding fence - * @param fencingToken current positive fencing token + * @param submissionId stable caller-assigned submission + * identifier + * @param namespace provider/store namespace + * @param semanticFingerprint versioned request fingerprint excluding + * fence + * @param fencingToken current positive fencing token * @param accessContext audit/governance context (never * {@code null}) * @param keyRef opaque reference to the private key (never @@ -313,9 +315,9 @@ public interface SignatureWorkflow extends Closeable { } /** - * Computes the canonical {@code signfp:v1} semantic fingerprint in - * O(payload) time and O(payload) temporary memory from the defensive payload - * copy, with constant-size digest state. + * Computes the canonical {@code signfp:v1} semantic fingerprint in O(payload) + * time and O(payload) temporary memory from the defensive payload copy, with + * constant-size digest state. */ public static String fingerprint(String namespace, AccessContext accessContext, KeyRef keyRef, String algorithmId, EncodedObject payload, Optional preferredSignatureEncoding, @@ -460,10 +462,11 @@ public interface SignatureWorkflow extends Closeable { *

    * *

    - * For terminal states, {@link #updatedAt()} is the durable completion timestamp, - * not the time at which a caller happened to poll. Providers must preserve that - * value across restart. A successful status at or after its request deadline is - * invalid and must instead be exposed as {@link State#EXPIRED} without a result. + * For terminal states, {@link #updatedAt()} is the durable completion + * timestamp, not the time at which a caller happened to poll. Providers must + * preserve that value across restart. A successful status at or after its + * request deadline is invalid and must instead be exposed as + * {@link State#EXPIRED} without a result. *

    * * @param state current lifecycle state of the operation (never @@ -485,8 +488,7 @@ public interface SignatureWorkflow extends Closeable { Objects.requireNonNull(updatedAt, "updatedAt"); Objects.requireNonNull(detailCode, "detailCode"); Objects.requireNonNull(result, "result"); - if (state == State.SUCCEEDED && result.isEmpty() - || state != State.SUCCEEDED && result.isPresent()) { + if (state == State.SUCCEEDED && result.isEmpty() || state != State.SUCCEEDED && result.isPresent()) { throw new IllegalArgumentException("Operation result does not match status state"); } } diff --git a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowProvider.java b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowProvider.java index 778bc0f..7958a44 100644 --- a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowProvider.java +++ b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowProvider.java @@ -41,28 +41,30 @@ import zeroecho.pki.spi.ProviderConfig; /** * ServiceLoader provider for {@link SignatureWorkflow}. * - *

    Service loading discovers provider factories only. Runtime capabilities, + *

    + * Service loading discovers provider factories only. Runtime capabilities, * including keyring unlock providers, are supplied explicitly through * {@link #allocate(ProviderConfig, SignatureWorkflowRuntimeDependencies)} and - * are never stored in textual provider configuration.

    + * are never stored in textual provider configuration. + *

    */ public interface SignatureWorkflowProvider extends ConfigurableProvider { /** * Allocates a workflow using explicit process-local runtime dependencies. * - *

    The default implementation supports providers that need no additional - * runtime capability. A provider requiring a software keyring must override - * this method and reject an absent unlock provider before opening any - * workflow resource.

    + *

    + * The default implementation supports providers that need no additional runtime + * capability. A provider requiring a software keyring must override this method + * and reject an absent unlock provider before opening any workflow resource. + *

    * - * @param config structural provider configuration + * @param config structural provider configuration * @param dependencies explicit process-local runtime dependencies * @return allocated workflow * @throws NullPointerException if {@code dependencies} is {@code null} - * @throws RuntimeException if allocation fails + * @throws RuntimeException if allocation fails */ - default SignatureWorkflow allocate(ProviderConfig config, - SignatureWorkflowRuntimeDependencies dependencies) { + default SignatureWorkflow allocate(ProviderConfig config, SignatureWorkflowRuntimeDependencies dependencies) { Objects.requireNonNull(dependencies, "dependencies must not be null"); return allocate(config); } diff --git a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowRuntimeDependencies.java b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowRuntimeDependencies.java index cf21fe1..62c3a7b 100644 --- a/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowRuntimeDependencies.java +++ b/pki/src/main/java/zeroecho/pki/spi/crypto/SignatureWorkflowRuntimeDependencies.java @@ -12,17 +12,20 @@ import zeroecho.core.spi.KeyringUnlockProvider; /** * Immutable runtime dependencies supplied when opening a signature workflow. * - *

    These dependencies are process-local capabilities. They are never encoded - * in {@link zeroecho.pki.spi.ProviderConfig}, persisted, or discovered through + *

    + * These dependencies are process-local capabilities. They are never encoded in + * {@link zeroecho.pki.spi.ProviderConfig}, persisted, or discovered through * {@link java.util.ServiceLoader}. An absent keyring unlock provider is valid - * only for workflow implementations that do not use a software keyring.

    + * only for workflow implementations that do not use a software keyring. + *

    * - *

    Instances are immutable and safe for concurrent use. This object does not - * acquire or retain password material.

    + *

    + * Instances are immutable and safe for concurrent use. This object does not + * acquire or retain password material. + *

    */ public final class SignatureWorkflowRuntimeDependencies { - private static final SignatureWorkflowRuntimeDependencies NONE = - new SignatureWorkflowRuntimeDependencies(null); + private static final SignatureWorkflowRuntimeDependencies NONE = new SignatureWorkflowRuntimeDependencies(null); private final KeyringUnlockProvider keyringUnlockProvider; @@ -40,18 +43,15 @@ public final class SignatureWorkflowRuntimeDependencies { } /** - * Creates dependencies containing an explicit software-keyring unlock - * provider. + * Creates dependencies containing an explicit software-keyring unlock provider. * * @param provider provider returning a fresh destroyable password for each - * keyring open + * keyring open * @return immutable runtime dependencies containing {@code provider} * @throws NullPointerException if {@code provider} is {@code null} */ - public static SignatureWorkflowRuntimeDependencies withKeyringUnlockProvider( - KeyringUnlockProvider provider) { - return new SignatureWorkflowRuntimeDependencies( - Objects.requireNonNull(provider, "provider must not be null")); + public static SignatureWorkflowRuntimeDependencies withKeyringUnlockProvider(KeyringUnlockProvider provider) { + return new SignatureWorkflowRuntimeDependencies(Objects.requireNonNull(provider, "provider must not be null")); } /** diff --git a/pki/src/main/java/zeroecho/pki/spi/framework/CredentialFramework.java b/pki/src/main/java/zeroecho/pki/spi/framework/CredentialFramework.java index 72e7dcf..cdbdecd 100644 --- a/pki/src/main/java/zeroecho/pki/spi/framework/CredentialFramework.java +++ b/pki/src/main/java/zeroecho/pki/spi/framework/CredentialFramework.java @@ -41,8 +41,8 @@ import zeroecho.pki.api.FormatId; *

    * A framework implementation provides request parsing, proof-of-possession * verification and status object generation for a particular {@link FormatId}. - * Credential minting is intentionally not exposed by this framework facade; core - * issuance services own that privileged implementation boundary. + * Credential minting is intentionally not exposed by this framework facade; + * core issuance services own that privileged implementation boundary. *

    */ public interface CredentialFramework { diff --git a/pki/src/main/java/zeroecho/pki/spi/framework/CredentialIssuerBackend.java b/pki/src/main/java/zeroecho/pki/spi/framework/CredentialIssuerBackend.java index 59d0129..9809a53 100644 --- a/pki/src/main/java/zeroecho/pki/spi/framework/CredentialIssuerBackend.java +++ b/pki/src/main/java/zeroecho/pki/spi/framework/CredentialIssuerBackend.java @@ -65,8 +65,8 @@ import zeroecho.pki.impl.core.ValidatedCertificateRequest; *
      *
    • PKI runtime wiring injects the concrete backend into authoritative core * services without publishing it through {@link CredentialFramework}.
    • - *
    • PKI core services prepare opaque proof-gated issuance authorities and call this - * backend to obtain framework-specific credentials.
    • + *
    • PKI core services prepare opaque proof-gated issuance authorities and + * call this backend to obtain framework-specific credentials.
    • *
    • The backend is a privileged post-gate component whose method signatures * cannot accept raw issuance commands.
    • *
    • This backend performs format-specific credential assembly, not CA policy @@ -109,11 +109,11 @@ public interface CredentialIssuerBackend { * *

      * This operation produces a credential for a non-CA subject, typically from a - * cryptographically verified and profile-authorized request carried by the opaque - * {@link ValidatedCertificateRequest}. The returned {@link CredentialBundle} may - * contain the issued leaf credential together with any additional runtime - * bundle material defined by the concrete framework, such as chain elements or - * accompanying metadata. + * cryptographically verified and profile-authorized request carried by the + * opaque {@link ValidatedCertificateRequest}. The returned + * {@link CredentialBundle} may contain the issued leaf credential together with + * any additional runtime bundle material defined by the concrete framework, + * such as chain elements or accompanying metadata. *

      * *

      @@ -127,10 +127,11 @@ public interface CredentialIssuerBackend { * by the trusted core issuance service. *

      * - * @param request gate-produced validated request; must not be {@code null} + * @param request gate-produced validated request; must not be + * {@code null} * @param issuerCertificate trusted encoded issuer certificate - * @param issuerKeyRef trusted issuer signing-key reference - * @param serial trusted positive issuer-controlled serial + * @param issuerKeyRef trusted issuer signing-key reference + * @param serial trusted positive issuer-controlled serial * @return issued credential bundle, never {@code null} * @throws IllegalArgumentException if {@code command} is {@code null} or * structurally invalid for the concrete @@ -158,9 +159,10 @@ public interface CredentialIssuerBackend { * key, subject, profile version, policy, validity, and serial. *

      * - * @param request gate-produced validated CA request; must not be {@code null} + * @param request gate-produced validated CA request; must not be + * {@code null} * @param issuerCertificate trusted encoded issuer certificate - * @param issuerKeyRef trusted issuer signing-key reference + * @param issuerKeyRef trusted issuer signing-key reference * @return issued CA credential, never {@code null} * @throws IllegalArgumentException if {@code command} is {@code null} or * structurally invalid for the concrete @@ -169,6 +171,6 @@ public interface CredentialIssuerBackend { * or other framework-specific issuance * processing fails */ - Credential issueIntermediateCertificate(ValidatedCaCertificateRequest request, - EncodedObject issuerCertificate, KeyRef issuerKeyRef); + Credential issueIntermediateCertificate(ValidatedCaCertificateRequest request, EncodedObject issuerCertificate, + KeyRef issuerKeyRef); } diff --git a/pki/src/main/java/zeroecho/pki/spi/framework/CrlEntry.java b/pki/src/main/java/zeroecho/pki/spi/framework/CrlEntry.java index 3767718..158671e 100644 --- a/pki/src/main/java/zeroecho/pki/spi/framework/CrlEntry.java +++ b/pki/src/main/java/zeroecho/pki/spi/framework/CrlEntry.java @@ -13,14 +13,16 @@ import zeroecho.pki.api.revocation.RevocationReason; /** * Immutable structured input for one certificate-revocation-list entry. * - *

      The serial number retains the full positive X.509 integer domain. The + *

      + * The serial number retains the full positive X.509 integer domain. The * transition time is authoritative runtime state and is not replaced by CRL * generation time. {@link RevocationReason#REMOVE_FROM_CRL} is excluded because - * removal is represented by omission from the current CRL.

      + * removal is represented by omission from the current CRL. + *

      * - * @param serialNumber positive certificate serial number + * @param serialNumber positive certificate serial number * @param transitionTime authoritative revocation or hold transition time - * @param reason explicit CRL reason + * @param reason explicit CRL reason */ public record CrlEntry(BigInteger serialNumber, Instant transitionTime, RevocationReason reason) { diff --git a/pki/src/main/java/zeroecho/pki/spi/framework/StatusObjectGenerator.java b/pki/src/main/java/zeroecho/pki/spi/framework/StatusObjectGenerator.java index eaeac4f..32c2e78 100644 --- a/pki/src/main/java/zeroecho/pki/spi/framework/StatusObjectGenerator.java +++ b/pki/src/main/java/zeroecho/pki/spi/framework/StatusObjectGenerator.java @@ -48,11 +48,11 @@ public interface StatusObjectGenerator { /** * Generates a status object. * - * @param command generation command + * @param command generation command * @param crlEntries structured CRL entries; empty for non-CRL objects * @return generated status object - * @throws IllegalArgumentException if {@code command} or {@code crlEntries} - * is invalid + * @throws IllegalArgumentException if {@code command} or {@code crlEntries} is + * invalid * @throws RuntimeException if generation fails */ StatusObject generate(StatusObjectGenerateCommand command, List crlEntries); diff --git a/pki/src/main/java/zeroecho/pki/spi/store/PkiStore.java b/pki/src/main/java/zeroecho/pki/spi/store/PkiStore.java index 7e4681c..2517531 100644 --- a/pki/src/main/java/zeroecho/pki/spi/store/PkiStore.java +++ b/pki/src/main/java/zeroecho/pki/spi/store/PkiStore.java @@ -160,7 +160,7 @@ public interface PkiStore extends SignWorkflowStore { /** * Atomically validates and appends one legal revocation transition. * - * @param command trusted transition command + * @param command trusted transition command * @param transitionTime authoritative transition time * @return committed journal */ @@ -250,18 +250,18 @@ public interface PkiStore extends SignWorkflowStore { * *

      * Every version stored under one logical profile ID must have the same - * certificate kind. An implementation must reject an import whose kind - * differs from any already imported version for that ID. For an existing - * identical ID and version, exact canonical-reference equality is - * idempotent and returns the committed version; a different reference for - * that same version is a version conflict. This same-version decision takes - * precedence over the cross-version kind check. + * certificate kind. An implementation must reject an import whose kind differs + * from any already imported version for that ID. For an existing identical ID + * and version, exact canonical-reference equality is idempotent and returns the + * committed version; a different reference for that same version is a version + * conflict. This same-version decision takes precedence over the cross-version + * kind check. *

      * * @param version validated immutable version to import * @return the newly committed version or the identical existing version - * @throws RuntimeException if validation, kind consistency, conflict - * handling, or durable persistence fails + * @throws RuntimeException if validation, kind consistency, conflict handling, + * or durable persistence fails */ ImportedCertificateProfileVersion importProfileVersion(ImportedCertificateProfileVersion version); diff --git a/pki/src/main/java/zeroecho/pki/spi/store/SignWorkflowStore.java b/pki/src/main/java/zeroecho/pki/spi/store/SignWorkflowStore.java index 8431342..ab1acf2 100644 --- a/pki/src/main/java/zeroecho/pki/spi/store/SignWorkflowStore.java +++ b/pki/src/main/java/zeroecho/pki/spi/store/SignWorkflowStore.java @@ -48,15 +48,15 @@ import zeroecho.pki.api.audit.Principal; * *

      * Implementations must provide atomic per-operation compare-and-set behavior. - * External signer, callback, and audit code must never run while an implementation - * holds its coordination lock. Different submission identifiers must not share a - * permanent global mutation lock. + * External signer, callback, and audit code must never run while an + * implementation holds its coordination lock. Different submission identifiers + * must not share a permanent global mutation lock. *

      * *

      * Retained records are the source of truth through {@link #signingHorizon()}. - * Purging a record does not make its stable identifier reusable because creation - * validates the identifier timestamp against the same horizon. + * Purging a record does not make its stable identifier reusable because + * creation validates the identifier timestamp against the same horizon. *

      */ public interface SignWorkflowStore { @@ -72,14 +72,7 @@ public interface SignWorkflowStore { *

      */ enum State { - INTENT(10), - DISPATCHED(20), - SUCCEEDED(40), - FAILED(50), - CANCELLED(60), - RETIRED(70), - EXPIRED(80), - CANCELLING(30); + INTENT(10), DISPATCHED(20), SUCCEEDED(40), FAILED(50), CANCELLED(60), RETIRED(70), EXPIRED(80), CANCELLING(30); private final int persistentCode; @@ -121,19 +114,19 @@ public interface SignWorkflowStore { /** * Complete durable signing state. * - * @param submissionId stable submission identifier - * @param namespace provider/store namespace - * @param fingerprint versioned semantic request fingerprint - * @param owner request owner - * @param createdAt store-validated creation time - * @param deadline request deadline - * @param request versioned request continuation - * @param state lifecycle state - * @param revision monotonic record revision - * @param fence monotonic claim fencing token - * @param leaseUntil optional active claim lease - * @param detailCode optional non-sensitive detail code - * @param result optional terminal signature result + * @param submissionId stable submission identifier + * @param namespace provider/store namespace + * @param fingerprint versioned semantic request fingerprint + * @param owner request owner + * @param createdAt store-validated creation time + * @param deadline request deadline + * @param request versioned request continuation + * @param state lifecycle state + * @param revision monotonic record revision + * @param fence monotonic claim fencing token + * @param leaseUntil optional active claim lease + * @param detailCode optional non-sensitive detail code + * @param result optional terminal signature result * @param providerUpdatedAt optional durable provider terminal-transition time; * the provider clock may be independent of the store * clock, but successful completion must be strictly @@ -227,9 +220,9 @@ public interface SignWorkflowStore { /** * Atomically claims an intent and increments its revision and fencing token. * - * @param submissionId stable submission identifier + * @param submissionId stable submission identifier * @param expectedRevision caller-observed revision - * @param lease positive claim duration + * @param lease positive claim duration * @return claimed record, or empty when compare-and-set loses */ Optional tryClaimSign(PkiId submissionId, long expectedRevision, Duration lease); @@ -237,10 +230,10 @@ public interface SignWorkflowStore { /** * Atomically renews an owned lease. * - * @param submissionId stable submission identifier + * @param submissionId stable submission identifier * @param expectedRevision caller-observed revision - * @param fence current fencing token - * @param lease positive renewal duration + * @param fence current fencing token + * @param lease positive renewal duration * @return renewed record, or empty when compare-and-set loses */ Optional renewSignClaim(PkiId submissionId, long expectedRevision, long fence, Duration lease); @@ -256,12 +249,12 @@ public interface SignWorkflowStore { * without treating provider acceptance as terminal. *

      * - * @param submissionId stable submission identifier - * @param expectedRevision caller-observed revision - * @param fence current fencing token - * @param target allowed target state - * @param detailCode optional non-sensitive detail - * @param result signature result, required only for success + * @param submissionId stable submission identifier + * @param expectedRevision caller-observed revision + * @param fence current fencing token + * @param target allowed target state + * @param detailCode optional non-sensitive detail + * @param result signature result, required only for success * @param providerUpdatedAt durable provider terminal-transition time from the * provider clock domain; required for success and * required to be strictly before the request deadline @@ -271,13 +264,13 @@ public interface SignWorkflowStore { Optional detailCode, Optional result, Optional providerUpdatedAt); /** - * Atomically retires a terminal current record while retaining its identity - * and any successful result. Non-terminal records, including + * Atomically retires a terminal current record while retaining its identity and + * any successful result. Non-terminal records, including * {@link State#CANCELLING}, are not retirable. * - * @param submissionId stable submission identifier + * @param submissionId stable submission identifier * @param expectedRevision caller-observed revision - * @param fence current fencing token + * @param fence current fencing token * @return retired record, or empty when compare-and-set loses */ Optional retireSign(PkiId submissionId, long expectedRevision, long fence); diff --git a/pki/src/test/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalogTest.java b/pki/src/test/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalogTest.java index 3b65f64..816cf4a 100644 --- a/pki/src/test/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalogTest.java +++ b/pki/src/test/java/zeroecho/pki/api/profile/BuiltInCertificateProfileCatalogTest.java @@ -44,33 +44,26 @@ final class BuiltInCertificateProfileCatalogTest { private static final String EMAIL = ROOT + "email-signing.json"; private static final String ROOT_CA = ROOT + "root-ca.json"; private static final String INTERMEDIATE_CA = ROOT + "intermediate-ca.json"; - private static final List PROFILE_RESOURCES = - List.of(SERVER, VPN_SERVER, VPN_CLIENT, EMAIL, ROOT_CA, INTERMEDIATE_CA); - private static final List EXPECTED_ORDER = - List.of("server-tls", "vpn-server", "vpn-client", "email-signing", - "root-ca", "intermediate-ca"); - private static final Set EXPECTED_ALGORITHMS = - Set.of("RSA", "ECDSA", "Ed25519"); - private static final ExtendedKeyUsageId SERVER_AUTH = - new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1"); - private static final ExtendedKeyUsageId CLIENT_AUTH = - new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.2"); - private static final ExtendedKeyUsageId EMAIL_PROTECTION = - new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.4"); + private static final List PROFILE_RESOURCES = List.of(SERVER, VPN_SERVER, VPN_CLIENT, EMAIL, ROOT_CA, + INTERMEDIATE_CA); + private static final List EXPECTED_ORDER = List.of("server-tls", "vpn-server", "vpn-client", + "email-signing", "root-ca", "intermediate-ca"); + private static final Set EXPECTED_ALGORITHMS = Set.of("RSA", "ECDSA", "Ed25519"); + private static final ExtendedKeyUsageId SERVER_AUTH = new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1"); + private static final ExtendedKeyUsageId CLIENT_AUTH = new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.2"); + private static final ExtendedKeyUsageId EMAIL_PROTECTION = new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.4"); private static boolean sentinelInitialized; @Test - void productionCatalogueLoadsCanonicalImmutableTemplatesInDeterministicOrder() - throws NoSuchAlgorithmException { - List first = - BuiltInCertificateProfileCatalog.load(getClass().getClassLoader()); - List second = - BuiltInCertificateProfileCatalog.load(getClass().getClassLoader()); + void productionCatalogueLoadsCanonicalImmutableTemplatesInDeterministicOrder() throws NoSuchAlgorithmException { + List first = BuiltInCertificateProfileCatalog + .load(getClass().getClassLoader()); + List second = BuiltInCertificateProfileCatalog + .load(getClass().getClassLoader()); assertEquals(6, first.size()); - assertEquals(EXPECTED_ORDER, - first.stream().map(template -> template.definition().profileId()).toList()); + assertEquals(EXPECTED_ORDER, first.stream().map(template -> template.definition().profileId()).toList()); assertEquals(first, second); assertThrows(UnsupportedOperationException.class, () -> first.add(first.get(0))); for (BuiltInCertificateProfileTemplate template : first) { @@ -79,13 +72,11 @@ final class BuiltInCertificateProfileCatalogTest { byte[] hash = template.canonicalSha256(); assertEquals(1, definition.profileVersion()); assertEquals("x509", definition.formatId().value()); - assertArrayEquals(canonical, - CertificateProfileDocumentCodec.writeCanonical(definition)); + assertArrayEquals(canonical, CertificateProfileDocumentCodec.writeCanonical(definition)); assertEquals(definition, CertificateProfileDocumentCodec.parse(canonical)); assertArrayEquals(MessageDigest.getInstance("SHA-256").digest(canonical), hash); assertEquals(32, hash.length); - assertFalse(template.toString().contains(new String(canonical, - StandardCharsets.UTF_8))); + assertFalse(template.toString().contains(new String(canonical, StandardCharsets.UTF_8))); byte originalJson = canonical[0]; byte originalHash = hash[0]; @@ -113,9 +104,8 @@ final class BuiltInCertificateProfileCatalogTest { assertFalse(san.requireEmailIdentity()); assertFalse(san.allowDnsWildcard()); assertFalse(san.criticalWithNonemptySubject()); - assertEquals(Set.of(SubjectAlternativeNameType.DNS_NAME, - SubjectAlternativeNameType.IP_ADDRESS), san.rules().stream() - .map(SubjectAlternativeNameRule::type).collect(java.util.stream.Collectors.toSet())); + assertEquals(Set.of(SubjectAlternativeNameType.DNS_NAME, SubjectAlternativeNameType.IP_ADDRESS), san.rules() + .stream().map(SubjectAlternativeNameRule::type).collect(java.util.stream.Collectors.toSet())); SubjectAlternativeNameRule ip = rule(san, SubjectAlternativeNameType.IP_ADDRESS); assertTrue(ip.allowIpv4()); assertTrue(ip.allowIpv6()); @@ -137,9 +127,8 @@ final class BuiltInCertificateProfileCatalogTest { assertFalse(san.requireEmailIdentity()); assertFalse(san.allowDnsWildcard()); assertEquals(Set.of("spiffe"), san.allowedUriSchemes()); - assertEquals(Set.of(SubjectAlternativeNameType.URI, - SubjectAlternativeNameType.RFC822_NAME), san.rules().stream() - .map(SubjectAlternativeNameRule::type).collect(java.util.stream.Collectors.toSet())); + assertEquals(Set.of(SubjectAlternativeNameType.URI, SubjectAlternativeNameType.RFC822_NAME), san.rules() + .stream().map(SubjectAlternativeNameRule::type).collect(java.util.stream.Collectors.toSet())); assertLeafPolicy(leaf, Set.of(CLIENT_AUTH)); } @@ -166,37 +155,27 @@ final class BuiltInCertificateProfileCatalogTest { @Test void caTemplatesHaveExactKindsSubjectAndCertificatePolicies() { Map profiles = productionDefinitions(); - assertCaProfile(profiles.get("root-ca"), CertificateProfileKind.ROOT_CA, - Duration.ofHours(87_600), 1); - assertCaProfile(profiles.get("intermediate-ca"), - CertificateProfileKind.INTERMEDIATE_CA, Duration.ofHours(43_800), 0); + assertCaProfile(profiles.get("root-ca"), CertificateProfileKind.ROOT_CA, Duration.ofHours(87_600), 1); + assertCaProfile(profiles.get("intermediate-ca"), CertificateProfileKind.INTERMEDIATE_CA, + Duration.ofHours(43_800), 0); } @Test void manifestRejectsUnknownDuplicateMissingInvalidPathsLimitsAndTrailingTokens() { - List invalidManifests = List.of( - "{\"schemaVersion\":1,\"unknown\":true,\"profiles\":[]}", - "{\"schemaVersion\":1,\"schemaVersion\":1,\"profiles\":[]}", - "{\"schemaVersion\":1}", + List invalidManifests = List.of("{\"schemaVersion\":1,\"unknown\":true,\"profiles\":[]}", + "{\"schemaVersion\":1,\"schemaVersion\":1,\"profiles\":[]}", "{\"schemaVersion\":1}", "{\"schemaVersion\":2,\"profiles\":[{\"resource\":\"" + SERVER + "\"}]}", - "{\"schemaVersion\":1,\"profiles\":[]}", - manifest(List.of(SERVER, SERVER)), - manifest(List.of("/" + SERVER)), - manifest(List.of(ROOT + "../server-tls.json")), - manifest(List.of(ROOT + "nested\\\\server-tls.json")), - manifest(List.of("https:" + SERVER)), + "{\"schemaVersion\":1,\"profiles\":[]}", manifest(List.of(SERVER, SERVER)), + manifest(List.of("/" + SERVER)), manifest(List.of(ROOT + "../server-tls.json")), + manifest(List.of(ROOT + "nested\\\\server-tls.json")), manifest(List.of("https:" + SERVER)), manifest(List.of("outside/server-tls.json")), - "{\"schemaVersion\":1,\"profiles\":[{\"resource\":\"" + SERVER - + "\",\"extra\":true}]}", - "{\"schemaVersion\":1,\"profiles\":[{}]}", - manifest(PROFILE_RESOURCES) + "{}"); + "{\"schemaVersion\":1,\"profiles\":[{\"resource\":\"" + SERVER + "\",\"extra\":true}]}", + "{\"schemaVersion\":1,\"profiles\":[{}]}", manifest(PROFILE_RESOURCES) + "{}"); for (String manifest : invalidManifests) { Map> resources = baseResources(); - resources.put(BuiltInCertificateProfileCatalog.MANIFEST_RESOURCE, - List.of(bytes(manifest))); + resources.put(BuiltInCertificateProfileCatalog.MANIFEST_RESOURCE, List.of(bytes(manifest))); assertThrows(PkiException.class, - () -> BuiltInCertificateProfileCatalog.load( - new MemoryResourceClassLoader(resources))); + () -> BuiltInCertificateProfileCatalog.load(new MemoryResourceClassLoader(resources))); } List tooMany = new ArrayList<>(); @@ -204,8 +183,7 @@ final class BuiltInCertificateProfileCatalogTest { tooMany.add(ROOT + "profile-" + index + ".json"); } Map> resources = baseResources(); - resources.put(BuiltInCertificateProfileCatalog.MANIFEST_RESOURCE, - List.of(bytes(manifest(tooMany)))); + resources.put(BuiltInCertificateProfileCatalog.MANIFEST_RESOURCE, List.of(bytes(manifest(tooMany)))); assertCode(resources, "MANIFEST_LIMIT_EXCEEDED"); } @@ -224,8 +202,7 @@ final class BuiltInCertificateProfileCatalogTest { assertCode(empty, "RESOURCE_EMPTY"); Map> oversized = baseResources(); - oversized.put(SERVER, List.of(new byte[ - CertificateProfileDocumentCodec.MAXIMUM_DOCUMENT_BYTES + 1])); + oversized.put(SERVER, List.of(new byte[CertificateProfileDocumentCodec.MAXIMUM_DOCUMENT_BYTES + 1])); assertCode(oversized, "RESOURCE_TOO_LARGE"); Map> noncanonical = baseResources(); @@ -239,13 +216,10 @@ final class BuiltInCertificateProfileCatalogTest { @Test void profileDocumentsFailClosedForInvalidSchemaFieldsEncodingAndClassMetadata() { List invalidDocuments = List.of( - replace(mainResource(SERVER), "\"schemaVersion\":2", - "\"schemaVersion\":1"), + replace(mainResource(SERVER), "\"schemaVersion\":2", "\"schemaVersion\":1"), + replace(mainResource(SERVER), "\"schemaVersion\":2,", "\"schemaVersion\":2,\"active\":true,"), replace(mainResource(SERVER), "\"schemaVersion\":2,", - "\"schemaVersion\":2,\"active\":true,"), - replace(mainResource(SERVER), "\"schemaVersion\":2,", - "\"schemaVersion\":2,\"@class\":\"" - + InitializationSentinel.CLASS_NAME + "\","), + "\"schemaVersion\":2,\"@class\":\"" + InitializationSentinel.CLASS_NAME + "\","), malformedUtf8(mainResource(SERVER))); for (byte[] invalid : invalidDocuments) { Map> resources = baseResources(); @@ -261,19 +235,15 @@ final class BuiltInCertificateProfileCatalogTest { duplicateHash.put(VPN_SERVER, List.of(mainResource(SERVER))); assertCode(duplicateHash, "DUPLICATE_PROFILE_HASH"); - CertificateProfileDefinition server = - CertificateProfileDocumentCodec.parse(mainResource(SERVER)); - CertificateProfileDefinition changed = new CertificateProfileDefinition( - server.certificateType(), server.profileId(), server.profileVersion(), - server.formatId(), "Changed display", server.maximumValidity(), - server.subjectPolicy(), server.certificatePolicy()); + CertificateProfileDefinition server = CertificateProfileDocumentCodec.parse(mainResource(SERVER)); + CertificateProfileDefinition changed = new CertificateProfileDefinition(server.certificateType(), + server.profileId(), server.profileVersion(), server.formatId(), "Changed display", + server.maximumValidity(), server.subjectPolicy(), server.certificatePolicy()); Map> duplicateIdentity = baseResources(); - duplicateIdentity.put(VPN_SERVER, List.of( - CertificateProfileDocumentCodec.writeCanonical(changed))); + duplicateIdentity.put(VPN_SERVER, List.of(CertificateProfileDocumentCodec.writeCanonical(changed))); assertCode(duplicateIdentity, "DUPLICATE_PROFILE_IDENTITY"); - byte[] unexpected = replace(mainResource(SERVER), "\"server-tls\"", - "\"unexpected\""); + byte[] unexpected = replace(mainResource(SERVER), "\"server-tls\"", "\"unexpected\""); Map> wrongSet = baseResources(); wrongSet.put(SERVER, List.of(unexpected)); assertCode(wrongSet, "BUILT_IN_PROFILE_SET_INVALID"); @@ -290,8 +260,7 @@ final class BuiltInCertificateProfileCatalogTest { assertTrue(commonName.fixedValue().isEmpty()); } - private static void assertLeafPolicy(LeafCertificatePolicy leaf, - Set expectedExtendedUsages) { + private static void assertLeafPolicy(LeafCertificatePolicy leaf, Set expectedExtendedUsages) { assertEquals(Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), leaf.keyUsages()); assertTrue(leaf.keyUsageCritical()); assertEquals(expectedExtendedUsages, leaf.extendedKeyUsages()); @@ -300,8 +269,8 @@ final class BuiltInCertificateProfileCatalogTest { assertEquals(EXPECTED_ALGORITHMS, leaf.allowedSubjectKeyAlgorithmIds()); } - private static void assertCaProfile(CertificateProfileDefinition definition, - CertificateProfileKind kind, Duration maximumValidity, int pathLength) { + private static void assertCaProfile(CertificateProfileDefinition definition, CertificateProfileKind kind, + Duration maximumValidity, int pathLength) { assertEquals(kind, definition.certificateType()); assertEquals(maximumValidity, definition.maximumValidity()); assertFalse(definition.subjectPolicy().allowEmpty()); @@ -321,15 +290,13 @@ final class BuiltInCertificateProfileCatalogTest { private static SubjectAlternativeNameRule rule(SubjectAlternativeNamePolicy policy, SubjectAlternativeNameType type) { - return policy.rules().stream().filter(candidate -> candidate.type() == type) - .findFirst().orElseThrow(); + return policy.rules().stream().filter(candidate -> candidate.type() == type).findFirst().orElseThrow(); } private static Map productionDefinitions() { Map result = new LinkedHashMap<>(); - for (BuiltInCertificateProfileTemplate template : - BuiltInCertificateProfileCatalog.load( - BuiltInCertificateProfileCatalogTest.class.getClassLoader())) { + for (BuiltInCertificateProfileTemplate template : BuiltInCertificateProfileCatalog + .load(BuiltInCertificateProfileCatalogTest.class.getClassLoader())) { result.put(template.definition().profileId(), template.definition()); } return Map.copyOf(result); @@ -390,8 +357,7 @@ final class BuiltInCertificateProfileCatalogTest { private static void assertCode(Map> resources, String code) { PkiException exception = assertThrows(PkiException.class, - () -> BuiltInCertificateProfileCatalog.load( - new MemoryResourceClassLoader(resources))); + () -> BuiltInCertificateProfileCatalog.load(new MemoryResourceClassLoader(resources))); assertTrue(exception.getMessage().contains("code=" + code)); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); @@ -404,8 +370,7 @@ final class BuiltInCertificateProfileCatalogTest { private MemoryResourceClassLoader(Map> resources) { super(null); Map> copied = new LinkedHashMap<>(); - resources.forEach((name, values) -> copied.put(name, - values.stream().map(byte[]::clone).toList())); + resources.forEach((name, values) -> copied.put(name, values.stream().map(byte[]::clone).toList())); this.resources = Map.copyOf(copied); } @@ -420,30 +385,28 @@ final class BuiltInCertificateProfileCatalogTest { } private static URL memoryUrl(String name, int index, byte[] value) throws IOException { - return URL.of(URI.create("memory:/" + index + "/" + name), - new URLStreamHandler() { + return URL.of(URI.create("memory:/" + index + "/" + name), new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) { + return new URLConnection(url) { @Override - protected URLConnection openConnection(URL url) { - return new URLConnection(url) { - @Override - public void connect() { - connected = true; - } - - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(value); - } - }; + public void connect() { + connected = true; } - }); + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(value); + } + }; + } + }); } } private static final class InitializationSentinel { - private static final String CLASS_NAME = - "zeroecho.pki.api.profile.BuiltInCertificateProfileCatalogTest" - + "$InitializationSentinel"; + private static final String CLASS_NAME = "zeroecho.pki.api.profile.BuiltInCertificateProfileCatalogTest" + + "$InitializationSentinel"; static { sentinelInitialized = true; diff --git a/pki/src/test/java/zeroecho/pki/api/profile/BuiltInProfileImportIntegrityTest.java b/pki/src/test/java/zeroecho/pki/api/profile/BuiltInProfileImportIntegrityTest.java index 84046b4..bd83bbe 100644 --- a/pki/src/test/java/zeroecho/pki/api/profile/BuiltInProfileImportIntegrityTest.java +++ b/pki/src/test/java/zeroecho/pki/api/profile/BuiltInProfileImportIntegrityTest.java @@ -57,10 +57,8 @@ final class BuiltInProfileImportIntegrityTest { root.addHandler(logs); try { for (BuiltInCertificateProfileTemplate template : invalid) { - PkiException failure = assertThrows(PkiException.class, - () -> service.importBuiltIn(template)); - assertEquals("Profile lifecycle operation failed: code=BUILT_IN_PROFILE_INVALID", - failure.getMessage()); + PkiException failure = assertThrows(PkiException.class, () -> service.importBuiltIn(template)); + assertEquals("Profile lifecycle operation failed: code=BUILT_IN_PROFILE_INVALID", failure.getMessage()); assertEquals(null, failure.getCause()); assertEquals(0, failure.getSuppressed().length); assertFalse(failure.getMessage().contains(SENTINEL)); @@ -75,8 +73,8 @@ final class BuiltInProfileImportIntegrityTest { } private static PkiStore countingStore(AtomicInteger calls) { - return (PkiStore) Proxy.newProxyInstance(PkiStore.class.getClassLoader(), - new Class[] { PkiStore.class }, (proxy, method, arguments) -> { + return (PkiStore) Proxy.newProxyInstance(PkiStore.class.getClassLoader(), new Class[] { PkiStore.class }, + (proxy, method, arguments) -> { if (method.getDeclaringClass() == Object.class) { return switch (method.getName()) { case "hashCode" -> System.identityHashCode(proxy); @@ -91,10 +89,8 @@ final class BuiltInProfileImportIntegrityTest { } private static BuiltInCertificateProfileTemplate builtIn(String profileId) { - return BuiltInCertificateProfileCatalog.load( - BuiltInProfileImportIntegrityTest.class.getClassLoader()).stream() - .filter(template -> profileId.equals(template.definition().profileId())) - .findFirst().orElseThrow(); + return BuiltInCertificateProfileCatalog.load(BuiltInProfileImportIntegrityTest.class.getClassLoader()).stream() + .filter(template -> profileId.equals(template.definition().profileId())).findFirst().orElseThrow(); } private static final class CollectingHandler extends Handler { diff --git a/pki/src/test/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodecTest.java b/pki/src/test/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodecTest.java index e4b3b90..0533d39 100644 --- a/pki/src/test/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodecTest.java +++ b/pki/src/test/java/zeroecho/pki/api/profile/CertificateProfileDocumentCodecTest.java @@ -122,14 +122,12 @@ final class CertificateProfileDocumentCodecTest { assertEquals("ZeroEcho", definition.subjectPolicy().rules().get(1).fixedValue().orElseThrow()); assertFalse(definition.subjectPolicy().allowEmpty()); assertTrue(leaf.subjectAlternativeNamePolicy().allowDnsWildcard()); - assertEquals(Set.of("https", "spiffe"), - leaf.subjectAlternativeNamePolicy().allowedUriSchemes()); - assertEquals(Set.of(LeafKeyUsage.DIGITAL_SIGNATURE, LeafKeyUsage.KEY_AGREEMENT, - LeafKeyUsage.ENCIPHER_ONLY), leaf.keyUsages()); - assertEquals(Set.of("RSA", "ECDSA", "Ed25519", "Ed448"), - leaf.allowedSubjectKeyAlgorithmIds()); - assertEquals(Set.of(new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1"), - new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.2")), leaf.extendedKeyUsages()); + assertEquals(Set.of("https", "spiffe"), leaf.subjectAlternativeNamePolicy().allowedUriSchemes()); + assertEquals(Set.of(LeafKeyUsage.DIGITAL_SIGNATURE, LeafKeyUsage.KEY_AGREEMENT, LeafKeyUsage.ENCIPHER_ONLY), + leaf.keyUsages()); + assertEquals(Set.of("RSA", "ECDSA", "Ed25519", "Ed448"), leaf.allowedSubjectKeyAlgorithmIds()); + assertEquals(Set.of(new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1"), new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.2")), + leaf.extendedKeyUsages()); } @Test @@ -143,8 +141,8 @@ final class CertificateProfileDocumentCodecTest { definition("uri", requesterCn(), uriSan(), eku(), Set.of("Ed448")), definition("email", requesterCn(), emailSan(), eku(), Set.of("RSA")), definition("mixed", requesterCn(), mixedSan(), eku(), Set.of("RSA", "ECDSA")), - definition("empty-subject", new SubjectPolicy(true, List.of()), dnsSan(false, true), - eku(), Set.of("RSA")), + definition("empty-subject", new SubjectPolicy(true, List.of()), dnsSan(false, true), eku(), + Set.of("RSA")), definition("fixed-rdn", fixedOrganization(), noSan(), eku(), Set.of("RSA")), definition("multiple-algorithms", requesterCn(), noSan(), Set.of(), Set.of("RSA", "ECDSA", "Ed25519", "Ed448"))); @@ -156,33 +154,27 @@ final class CertificateProfileDocumentCodecTest { assertEquals(expected, actual, expected.profileId()); assertFalse(json.contains("\"active\""), expected.profileId()); - assertArrayEquals(encoded, CertificateProfileDocumentCodec.writeCanonical(actual), - expected.profileId()); + assertArrayEquals(encoded, CertificateProfileDocumentCodec.writeCanonical(actual), expected.profileId()); } - CertificateProfileDefinition immutable = CertificateProfileDocumentCodec.parse( - CertificateProfileDocumentCodec.writeCanonical(definitions.get(7))); - assertThrows(UnsupportedOperationException.class, - () -> immutable.subjectPolicy().rules().add( - new SubjectRdnRule(SubjectRdnType.PSEUDONYM, 0, 0, 32, - Optional.empty(), true))); + CertificateProfileDefinition immutable = CertificateProfileDocumentCodec + .parse(CertificateProfileDocumentCodec.writeCanonical(definitions.get(7))); + assertThrows(UnsupportedOperationException.class, () -> immutable.subjectPolicy().rules() + .add(new SubjectRdnRule(SubjectRdnType.PSEUDONYM, 0, 0, 32, Optional.empty(), true))); assertThrows(UnsupportedOperationException.class, () -> immutable.leafPolicy().keyUsages().add(LeafKeyUsage.CONTENT_COMMITMENT)); assertThrows(UnsupportedOperationException.class, - () -> immutable.leafPolicy().subjectAlternativeNamePolicy() - .allowedUriSchemes().add("ssh")); + () -> immutable.leafPolicy().subjectAlternativeNamePolicy().allowedUriSchemes().add("ssh")); } @Test void roundTripsRootAndIntermediateCaPoliciesAndRejectsWrongKindShapes() { - CertificateProfileDefinition root = caDefinition("root-test", - CertificateProfileKind.ROOT_CA, 1); + CertificateProfileDefinition root = caDefinition("root-test", CertificateProfileKind.ROOT_CA, 1); CertificateProfileDefinition intermediate = caDefinition("intermediate-test", CertificateProfileKind.INTERMEDIATE_CA, 0); - CertificateProfileDefinition delegatedIntermediate = caDefinition( - "delegated-intermediate-test", CertificateProfileKind.INTERMEDIATE_CA, 1); - for (CertificateProfileDefinition expected : - List.of(root, intermediate, delegatedIntermediate)) { + CertificateProfileDefinition delegatedIntermediate = caDefinition("delegated-intermediate-test", + CertificateProfileKind.INTERMEDIATE_CA, 1); + for (CertificateProfileDefinition expected : List.of(root, intermediate, delegatedIntermediate)) { byte[] canonical = CertificateProfileDocumentCodec.writeCanonical(expected); CertificateProfileDefinition actual = CertificateProfileDocumentCodec.parse(canonical); assertEquals(expected, actual); @@ -190,43 +182,35 @@ final class CertificateProfileDocumentCodecTest { } String rootJson = canonical(root); - assertCode(rootJson.replace("\"certificateType\":\"ROOT_CA\"", - "\"certificateType\":\"END_ENTITY\""), + assertCode(rootJson.replace("\"certificateType\":\"ROOT_CA\"", "\"certificateType\":\"END_ENTITY\""), "FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE"); assertCode(rootJson.replace("\"caCertificate\":", "\"leafCertificate\":"), "FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE"); assertCode(rootJson.replace("\"caCertificate\":", "\"subjectAlternativeNames\":"), "FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE"); - assertCode(rootJson.replace(",\"caCertificate\":{", ",\"leafCertificate\":{}" - + ",\"caCertificate\":{"), "FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE"); - assertCode(rootJson.replace("\"allowEmpty\":false", "\"allowEmpty\":true"), + assertCode(rootJson.replace(",\"caCertificate\":{", ",\"leafCertificate\":{}" + ",\"caCertificate\":{"), + "FIELD_FORBIDDEN_FOR_CERTIFICATE_TYPE"); + assertCode(rootJson.replace("\"allowEmpty\":false", "\"allowEmpty\":true"), "SEMANTIC_INVALID"); + assertCode(rootJson.replace("\"pathLengthConstraint\":1", "\"pathLengthConstraint\":-1"), "SEMANTIC_INVALID"); + assertCode(rootJson.replace("\"pathLengthConstraint\":1", "\"pathLengthConstraint\":33"), "SEMANTIC_INVALID"); + assertCode(rootJson.replace("\"basicConstraintsCritical\":true", "\"basicConstraintsCritical\":false"), + "SEMANTIC_INVALID"); + assertCode(rootJson.replace("\"keyUsageCritical\":true", "\"keyUsageCritical\":false"), "SEMANTIC_INVALID"); + assertCode(rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", "[\"KEY_CERT_SIGN\"]"), "SEMANTIC_INVALID"); + assertCode(rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", "[\"CRL_SIGN\"]"), "SEMANTIC_INVALID"); + assertCode( + rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", "[\"CRL_SIGN\",\"CRL_SIGN\",\"KEY_CERT_SIGN\"]"), + "SEMANTIC_INVALID"); + assertCode(rootJson.replace("\"allowedSubjectKeyAlgorithms\":[\"RSA\"]", "\"allowedSubjectKeyAlgorithms\":[]"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("\"pathLengthConstraint\":1", - "\"pathLengthConstraint\":-1"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("\"pathLengthConstraint\":1", - "\"pathLengthConstraint\":33"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("\"basicConstraintsCritical\":true", - "\"basicConstraintsCritical\":false"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("\"keyUsageCritical\":true", - "\"keyUsageCritical\":false"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", - "[\"KEY_CERT_SIGN\"]"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", - "[\"CRL_SIGN\"]"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("[\"CRL_SIGN\",\"KEY_CERT_SIGN\"]", - "[\"CRL_SIGN\",\"CRL_SIGN\",\"KEY_CERT_SIGN\"]"), "SEMANTIC_INVALID"); - assertCode(rootJson.replace("\"allowedSubjectKeyAlgorithms\":[\"RSA\"]", - "\"allowedSubjectKeyAlgorithms\":[]"), "SEMANTIC_INVALID"); assertCode(rootJson.replace("\"allowedSubjectKeyAlgorithms\":", - "\"extendedKeyUsage\":[],\"allowedSubjectKeyAlgorithms\":"), - "UNKNOWN_FIELD"); + "\"extendedKeyUsage\":[],\"allowedSubjectKeyAlgorithms\":"), "UNKNOWN_FIELD"); } @Test void canonicalOutputHasFixedOrderSortedSetsAndIsIdempotent() { byte[] first = CertificateProfileDocumentCodec.writeCanonical(parse(VALID_DOCUMENT)); - byte[] second = CertificateProfileDocumentCodec.writeCanonical( - CertificateProfileDocumentCodec.parse(first)); + byte[] second = CertificateProfileDocumentCodec.writeCanonical(CertificateProfileDocumentCodec.parse(first)); String json = new String(first, StandardCharsets.UTF_8); assertArrayEquals(first, second); @@ -243,25 +227,23 @@ final class CertificateProfileDocumentCodecTest { assertTrue(json.contains("\"allowedSchemes\":[\"https\",\"spiffe\"]")); assertTrue(json.contains("\"source\":\"REQUESTER\"")); assertTrue(json.contains("\"source\":\"PROFILE_FIXED\"")); - assertTrue(json.contains("\"keyUsage\":[\"DIGITAL_SIGNATURE\",\"ENCIPHER_ONLY\"," - + "\"KEY_AGREEMENT\"]")); + assertTrue(json.contains("\"keyUsage\":[\"DIGITAL_SIGNATURE\",\"ENCIPHER_ONLY\"," + "\"KEY_AGREEMENT\"]")); assertTrue(json.contains("\"allowedKeyAlgorithms\":[\"ECDSA\",\"Ed25519\",\"Ed448\",\"RSA\"]")); } @Test void writerRejectsProfileStringsThatItsParserWouldReject() { - CertificateProfileDefinition valid = definition("valid", requesterCn(), noSan(), - Set.of(), Set.of("RSA")); + CertificateProfileDefinition valid = definition("valid", requesterCn(), noSan(), Set.of(), Set.of("RSA")); List invalid = List.of( new CertificateProfileDefinition(valid.certificateType(), " padded", valid.profileVersion(), - valid.formatId(), valid.displayName(), valid.maximumValidity(), - valid.subjectPolicy(), valid.certificatePolicy()), - new CertificateProfileDefinition(valid.certificateType(), valid.profileId(), - valid.profileVersion(), new FormatId("x509 "), valid.displayName(), - valid.maximumValidity(), valid.subjectPolicy(), valid.certificatePolicy()), - new CertificateProfileDefinition(valid.certificateType(), valid.profileId(), - valid.profileVersion(), valid.formatId(), " padded ", valid.maximumValidity(), - valid.subjectPolicy(), valid.certificatePolicy())); + valid.formatId(), valid.displayName(), valid.maximumValidity(), valid.subjectPolicy(), + valid.certificatePolicy()), + new CertificateProfileDefinition(valid.certificateType(), valid.profileId(), valid.profileVersion(), + new FormatId("x509 "), valid.displayName(), valid.maximumValidity(), valid.subjectPolicy(), + valid.certificatePolicy()), + new CertificateProfileDefinition(valid.certificateType(), valid.profileId(), valid.profileVersion(), + valid.formatId(), " padded ", valid.maximumValidity(), valid.subjectPolicy(), + valid.certificatePolicy())); for (CertificateProfileDefinition definition : invalid) { assertWriteCode(definition, "CANONICALIZATION_FAILED"); @@ -300,34 +282,26 @@ final class CertificateProfileDocumentCodecTest { @Test void rejectsDuplicateUnknownMissingNullWrongAndNonintegralFields() { - assertCode(VALID_DOCUMENT.replace("\"schemaVersion\": 2,", - "\"schemaVersion\": 2,\"schemaVersion\": 2,"), "DUPLICATE_FIELD"); + assertCode(VALID_DOCUMENT.replace("\"schemaVersion\": 2,", "\"schemaVersion\": 2,\"schemaVersion\": 2,"), + "DUPLICATE_FIELD"); for (String document : List.of( - VALID_DOCUMENT.replace("\"schemaVersion\": 2,", - "\"secret-field\": true,\"schemaVersion\": 2,"), - VALID_DOCUMENT.replace("\"allowEmpty\": false,", - "\"unknown\": true,\"allowEmpty\": false,"), - VALID_DOCUMENT.replace("\"oid\": \"2.5.4.3\",", - "\"unknown\": true,\"oid\": \"2.5.4.3\","), - VALID_DOCUMENT.replace("\"minimumTotal\": 1,", - "\"unknown\": true,\"minimumTotal\": 1,"), - VALID_DOCUMENT.replace("\"type\": \"URI\",", - "\"unknown\": true,\"type\": \"URI\","), + VALID_DOCUMENT.replace("\"schemaVersion\": 2,", "\"secret-field\": true,\"schemaVersion\": 2,"), + VALID_DOCUMENT.replace("\"allowEmpty\": false,", "\"unknown\": true,\"allowEmpty\": false,"), + VALID_DOCUMENT.replace("\"oid\": \"2.5.4.3\",", "\"unknown\": true,\"oid\": \"2.5.4.3\","), + VALID_DOCUMENT.replace("\"minimumTotal\": 1,", "\"unknown\": true,\"minimumTotal\": 1,"), + VALID_DOCUMENT.replace("\"type\": \"URI\",", "\"unknown\": true,\"type\": \"URI\","), VALID_DOCUMENT.replace("\"basicConstraintsCritical\": true,", "\"unknown\": true,\"basicConstraintsCritical\": true,"))) { assertCode(document, "UNKNOWN_FIELD"); } for (String document : List.of( - VALID_DOCUMENT.replace("\"allowEmpty\": false,", - "\"allowEmpty\": false,\"allowEmpty\": false,"), - VALID_DOCUMENT.replace("\"type\": \"URI\",", - "\"type\": \"URI\",\"type\": \"URI\","), + VALID_DOCUMENT.replace("\"allowEmpty\": false,", "\"allowEmpty\": false,\"allowEmpty\": false,"), + VALID_DOCUMENT.replace("\"type\": \"URI\",", "\"type\": \"URI\",\"type\": \"URI\","), VALID_DOCUMENT.replace("\"keyUsageCritical\": true,", "\"keyUsageCritical\": true,\"keyUsageCritical\": true,"))) { assertCode(document, "DUPLICATE_FIELD"); } - for (String document : List.of( - VALID_DOCUMENT.replace("\"schemaVersion\": 2,\n", ""), + for (String document : List.of(VALID_DOCUMENT.replace("\"schemaVersion\": 2,\n", ""), VALID_DOCUMENT.replace("\"allowEmpty\": false,\n", ""), VALID_DOCUMENT.replace("\"oid\": \"2.5.4.3\",\n", ""), VALID_DOCUMENT.replace("\"minimumTotal\": 1,\n", ""), @@ -335,131 +309,99 @@ final class CertificateProfileDocumentCodecTest { VALID_DOCUMENT.replace("\"basicConstraintsCritical\": true,\n", ""))) { assertCode(document, "MISSING_FIELD"); } - assertCode(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", - "\"displayName\": null"), "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"allowEmpty\": false", - "\"allowEmpty\": \"false\""), "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"minimumTotal\": 1", - "\"minimumTotal\": \"1\""), "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"keyUsage\": [\"KEY_AGREEMENT\", " - + "\"ENCIPHER_ONLY\", \"DIGITAL_SIGNATURE\"]", "\"keyUsage\": true"), - "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": \"7\""), "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": -1"), "PROFILE_VERSION_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": 9223372036854775808"), "LIMIT_EXCEEDED"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": 7.0"), "WRONG_TYPE"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": 7e0"), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", "\"displayName\": null"), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"allowEmpty\": false", "\"allowEmpty\": \"false\""), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"minimumTotal\": 1", "\"minimumTotal\": \"1\""), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace( + "\"keyUsage\": [\"KEY_AGREEMENT\", " + "\"ENCIPHER_ONLY\", \"DIGITAL_SIGNATURE\"]", + "\"keyUsage\": true"), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": \"7\""), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": -1"), + "PROFILE_VERSION_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": 9223372036854775808"), + "LIMIT_EXCEEDED"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": 7.0"), "WRONG_TYPE"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": 7e0"), "WRONG_TYPE"); } @Test void rejectsUnsupportedVersionsTokensCaseWhitespaceAndNoncanonicalDuration() { - assertCode(VALID_DOCUMENT.replace("\"schemaVersion\": 2", - "\"schemaVersion\": 1"), "SCHEMA_VERSION_UNSUPPORTED"); - assertCode(VALID_DOCUMENT.replace("\"certificateType\": \"END_ENTITY\"", - "\"certificateType\": \"end_entity\""), "CERTIFICATE_TYPE_UNSUPPORTED"); - assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", - "\"profileVersion\": 0"), "PROFILE_VERSION_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"type\": \"DNS_NAME\"", - "\"type\": \"dns_name\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"source\": \"REQUESTER\"", - "\"source\": \" REQUESTER\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"profileId\": \"tls-service\"", - "\"profileId\": \" tls-service\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"formatId\": \"x509\"", - "\"formatId\": \"x509 \""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", - "\"displayName\": \" TLS service\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"oid\": \"2.5.4.3\"", - "\"oid\": \"2.5.4.999\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"type\": \"DNS_NAME\"", - "\"type\": \"OTHER_NAME\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"DIGITAL_SIGNATURE\"", - "\"CERTIFICATE_SIGN\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"schemaVersion\": 2", "\"schemaVersion\": 1"), + "SCHEMA_VERSION_UNSUPPORTED"); + assertCode(VALID_DOCUMENT.replace("\"certificateType\": \"END_ENTITY\"", "\"certificateType\": \"end_entity\""), + "CERTIFICATE_TYPE_UNSUPPORTED"); + assertCode(VALID_DOCUMENT.replace("\"profileVersion\": 7", "\"profileVersion\": 0"), "PROFILE_VERSION_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"type\": \"DNS_NAME\"", "\"type\": \"dns_name\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"source\": \"REQUESTER\"", "\"source\": \" REQUESTER\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"profileId\": \"tls-service\"", "\"profileId\": \" tls-service\""), + "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"formatId\": \"x509\"", "\"formatId\": \"x509 \""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", "\"displayName\": \" TLS service\""), + "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"oid\": \"2.5.4.3\"", "\"oid\": \"2.5.4.999\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"type\": \"DNS_NAME\"", "\"type\": \"OTHER_NAME\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"DIGITAL_SIGNATURE\"", "\"CERTIFICATE_SIGN\""), "TOKEN_INVALID"); assertCode(VALID_DOCUMENT.replace("\"Ed448\"", "\"ed448\""), "TOKEN_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"1.3.6.1.5.5.7.3.2\"", - "\"not-an-oid\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"1.3.6.1.5.5.7.3.2\"", "\"not-an-oid\""), "TOKEN_INVALID"); for (String duration : List.of("P1D", "PT0S", "PT-1S", "one-day")) { - assertCode(VALID_DOCUMENT.replace("\"maxValidity\": \"PT24H\"", - "\"maxValidity\": \"" + duration + "\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"maxValidity\": \"PT24H\"", "\"maxValidity\": \"" + duration + "\""), + "TOKEN_INVALID"); } } @Test void rejectsTypeSpecificShapeViolationsAndSemanticInvariants() { - assertCode(VALID_DOCUMENT.replace("\"wildcardAllowed\": true", - "\"ipv4Allowed\": true"), "MISSING_FIELD"); + assertCode(VALID_DOCUMENT.replace("\"wildcardAllowed\": true", "\"ipv4Allowed\": true"), "MISSING_FIELD"); assertCode(VALID_DOCUMENT.replace("\"type\": \"RFC822_NAME\",", - "\"type\": \"RFC822_NAME\",\"wildcardAllowed\": false,"), - "UNKNOWN_FIELD"); + "\"type\": \"RFC822_NAME\",\"wildcardAllowed\": false,"), "UNKNOWN_FIELD"); assertCode(VALID_DOCUMENT.replace("\"source\": \"REQUESTER\",", - "\"source\": \"REQUESTER\",\"fixedValue\": \"forbidden\","), - "UNKNOWN_FIELD"); - assertCode(VALID_DOCUMENT.replace(",\n \"fixedValue\": \"ZeroEcho\"", ""), - "MISSING_FIELD"); - assertCode(VALID_DOCUMENT.replace("\"allowEmpty\": false", - "\"allowEmpty\": true").replace("\"minimumTotal\": 1", - "\"minimumTotal\": 0"), - "SEMANTIC_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"ipv4Allowed\": true", - "\"ipv4Allowed\": false").replace("\"ipv6Allowed\": true", - "\"ipv6Allowed\": false"), - "SEMANTIC_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"keyUsage\": [\"KEY_AGREEMENT\", " - + "\"ENCIPHER_ONLY\", \"DIGITAL_SIGNATURE\"]", + "\"source\": \"REQUESTER\",\"fixedValue\": \"forbidden\","), "UNKNOWN_FIELD"); + assertCode(VALID_DOCUMENT.replace(",\n \"fixedValue\": \"ZeroEcho\"", ""), "MISSING_FIELD"); + assertCode(VALID_DOCUMENT.replace("\"allowEmpty\": false", "\"allowEmpty\": true") + .replace("\"minimumTotal\": 1", "\"minimumTotal\": 0"), "SEMANTIC_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"ipv4Allowed\": true", "\"ipv4Allowed\": false") + .replace("\"ipv6Allowed\": true", "\"ipv6Allowed\": false"), "SEMANTIC_INVALID"); + assertCode(VALID_DOCUMENT.replace( + "\"keyUsage\": [\"KEY_AGREEMENT\", " + "\"ENCIPHER_ONLY\", \"DIGITAL_SIGNATURE\"]", "\"keyUsage\": [\"ENCIPHER_ONLY\"]"), "SEMANTIC_INVALID"); String rich = canonical(parse(VALID_DOCUMENT)); - assertCode(rich.replace("\"oid\":\"2.5.4.10\"", "\"oid\":\"2.5.4.3\""), + assertCode(rich.replace("\"oid\":\"2.5.4.10\"", "\"oid\":\"2.5.4.3\""), "SEMANTIC_INVALID"); + assertCode(replaceFirst(rich, "\"minimumOccurrences\":1", "\"minimumOccurrences\":2"), "SEMANTIC_INVALID"); + assertCode(replaceFirst(rich, "\"maximumOccurrences\":1", "\"maximumOccurrences\":33"), "SEMANTIC_INVALID"); + assertCode(rich.replace("\"minimumTotal\":1,\"maximumTotal\":4", "\"minimumTotal\":5,\"maximumTotal\":4"), "SEMANTIC_INVALID"); - assertCode(replaceFirst(rich, "\"minimumOccurrences\":1", - "\"minimumOccurrences\":2"), "SEMANTIC_INVALID"); - assertCode(replaceFirst(rich, "\"maximumOccurrences\":1", - "\"maximumOccurrences\":33"), "SEMANTIC_INVALID"); - assertCode(rich.replace("\"minimumTotal\":1,\"maximumTotal\":4", - "\"minimumTotal\":5,\"maximumTotal\":4"), "SEMANTIC_INVALID"); - assertCode(replaceFirst(rich, "\"minimumOccurrences\":0", - "\"minimumOccurrences\":2"), "SEMANTIC_INVALID"); + assertCode(replaceFirst(rich, "\"minimumOccurrences\":0", "\"minimumOccurrences\":2"), "SEMANTIC_INVALID"); String dnsRule = "{\"type\":\"DNS_NAME\",\"minimumOccurrences\":0," + "\"maximumOccurrences\":1,\"wildcardAllowed\":true}"; assertCode(rich.replace(dnsRule, dnsRule + "," + dnsRule), "SEMANTIC_INVALID"); - String uri = canonical(definition("uri-invalid", requesterCn(), uriSan(), eku(), - Set.of("RSA"))); - assertCode(uri.replace("\"allowedSchemes\":[\"https\"]", - "\"allowedSchemes\":[\"not a scheme\"]"), "SEMANTIC_INVALID"); - assertCode(uri.replace("\"allowedSchemes\":[\"https\"]", - "\"allowedSchemes\":[\"https\",\"https\"]"), "TOKEN_INVALID"); - - String dns = canonical(definition("dns-identity", requesterCn(), dnsSan(false, false), - eku(), Set.of("RSA"))); - assertCode(dns.replace("\"emailIdentityRequired\":false", - "\"emailIdentityRequired\":true"), "SEMANTIC_INVALID"); - String email = canonical(definition("service-identity", requesterCn(), emailSan(), - eku(), Set.of("RSA"))); - assertCode(email.replace("\"serviceIdentityRequired\":false", - "\"serviceIdentityRequired\":true"), "SEMANTIC_INVALID"); - String empty = canonical(definition("empty-invalid", new SubjectPolicy(true, List.of()), - dnsSan(false, true), eku(), Set.of("RSA"))); - assertCode(empty.replace("\"minimumTotal\":1", "\"minimumTotal\":0"), + String uri = canonical(definition("uri-invalid", requesterCn(), uriSan(), eku(), Set.of("RSA"))); + assertCode(uri.replace("\"allowedSchemes\":[\"https\"]", "\"allowedSchemes\":[\"not a scheme\"]"), "SEMANTIC_INVALID"); + assertCode(uri.replace("\"allowedSchemes\":[\"https\"]", "\"allowedSchemes\":[\"https\",\"https\"]"), + "TOKEN_INVALID"); + + String dns = canonical(definition("dns-identity", requesterCn(), dnsSan(false, false), eku(), Set.of("RSA"))); + assertCode(dns.replace("\"emailIdentityRequired\":false", "\"emailIdentityRequired\":true"), + "SEMANTIC_INVALID"); + String email = canonical(definition("service-identity", requesterCn(), emailSan(), eku(), Set.of("RSA"))); + assertCode(email.replace("\"serviceIdentityRequired\":false", "\"serviceIdentityRequired\":true"), + "SEMANTIC_INVALID"); + String empty = canonical(definition("empty-invalid", new SubjectPolicy(true, List.of()), dnsSan(false, true), + eku(), Set.of("RSA"))); + assertCode(empty.replace("\"minimumTotal\":1", "\"minimumTotal\":0"), "SEMANTIC_INVALID"); } @Test void rejectsDuplicateSetLikeValues() { - assertCode(VALID_DOCUMENT.replace("\"DIGITAL_SIGNATURE\"]", - "\"DIGITAL_SIGNATURE\",\"DIGITAL_SIGNATURE\"]"), "SEMANTIC_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"1.3.6.1.5.5.7.3.1\"]", - "\"1.3.6.1.5.5.7.3.1\",\"1.3.6.1.5.5.7.3.1\"]"), + assertCode(VALID_DOCUMENT.replace("\"DIGITAL_SIGNATURE\"]", "\"DIGITAL_SIGNATURE\",\"DIGITAL_SIGNATURE\"]"), "SEMANTIC_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"Ed448\"]", "\"Ed448\",\"Ed448\"]"), + assertCode(VALID_DOCUMENT.replace("\"1.3.6.1.5.5.7.3.1\"]", "\"1.3.6.1.5.5.7.3.1\",\"1.3.6.1.5.5.7.3.1\"]"), "SEMANTIC_INVALID"); - assertCode(VALID_DOCUMENT.replace("\"spiffe\", \"https\"", - "\"spiffe\", \"https\", \"https\""), "TOKEN_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"Ed448\"]", "\"Ed448\",\"Ed448\"]"), "SEMANTIC_INVALID"); + assertCode(VALID_DOCUMENT.replace("\"spiffe\", \"https\"", "\"spiffe\", \"https\", \"https\""), + "TOKEN_INVALID"); } @Test @@ -470,28 +412,24 @@ final class CertificateProfileDocumentCodecTest { String longProfileId = "a".repeat(129); assertCode(VALID_DOCUMENT.replace("tls-service", longProfileId), "LIMIT_EXCEEDED"); - assertEquals("a".repeat(128), - parse(VALID_DOCUMENT.replace("tls-service", "a".repeat(128))).profileId()); + assertEquals("a".repeat(128), parse(VALID_DOCUMENT.replace("tls-service", "a".repeat(128))).profileId()); assertCode(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", "\"displayName\": \"" + "d".repeat(257) + "\""), "LIMIT_EXCEEDED"); - assertEquals("d".repeat(256), parse(VALID_DOCUMENT.replace( - "\"displayName\": \"TLS service\"", + assertEquals("d".repeat(256), parse(VALID_DOCUMENT.replace("\"displayName\": \"TLS service\"", "\"displayName\": \"" + "d".repeat(256) + "\"")).displayName()); - assertCode(VALID_DOCUMENT.replace("\"formatId\": \"x509\"", - "\"formatId\": \"" + "f".repeat(129) + "\""), "LIMIT_EXCEEDED"); - assertEquals("f".repeat(128), parse(VALID_DOCUMENT.replace( - "\"formatId\": \"x509\"", - "\"formatId\": \"" + "f".repeat(128) + "\"")).formatId().value()); - assertCode(VALID_DOCUMENT.replace("\"spiffe\"", "\"" + "s".repeat(33) + "\""), + assertCode(VALID_DOCUMENT.replace("\"formatId\": \"x509\"", "\"formatId\": \"" + "f".repeat(129) + "\""), + "LIMIT_EXCEEDED"); + assertEquals("f".repeat(128), + parse(VALID_DOCUMENT.replace("\"formatId\": \"x509\"", "\"formatId\": \"" + "f".repeat(128) + "\"")) + .formatId().value()); + assertCode(VALID_DOCUMENT.replace("\"spiffe\"", "\"" + "s".repeat(33) + "\""), "LIMIT_EXCEEDED"); + assertTrue(parse(VALID_DOCUMENT.replace("\"spiffe\"", "\"" + "s".repeat(32) + "\"")).leafPolicy() + .subjectAlternativeNamePolicy().allowedUriSchemes().contains("s".repeat(32))); + assertCode( + VALID_DOCUMENT.replace("\"fixedValue\": \"ZeroEcho\"", "\"fixedValue\": \"" + "v".repeat(4_097) + "\""), "LIMIT_EXCEEDED"); - assertTrue(parse(VALID_DOCUMENT.replace("\"spiffe\"", "\"" + "s".repeat(32) + "\"")) - .leafPolicy().subjectAlternativeNamePolicy().allowedUriSchemes() - .contains("s".repeat(32))); - assertCode(VALID_DOCUMENT.replace("\"fixedValue\": \"ZeroEcho\"", - "\"fixedValue\": \"" + "v".repeat(4_097) + "\""), "LIMIT_EXCEEDED"); - String schemes = IntStream.range(0, 129) - .mapToObj(index -> "\"a" + index + "\"") + String schemes = IntStream.range(0, 129).mapToObj(index -> "\"a" + index + "\"") .reduce((left, right) -> left + "," + right).orElseThrow(); assertCode(VALID_DOCUMENT.replace("\"spiffe\", \"https\"", schemes), "LIMIT_EXCEEDED"); } @@ -514,14 +452,13 @@ final class CertificateProfileDocumentCodecTest { assertFalse(exception.getMessage().contains(secret)); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); - assertTrue(exception.getMessage().startsWith( - "Certificate profile document rejected: code=UNKNOWN_FIELD path=$.?")); + assertTrue(exception.getMessage() + .startsWith("Certificate profile document rejected: code=UNKNOWN_FIELD path=$.?")); } @Test void rejectsPolymorphicMetadataWithoutLoadingClassesOrLoggingInput() { - String probeName = "zeroecho.pki.api.profile.CertificateProfileDocumentCodecTest" - + "$InitializationProbe"; + String probeName = "zeroecho.pki.api.profile.CertificateProfileDocumentCodecTest" + "$InitializationProbe"; List hostileFields = List.of("@class", "class", "typeName", "java.lang.Runtime"); Logger logger = Logger.getLogger(CertificateProfileDocumentCodec.class.getName()); List records = new ArrayList<>(); @@ -562,84 +499,70 @@ final class CertificateProfileDocumentCodecTest { } private static String canonical(CertificateProfileDefinition definition) { - return new String(CertificateProfileDocumentCodec.writeCanonical(definition), - StandardCharsets.UTF_8); + return new String(CertificateProfileDocumentCodec.writeCanonical(definition), StandardCharsets.UTF_8); } private static CertificateProfileDefinition definition(String id, SubjectPolicy subject, - SubjectAlternativeNamePolicy san, Set extendedKeyUsages, - Set algorithms) { - LeafCertificatePolicy leaf = new LeafCertificatePolicy(san, - Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), extendedKeyUsages, true, false, true, - algorithms); - return new CertificateProfileDefinition(CertificateProfileKind.END_ENTITY, id, 1, - new FormatId("x509"), id, Duration.ofDays(1), subject, leaf); + SubjectAlternativeNamePolicy san, Set extendedKeyUsages, Set algorithms) { + LeafCertificatePolicy leaf = new LeafCertificatePolicy(san, Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), + extendedKeyUsages, true, false, true, algorithms); + return new CertificateProfileDefinition(CertificateProfileKind.END_ENTITY, id, 1, new FormatId("x509"), id, + Duration.ofDays(1), subject, leaf); } - private static CertificateProfileDefinition caDefinition(String id, - CertificateProfileKind kind, int pathLength) { + private static CertificateProfileDefinition caDefinition(String id, CertificateProfileKind kind, int pathLength) { SubjectPolicy subject = new SubjectPolicy(false, - List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 253, - Optional.empty(), true))); + List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 253, Optional.empty(), true))); CaCertificatePolicy ca = new CaCertificatePolicy(true, pathLength, true, Set.of(CaKeyUsage.KEY_CERT_SIGN, CaKeyUsage.CRL_SIGN), Set.of("RSA")); - return new CertificateProfileDefinition(kind, id, 1, new FormatId("x509"), id, - Duration.ofDays(365), subject, ca); + return new CertificateProfileDefinition(kind, id, 1, new FormatId("x509"), id, Duration.ofDays(365), subject, + ca); } private static SubjectPolicy requesterCn() { - return new SubjectPolicy(false, List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, - 1, 1, 128, Optional.empty(), true))); + return new SubjectPolicy(false, + List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 128, Optional.empty(), true))); } private static SubjectPolicy fixedOrganization() { - return new SubjectPolicy(false, List.of(new SubjectRdnRule(SubjectRdnType.ORGANIZATION_NAME, - 1, 1, 64, Optional.of("ZeroEcho"), false))); + return new SubjectPolicy(false, List + .of(new SubjectRdnRule(SubjectRdnType.ORGANIZATION_NAME, 1, 1, 64, Optional.of("ZeroEcho"), false))); } private static SubjectAlternativeNamePolicy noSan() { - return new SubjectAlternativeNamePolicy(0, 0, List.of(), false, Set.of(), - false, false, false); + return new SubjectAlternativeNamePolicy(0, 0, List.of(), false, Set.of(), false, false, false); } private static SubjectAlternativeNamePolicy dnsSan(boolean wildcard, boolean emptySubject) { return new SubjectAlternativeNamePolicy(1, 1, - List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, - 1, 1, false, false)), + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 1, 1, false, false)), wildcard, Set.of(), emptySubject, true, false); } private static SubjectAlternativeNamePolicy ipSan(boolean ipv4, boolean ipv6) { return new SubjectAlternativeNamePolicy(1, 1, - List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.IP_ADDRESS, - 1, 1, ipv4, ipv6)), - false, Set.of(), false, true, false); + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.IP_ADDRESS, 1, 1, ipv4, ipv6)), false, + Set.of(), false, true, false); } private static SubjectAlternativeNamePolicy uriSan() { return new SubjectAlternativeNamePolicy(1, 1, - List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.URI, - 1, 1, false, false)), - false, Set.of("https"), false, true, false); + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.URI, 1, 1, false, false)), false, + Set.of("https"), false, true, false); } private static SubjectAlternativeNamePolicy emailSan() { return new SubjectAlternativeNamePolicy(1, 1, - List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.RFC822_NAME, - 1, 1, false, false)), + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.RFC822_NAME, 1, 1, false, false)), false, Set.of(), false, false, true); } private static SubjectAlternativeNamePolicy mixedSan() { - return new SubjectAlternativeNamePolicy(0, 4, List.of( - new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, - 0, 1, false, false), - new SubjectAlternativeNameRule(SubjectAlternativeNameType.IP_ADDRESS, - 0, 1, true, true), - new SubjectAlternativeNameRule(SubjectAlternativeNameType.RFC822_NAME, - 0, 1, false, false), - new SubjectAlternativeNameRule(SubjectAlternativeNameType.URI, - 0, 1, false, false)), + return new SubjectAlternativeNamePolicy(0, 4, + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 0, 1, false, false), + new SubjectAlternativeNameRule(SubjectAlternativeNameType.IP_ADDRESS, 0, 1, true, true), + new SubjectAlternativeNameRule(SubjectAlternativeNameType.RFC822_NAME, 0, 1, false, false), + new SubjectAlternativeNameRule(SubjectAlternativeNameType.URI, 0, 1, false, false)), false, Set.of("https"), false, false, false); } @@ -666,23 +589,19 @@ final class CertificateProfileDocumentCodecTest { private static void assertCode(byte[] document, String code) { PkiException exception = assertThrows(PkiException.class, () -> CertificateProfileDocumentCodec.parse(document)); - assertTrue(exception.getMessage().contains("code=" + code + " "), - exception.getMessage()); + assertTrue(exception.getMessage().contains("code=" + code + " "), exception.getMessage()); assertNull(exception.getCause()); } private static void assertCode(ByteArrayInputStream input, String code) { - PkiException exception = assertThrows(PkiException.class, - () -> CertificateProfileDocumentCodec.parse(input)); - assertTrue(exception.getMessage().contains("code=" + code + " "), - exception.getMessage()); + PkiException exception = assertThrows(PkiException.class, () -> CertificateProfileDocumentCodec.parse(input)); + assertTrue(exception.getMessage().contains("code=" + code + " "), exception.getMessage()); } private static void assertWriteCode(CertificateProfileDefinition definition, String code) { PkiException exception = assertThrows(PkiException.class, () -> CertificateProfileDocumentCodec.writeCanonical(definition)); - assertTrue(exception.getMessage().contains("code=" + code + " "), - exception.getMessage()); + assertTrue(exception.getMessage().contains("code=" + code + " "), exception.getMessage()); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); } diff --git a/pki/src/test/java/zeroecho/pki/e2e/CaProfileIssuanceEnforcementTest.java b/pki/src/test/java/zeroecho/pki/e2e/CaProfileIssuanceEnforcementTest.java index 8687a1f..365d656 100644 --- a/pki/src/test/java/zeroecho/pki/e2e/CaProfileIssuanceEnforcementTest.java +++ b/pki/src/test/java/zeroecho/pki/e2e/CaProfileIssuanceEnforcementTest.java @@ -72,8 +72,7 @@ import zeroecho.pki.testkit.PkiTestRuntime; final class CaProfileIssuanceEnforcementTest { @Test - void fixedSubjectIntermediateIsValidatedOnceAndPersistsCanonicalSubject(@TempDir Path directory) - throws Exception { + void fixedSubjectIntermediateIsValidatedOnceAndPersistsCanonicalSubject(@TempDir Path directory) throws Exception { KeyPair rootKey = rsa(); KeyPair intermediateKey = rsa(); KeyRef rootRef = new KeyRef("kref:v1:keyring:ca-profile:fixed-root"); @@ -81,8 +80,7 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { byte[] fixedProfile = builtIn("intermediate-ca") - .replace("\"profileId\":\"intermediate-ca\"", - "\"profileId\":\"fixed-intermediate-ca\"") + .replace("\"profileId\":\"intermediate-ca\"", "\"profileId\":\"fixed-intermediate-ca\"") .replace("\"maximumUtf8Bytes\":253}]}", "\"maximumUtf8Bytes\":253},{\"oid\":\"2.5.4.10\",\"source\":\"PROFILE_FIXED\"," + "\"minimumOccurrences\":1,\"maximumOccurrences\":1," @@ -91,12 +89,13 @@ final class CaProfileIssuanceEnforcementTest { CertificateProfileRef reference = runtime.profileService().importProfile(fixedProfile); runtime.profileService().activateProfile(reference.profileId(), reference.profileVersion()); - PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Fixed Subject Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); - PkiId intermediateId = runtime.caService().createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootId, new SubjectRef("CN=Fixed Subject Intermediate"), - "fixed-intermediate-ca", Optional.of(intermediateRef), new SimpleAttributeSet())); + PkiId rootId = runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Fixed Subject Root"), + "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); + PkiId intermediateId = runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Fixed Subject Intermediate"), "fixed-intermediate-ca", + Optional.of(intermediateRef), new SimpleAttributeSet())); Credential additional = runtime.caService().issueIntermediateCertificate( new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, intermediateId, "fixed-intermediate-ca", Optional.empty(), new SimpleAttributeSet())); @@ -131,24 +130,25 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(store, bus, Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Profile Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); - intermediateId = runtime.caService().createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootId, new SubjectRef("CN=Profile Intermediate"), - "intermediate-ca", Optional.of(intermediateRef), new SimpleAttributeSet())); - Credential additional = runtime.caService().issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, intermediateId, - "intermediate-ca", Optional.empty(), new SimpleAttributeSet())); - rootProfile = ((CaProfileBinding) runtime.caService().getCa(rootId).caCredentials().get(0) - .profileBinding()).reference(); + new SubjectRef("CN=Profile Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); + intermediateId = runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Profile Intermediate"), "intermediate-ca", Optional.of(intermediateRef), + new SimpleAttributeSet())); + Credential additional = runtime.caService() + .issueIntermediateCertificate(new IntermediateCertIssueCommand(runtime.framework().formatId(), + rootId, intermediateId, "intermediate-ca", Optional.empty(), new SimpleAttributeSet())); + rootProfile = ((CaProfileBinding) runtime.caService().getCa(rootId).caCredentials().get(0).profileBinding()) + .reference(); intermediateProfile = ((CaProfileBinding) additional.profileBinding()).reference(); assertCaCertificate(runtime.caService().getCa(rootId).caCredentials().get(0), 1); assertCaCertificate(additional, 0); } try (PkiTestRuntime reopened = PkiTestRuntime.create(store, directory.resolve("reopened-bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { - assertEquals(rootProfile, ((CaProfileBinding) reopened.caService().getCa(rootId) - .caCredentials().get(0).profileBinding()).reference()); + assertEquals(rootProfile, + ((CaProfileBinding) reopened.caService().getCa(rootId).caCredentials().get(0).profileBinding()) + .reference()); assertEquals(intermediateProfile, ((CaProfileBinding) reopened.caService().getCa(intermediateId) .caCredentials().get(1).profileBinding()).reference()); } @@ -160,12 +160,14 @@ final class CaProfileIssuanceEnforcementTest { KeyRef rootRef = new KeyRef("kref:v1:keyring:ca-profile:missing"); try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey))) { - assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand( - runtime.framework().formatId(), new SubjectRef("CN=Missing"), "missing-ca-profile", - Optional.of(rootRef), new SimpleAttributeSet()))); - assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand( - runtime.framework().formatId(), new SubjectRef("CN=Wrong Kind"), "default", - Optional.of(rootRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Missing"), + "missing-ca-profile", Optional.of(rootRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Wrong Kind"), + "default", Optional.of(rootRef), new SimpleAttributeSet()))); assertEquals(0, runtime.submittedSignCount()); assertTrue(runtime.store().listCas().isEmpty()); } @@ -180,18 +182,18 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Historical Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); - byte[] versionTwo = builtIn("root-ca").replace("\"profileVersion\":1", - "\"profileVersion\":2").getBytes(StandardCharsets.UTF_8); + new SubjectRef("CN=Historical Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); + byte[] versionTwo = builtIn("root-ca").replace("\"profileVersion\":1", "\"profileVersion\":2") + .getBytes(StandardCharsets.UTF_8); CertificateProfileRef imported = runtime.profileService().importProfile(versionTwo); runtime.profileService().activateProfile(imported.profileId(), imported.profileVersion()); - PkiId intermediateId = runtime.caService().createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootId, new SubjectRef("CN=Historical Intermediate"), - "intermediate-ca", Optional.of(intermediateRef), new SimpleAttributeSet())); - CaProfileBinding issuerBinding = (CaProfileBinding) runtime.caService().getCa(rootId) - .caCredentials().get(0).profileBinding(); + PkiId intermediateId = runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Historical Intermediate"), "intermediate-ca", + Optional.of(intermediateRef), new SimpleAttributeSet())); + CaProfileBinding issuerBinding = (CaProfileBinding) runtime.caService().getCa(rootId).caCredentials().get(0) + .profileBinding(); assertEquals(1, issuerBinding.reference().profileVersion()); assertEquals(1, runtime.caService().getCa(intermediateId).caCredentials().size()); } @@ -233,8 +235,8 @@ final class CaProfileIssuanceEnforcementTest { assertCaProfileCredential("A", issuedA, versionOne, 1); credentialA = issuedA.credentialId(); - ImportedCertificateProfileVersion storedOne = runtime.profileService() - .getImportedVersion(profileId, 1).orElseThrow(); + ImportedCertificateProfileVersion storedOne = runtime.profileService().getImportedVersion(profileId, 1) + .orElseThrow(); persistedVersionOne = storedOne.canonicalJson(); persistedVersionOneHash = storedOne.reference().canonicalSha256(); @@ -255,12 +257,12 @@ final class CaProfileIssuanceEnforcementTest { assertCaProfileCredential("C", issuedC, versionTwo, 2); credentialC = issuedC.credentialId(); - assertCaProfileCredential("A-reread", runtime.store().getCredential(credentialA).orElseThrow(), - versionOne, 1); - assertCaProfileCredential("B-reread", runtime.store().getCredential(credentialB).orElseThrow(), - versionOne, 1); - ImportedCertificateProfileVersion unchanged = runtime.profileService() - .getImportedVersion(profileId, 1).orElseThrow(); + assertCaProfileCredential("A-reread", runtime.store().getCredential(credentialA).orElseThrow(), versionOne, + 1); + assertCaProfileCredential("B-reread", runtime.store().getCredential(credentialB).orElseThrow(), versionOne, + 1); + ImportedCertificateProfileVersion unchanged = runtime.profileService().getImportedVersion(profileId, 1) + .orElseThrow(); assertArrayEquals(persistedVersionOne, unchanged.canonicalJson()); assertArrayEquals(persistedVersionOneHash, unchanged.reference().canonicalSha256()); } @@ -273,8 +275,8 @@ final class CaProfileIssuanceEnforcementTest { versionOne, 1); assertCaProfileCredential("C-restart", reopened.store().getCredential(credentialC).orElseThrow(), versionTwo, 2); - ImportedCertificateProfileVersion unchanged = reopened.profileService() - .getImportedVersion(profileId, 1).orElseThrow(); + ImportedCertificateProfileVersion unchanged = reopened.profileService().getImportedVersion(profileId, 1) + .orElseThrow(); assertArrayEquals(persistedVersionOne, unchanged.canonicalJson()); assertArrayEquals(persistedVersionOneHash, unchanged.reference().canonicalSha256()); } @@ -329,8 +331,7 @@ final class CaProfileIssuanceEnforcementTest { Credential issuedA = onlyCredential(runtime.caService(), caA); assertCaProfileCredential("A", issuedA, versionOne, 0); credentialA = issuedA.credentialId(); - assertCaProfileCredential("issuer-after-A", onlyCredential(runtime.caService(), rootId), - rootProfile, 2); + assertCaProfileCredential("issuer-after-A", onlyCredential(runtime.caService(), rootId), rootProfile, 2); ImportedCertificateProfileVersion storedOne = runtime.profileService() .getImportedVersion(intermediateProfileId, 1).orElseThrow(); @@ -341,8 +342,7 @@ final class CaProfileIssuanceEnforcementTest { assertEquals(intermediateProfileId, versionTwo.profileId()); assertEquals(2, versionTwo.profileVersion()); assertTrue(!MessageDigest.isEqual(versionOne.canonicalSha256(), versionTwo.canonicalSha256())); - assertEquals(versionOne, - runtime.profileService().getActiveReference(intermediateProfileId).orElseThrow()); + assertEquals(versionOne, runtime.profileService().getActiveReference(intermediateProfileId).orElseThrow()); caB = createIntermediate(runtime, rootId, keyRefB, "CN=Version Switch Intermediate B", intermediateProfileId); @@ -357,12 +357,11 @@ final class CaProfileIssuanceEnforcementTest { assertCaProfileCredential("C", issuedC, versionTwo, 1); credentialC = issuedC.credentialId(); - assertCaProfileCredential("A-reread", runtime.store().getCredential(credentialA).orElseThrow(), - versionOne, 0); - assertCaProfileCredential("B-reread", runtime.store().getCredential(credentialB).orElseThrow(), - versionOne, 0); - assertCaProfileCredential("issuer-reread", onlyCredential(runtime.caService(), rootId), - rootProfile, 2); + assertCaProfileCredential("A-reread", runtime.store().getCredential(credentialA).orElseThrow(), versionOne, + 0); + assertCaProfileCredential("B-reread", runtime.store().getCredential(credentialB).orElseThrow(), versionOne, + 0); + assertCaProfileCredential("issuer-reread", onlyCredential(runtime.caService(), rootId), rootProfile, 2); ImportedCertificateProfileVersion unchanged = runtime.profileService() .getImportedVersion(intermediateProfileId, 1).orElseThrow(); assertArrayEquals(persistedVersionOne, unchanged.canonicalJson()); @@ -370,16 +369,14 @@ final class CaProfileIssuanceEnforcementTest { } try (PkiTestRuntime reopened = PkiTestRuntime.create(store, directory.resolve("reopened-bus.log"), keys)) { - assertEquals(versionTwo, - reopened.profileService().getActiveReference(intermediateProfileId).orElseThrow()); + assertEquals(versionTwo, reopened.profileService().getActiveReference(intermediateProfileId).orElseThrow()); assertCaProfileCredential("A-restart", reopened.store().getCredential(credentialA).orElseThrow(), versionOne, 0); assertCaProfileCredential("B-restart", reopened.store().getCredential(credentialB).orElseThrow(), versionOne, 0); assertCaProfileCredential("C-restart", reopened.store().getCredential(credentialC).orElseThrow(), versionTwo, 1); - assertCaProfileCredential("issuer-restart", onlyCredential(reopened.caService(), rootId), - rootProfile, 2); + assertCaProfileCredential("issuer-restart", onlyCredential(reopened.caService(), rootId), rootProfile, 2); ImportedCertificateProfileVersion unchanged = reopened.profileService() .getImportedVersion(intermediateProfileId, 1).orElseThrow(); assertArrayEquals(persistedVersionOne, unchanged.canonicalJson()); @@ -397,12 +394,10 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Format Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + new SubjectRef("CN=Format Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); CertificateProfileRef wrongFormat = runtime.profileService().importProfile(builtIn("root-ca") .replace("\"profileId\":\"root-ca\"", "\"profileId\":\"other-format-root\"") - .replace("\"formatId\":\"x509\"", "\"formatId\":\"other\"") - .getBytes(StandardCharsets.UTF_8)); + .replace("\"formatId\":\"x509\"", "\"formatId\":\"other\"").getBytes(StandardCharsets.UTF_8)); CaRecord root = runtime.caService().getCa(rootId); Credential original = root.caCredentials().get(0); Credential mutated = new Credential(original.credentialId(), original.formatId(), original.issuerRef(), @@ -412,10 +407,11 @@ final class CaProfileIssuanceEnforcementTest { root.subjectRef(), List.of(mutated))); int signCount = runtime.submittedSignCount(); - assertThrows(PkiException.class, () -> runtime.caService().createIntermediate( - new IntermediateCreateCommand(runtime.framework().formatId(), rootId, - new SubjectRef("CN=Format Intermediate"), "intermediate-ca", - Optional.of(intermediateRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Format Intermediate"), "intermediate-ca", + Optional.of(intermediateRef), new SimpleAttributeSet()))); assertEquals(signCount, runtime.submittedSignCount()); assertEquals(1, runtime.store().listCas().size()); } @@ -429,12 +425,14 @@ final class CaProfileIssuanceEnforcementTest { KeyRef dsaRef = new KeyRef("kref:v1:keyring:ca-profile:invalid-dsa"); try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, dsaRef, dsaKey))) { - assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand( - runtime.framework().formatId(), new SubjectRef("O=Forbidden"), "root-ca", - Optional.of(rootRef), new SimpleAttributeSet()))); - assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand( - runtime.framework().formatId(), new SubjectRef("CN=Unsupported Algorithm"), "root-ca", - Optional.of(dsaRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), + new SubjectRef("O=Forbidden"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService() + .createRoot(new CaCreateCommand(runtime.framework().formatId(), + new SubjectRef("CN=Unsupported Algorithm"), "root-ca", Optional.of(dsaRef), + new SimpleAttributeSet()))); assertEquals(0, runtime.submittedSignCount()); assertTrue(runtime.store().listCas().isEmpty()); } @@ -454,12 +452,11 @@ final class CaProfileIssuanceEnforcementTest { CaService service = runtime.caService(profiles); PkiId rootId = service.createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Lookup Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + new SubjectRef("CN=Lookup Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); profiles.assertAndReset("root-ca"); PkiId intermediateId = service.createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootId, new SubjectRef("CN=Lookup Intermediate"), - "intermediate-ca", Optional.of(intermediateRef), new SimpleAttributeSet())); + runtime.framework().formatId(), rootId, new SubjectRef("CN=Lookup Intermediate"), "intermediate-ca", + Optional.of(intermediateRef), new SimpleAttributeSet())); profiles.assertAndReset("intermediate-ca"); service.issueIntermediateCertificate(new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, intermediateId, "intermediate-ca", Optional.empty(), new SimpleAttributeSet())); @@ -472,13 +469,11 @@ final class CaProfileIssuanceEnforcementTest { CountingProfileService profiles = new CountingProfileService(target.profileService()); CaService service = target.caService(profiles); service.importRoot(new CaImportCommand(target.framework().formatId(), new SubjectRef("CN=Lookup Root"), - "root-ca", rootRef, new EncodedObject(Encoding.DER, rootCertificate), - new SimpleAttributeSet())); + "root-ca", rootRef, new EncodedObject(Encoding.DER, rootCertificate), new SimpleAttributeSet())); profiles.assertAndReset("root-ca"); } } - @Test void rootCannotBeUsedAsAdditionalIntermediateSubject(@TempDir Path directory) throws Exception { KeyPair rootKey = rsa(); @@ -486,12 +481,12 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey))) { PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Root Subject"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + new SubjectRef("CN=Root Subject"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); int signCount = runtime.submittedSignCount(); - assertThrows(PkiException.class, () -> runtime.caService().issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, rootId, - "intermediate-ca", Optional.empty(), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService().issueIntermediateCertificate( + new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, rootId, + "intermediate-ca", Optional.empty(), new SimpleAttributeSet()))); assertEquals(signCount, runtime.submittedSignCount()); assertEquals(1, runtime.caService().getCa(rootId).caCredentials().size()); } @@ -506,17 +501,19 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Validity Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); - PkiId intermediateId = runtime.caService().createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootId, new SubjectRef("CN=Validity Intermediate"), - "intermediate-ca", Optional.of(intermediateRef), new SimpleAttributeSet())); + new SubjectRef("CN=Validity Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); + PkiId intermediateId = runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Validity Intermediate"), "intermediate-ca", Optional.of(intermediateRef), + new SimpleAttributeSet())); int signCount = runtime.submittedSignCount(); Validity invalid = new Validity(Instant.EPOCH, Instant.EPOCH.plusSeconds(3_153_600_000L)); - assertThrows(PkiException.class, () -> runtime.caService().issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootId, intermediateId, - "intermediate-ca", Optional.of(invalid), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService() + .issueIntermediateCertificate(new IntermediateCertIssueCommand( + runtime.framework().formatId(), rootId, intermediateId, "intermediate-ca", + Optional.of(invalid), new SimpleAttributeSet()))); assertEquals(signCount, runtime.submittedSignCount()); assertEquals(1, runtime.caService().getCa(intermediateId).caCredentials().size()); } @@ -530,15 +527,14 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime source = PkiTestRuntime.create(directory.resolve("source"), directory.resolve("source-bus.log"), Map.of(rootRef, rootKey))) { PkiId rootId = source.caService().createRoot(new CaCreateCommand(source.framework().formatId(), - new SubjectRef("CN=Imported Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + new SubjectRef("CN=Imported Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); encoded = source.caService().getCa(rootId).caCredentials().get(0).encoded().bytes(); } try (PkiTestRuntime target = PkiTestRuntime.create(directory.resolve("target"), directory.resolve("target-bus.log"), Map.of(rootRef, rootKey))) { - PkiId imported = target.caService().importRoot(new CaImportCommand(target.framework().formatId(), - new SubjectRef("CN=Imported Root"), "root-ca", rootRef, - new EncodedObject(Encoding.DER, encoded), new SimpleAttributeSet())); + PkiId imported = target.caService() + .importRoot(new CaImportCommand(target.framework().formatId(), new SubjectRef("CN=Imported Root"), + "root-ca", rootRef, new EncodedObject(Encoding.DER, encoded), new SimpleAttributeSet())); Credential credential = target.caService().getCa(imported).caCredentials().get(0); assertEquals(target.profileService().getActiveReference("root-ca").orElseThrow(), ((CaProfileBinding) credential.profileBinding()).reference()); @@ -547,16 +543,16 @@ final class CaProfileIssuanceEnforcementTest { @ParameterizedTest @EnumSource(ImportMutation.class) - void importedRootMutationFailsBeforePersistence(ImportMutation mutation, @TempDir Path directory) - throws Exception { + void importedRootMutationFailsBeforePersistence(ImportMutation mutation, @TempDir Path directory) throws Exception { KeyPair rootKey = rsa(); KeyRef rootRef = new KeyRef("kref:v1:keyring:ca-profile:import-mutation"); byte[] encoded; try (PkiTestRuntime source = PkiTestRuntime.create(directory.resolve("source"), directory.resolve("source-bus.log"), Map.of(rootRef, rootKey))) { - PkiId rootId = source.caService().createRoot(new CaCreateCommand(source.framework().formatId(), - new SubjectRef("CN=Import Mutation Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + PkiId rootId = source.caService() + .createRoot(new CaCreateCommand(source.framework().formatId(), + new SubjectRef("CN=Import Mutation Root"), "root-ca", Optional.of(rootRef), + new SimpleAttributeSet())); Credential sourceCredential = source.caService().getCa(rootId).caCredentials().get(0); encoded = mutation.mutate(sourceCredential, rootKey); } @@ -571,8 +567,8 @@ final class CaProfileIssuanceEnforcementTest { @ParameterizedTest @EnumSource(MetadataMutation.class) - void intermediateBackendMetadataMutationFailsBeforePersistence(MetadataMutation mutation, - @TempDir Path directory) throws Exception { + void intermediateBackendMetadataMutationFailsBeforePersistence(MetadataMutation mutation, @TempDir Path directory) + throws Exception { KeyPair rootKey = rsa(); KeyPair intermediateKey = rsa(); KeyRef rootRef = new KeyRef("kref:v1:keyring:ca-profile:mutation-root"); @@ -580,8 +576,7 @@ final class CaProfileIssuanceEnforcementTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(directory, directory.resolve("bus.log"), Map.of(rootRef, rootKey, intermediateRef, intermediateKey))) { PkiId rootId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Mutation Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + new SubjectRef("CN=Mutation Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); CredentialIssuerBackend delegate = runtime.issuerBackend(); CredentialIssuerBackend backend = new CredentialIssuerBackend() { @Override @@ -593,22 +588,23 @@ final class CaProfileIssuanceEnforcementTest { @Override public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest request, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - return mutation.apply(delegate.issueIntermediateCertificate(request, issuerCertificate, - issuerKeyRef), rootKey); + return mutation.apply( + delegate.issueIntermediateCertificate(request, issuerCertificate, issuerKeyRef), rootKey); } }; - assertThrows(PkiException.class, () -> runtime.caService(backend).createIntermediate( - new IntermediateCreateCommand(runtime.framework().formatId(), rootId, - new SubjectRef("CN=Mutation Intermediate"), "intermediate-ca", - Optional.of(intermediateRef), new SimpleAttributeSet()))); + assertThrows(PkiException.class, + () -> runtime.caService(backend) + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootId, + new SubjectRef("CN=Mutation Intermediate"), "intermediate-ca", + Optional.of(intermediateRef), new SimpleAttributeSet()))); assertEquals(1, runtime.store().listCas().size()); } } private static void assertCaCertificate(Credential credential, int pathLength) throws Exception { X509CertificateHolder holder = new X509CertificateHolder(credential.encoded().bytes()); - BasicConstraints constraints = BasicConstraints.getInstance( - holder.getExtension(Extension.basicConstraints).getParsedValue()); + BasicConstraints constraints = BasicConstraints + .getInstance(holder.getExtension(Extension.basicConstraints).getParsedValue()); KeyUsage usage = KeyUsage.getInstance(holder.getExtension(Extension.keyUsage).getParsedValue()); assertTrue(holder.getExtension(Extension.basicConstraints).isCritical()); assertTrue(holder.getExtension(Extension.keyUsage).isCritical()); @@ -622,9 +618,8 @@ final class CaProfileIssuanceEnforcementTest { assertTrue(credential.profileBinding() instanceof CaProfileBinding); assertEquals(expectedProfile, ((CaProfileBinding) credential.profileBinding()).reference()); assertCaCertificate(credential, pathLength); - System.out.println("..." + label + " profileVersion=" + expectedProfile.profileVersion() - + " hash=" + abbreviatedHash(expectedProfile) - + " credentialId=" + credential.credentialId().value() + System.out.println("..." + label + " profileVersion=" + expectedProfile.profileVersion() + " hash=" + + abbreviatedHash(expectedProfile) + " credentialId=" + credential.credentialId().value() + " pathLength=" + pathLength); } @@ -633,8 +628,8 @@ final class CaProfileIssuanceEnforcementTest { new SubjectRef(subject), profileId, Optional.of(keyRef), new SimpleAttributeSet())); } - private static PkiId createIntermediate(PkiTestRuntime runtime, PkiId issuerId, KeyRef keyRef, - String subject, String profileId) { + private static PkiId createIntermediate(PkiTestRuntime runtime, PkiId issuerId, KeyRef keyRef, String subject, + String profileId) { return runtime.caService().createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), issuerId, new SubjectRef(subject), profileId, Optional.of(keyRef), new SimpleAttributeSet())); } @@ -645,14 +640,12 @@ final class CaProfileIssuanceEnforcementTest { return ca.caCredentials().get(0); } - private static byte[] caProfileDocument(String builtInProfileId, String profileId, - long profileVersion, int originalPathLength, int pathLength) { + private static byte[] caProfileDocument(String builtInProfileId, String profileId, long profileVersion, + int originalPathLength, int pathLength) { return builtIn(builtInProfileId) - .replace("\"profileId\":\"" + builtInProfileId + "\"", - "\"profileId\":\"" + profileId + "\"") + .replace("\"profileId\":\"" + builtInProfileId + "\"", "\"profileId\":\"" + profileId + "\"") .replace("\"profileVersion\":1", "\"profileVersion\":" + profileVersion) - .replace("\"pathLengthConstraint\":" + originalPathLength, - "\"pathLengthConstraint\":" + pathLength) + .replace("\"pathLengthConstraint\":" + originalPathLength, "\"pathLengthConstraint\":" + pathLength) .getBytes(StandardCharsets.UTF_8); } @@ -662,10 +655,10 @@ final class CaProfileIssuanceEnforcementTest { } private static String builtIn(String profileId) { - return BuiltInCertificateProfileCatalog.load(CaProfileIssuanceEnforcementTest.class.getClassLoader()) - .stream().filter(template -> template.definition().profileId().equals(profileId)) - .map(template -> new String(template.canonicalJson(), StandardCharsets.UTF_8)) - .findFirst().orElseThrow(); + return BuiltInCertificateProfileCatalog.load(CaProfileIssuanceEnforcementTest.class.getClassLoader()).stream() + .filter(template -> template.definition().profileId().equals(profileId)) + .map(template -> new String(template.canonicalJson(), StandardCharsets.UTF_8)).findFirst() + .orElseThrow(); } private static KeyPair rsa() throws Exception { @@ -715,8 +708,7 @@ final class CaProfileIssuanceEnforcementTest { } @Override - public Optional getImportedVersion(String profileId, - long profileVersion) { + public Optional getImportedVersion(String profileId, long profileVersion) { return delegate.getImportedVersion(profileId, profileVersion); } @@ -739,26 +731,28 @@ final class CaProfileIssuanceEnforcementTest { private enum MetadataMutation { PROFILE { - @Override Credential apply(Credential value, KeyPair issuerKey) { + @Override + Credential apply(Credential value, KeyPair issuerKey) { return copy(value, value.serialOrUniqueId(), value.validity(), new CaProfileBinding(new CertificateProfileRef("other", 1, new byte[32]))); } }, SERIAL { - @Override Credential apply(Credential value, KeyPair issuerKey) { + @Override + Credential apply(Credential value, KeyPair issuerKey) { return copy(value, value.serialOrUniqueId() + "1", value.validity(), value.profileBinding()); } }, VALIDITY { - @Override Credential apply(Credential value, KeyPair issuerKey) { - return copy(value, value.serialOrUniqueId(), - new zeroecho.pki.api.Validity(value.validity().notBefore(), - value.validity().notAfter().minusSeconds(1)), - value.profileBinding()); + @Override + Credential apply(Credential value, KeyPair issuerKey) { + return copy(value, value.serialOrUniqueId(), new zeroecho.pki.api.Validity(value.validity().notBefore(), + value.validity().notAfter().minusSeconds(1)), value.profileBinding()); } }, SUBJECT { - @Override Credential apply(Credential value, KeyPair issuerKey) { + @Override + Credential apply(Credential value, KeyPair issuerKey) { return new Credential(value.credentialId(), value.formatId(), value.issuerRef(), new SubjectRef("CN=Substituted"), value.validity(), value.serialOrUniqueId(), value.publicKeyId(), value.profileBinding(), value.status(), value.encoded(), @@ -766,26 +760,28 @@ final class CaProfileIssuanceEnforcementTest { } }, ATTRIBUTES { - @Override Credential apply(Credential value, KeyPair issuerKey) { + @Override + Credential apply(Credential value, KeyPair issuerKey) { zeroecho.pki.api.attr.AttributeSet attributes = SimpleAttributeSet.builder() .put(new zeroecho.pki.api.attr.AttributeId("test.unexpected"), new zeroecho.pki.api.attr.AttributeValue.StringValue("unexpected")) .build(); - return new Credential(value.credentialId(), value.formatId(), value.issuerRef(), - value.subjectRef(), value.validity(), value.serialOrUniqueId(), value.publicKeyId(), - value.profileBinding(), value.status(), value.encoded(), attributes); + return new Credential(value.credentialId(), value.formatId(), value.issuerRef(), value.subjectRef(), + value.validity(), value.serialOrUniqueId(), value.publicKeyId(), value.profileBinding(), + value.status(), value.encoded(), attributes); } }, EXTRA_EXTENSION { - @Override Credential apply(Credential value, KeyPair issuerKey) { + @Override + Credential apply(Credential value, KeyPair issuerKey) { return rebuild(value, issuerKey, null, true); } }, SUBJECT_DER { - @Override Credential apply(Credential value, KeyPair issuerKey) { - X500Name alternate = new X500Name(new RDN[] { - new RDN(BCStyle.CN, new DERPrintableString("Mutation Intermediate")) - }); + @Override + Credential apply(Credential value, KeyPair issuerKey) { + X500Name alternate = new X500Name( + new RDN[] { new RDN(BCStyle.CN, new DERPrintableString("Mutation Intermediate")) }); return rebuild(value, issuerKey, alternate, false); } }; @@ -825,11 +821,10 @@ final class CaProfileIssuanceEnforcementTest { builder.addExtension(Extension.keyUsage, true, alternateKeyUsage); } if (extraExtension) { - builder.addExtension(Extension.subjectKeyIdentifier, false, - new DEROctetString(new byte[] { 1 })); + builder.addExtension(Extension.subjectKeyIdentifier, false, new DEROctetString(new byte[] { 1 })); } - byte[] encoded = builder.build(new JcaContentSignerBuilder("SHA256withRSA") - .build(issuerKey.getPrivate())).getEncoded(); + byte[] encoded = builder + .build(new JcaContentSignerBuilder("SHA256withRSA").build(issuerKey.getPrivate())).getEncoded(); return new Credential(new PkiId("x509:" + sha256(encoded)), value.formatId(), value.issuerRef(), value.subjectRef(), value.validity(), value.serialOrUniqueId(), value.publicKeyId(), value.profileBinding(), value.status(), new EncodedObject(Encoding.DER, encoded), @@ -846,60 +841,72 @@ final class CaProfileIssuanceEnforcementTest { private enum ImportMutation { SUBJECT { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Substituted"), "root-ca"); } }, KIND { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), "intermediate-ca"); } }, MALFORMED_DER { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { return importCommand(runtime, keyRef, new byte[] { 0x30, 0x01, 0x00 }, new SubjectRef("CN=Import Mutation Root"), "root-ca"); } }, EXTRA_EXTENSION { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { - return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), - "root-ca"); + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), "root-ca"); } - @Override byte[] mutate(Credential credential, KeyPair issuerKey) { + + @Override + byte[] mutate(Credential credential, KeyPair issuerKey) { return MetadataMutation.rebuild(credential, issuerKey, null, true).encoded().bytes(); } }, BASIC_CONSTRAINTS { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { - return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), - "root-ca"); + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), "root-ca"); } - @Override byte[] mutate(Credential credential, KeyPair issuerKey) { - return MetadataMutation.rebuild(credential, issuerKey, null, false, - new BasicConstraints(false), null, null).encoded().bytes(); + + @Override + byte[] mutate(Credential credential, KeyPair issuerKey) { + return MetadataMutation + .rebuild(credential, issuerKey, null, false, new BasicConstraints(false), null, null).encoded() + .bytes(); } }, KEY_USAGE { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { - return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), - "root-ca"); + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), "root-ca"); } - @Override byte[] mutate(Credential credential, KeyPair issuerKey) { + + @Override + byte[] mutate(Credential credential, KeyPair issuerKey) { return MetadataMutation.rebuild(credential, issuerKey, null, false, null, new KeyUsage(KeyUsage.digitalSignature), null).encoded().bytes(); } }, VALIDITY { - @Override CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { - return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), - "root-ca"); + @Override + CaImportCommand command(PkiTestRuntime runtime, KeyRef keyRef, byte[] encoded) { + return importCommand(runtime, keyRef, encoded, new SubjectRef("CN=Import Mutation Root"), "root-ca"); } - @Override byte[] mutate(Credential credential, KeyPair issuerKey) { + + @Override + byte[] mutate(Credential credential, KeyPair issuerKey) { Date extended = Date.from(credential.validity().notAfter().plusSeconds(1)); - return MetadataMutation.rebuild(credential, issuerKey, null, false, null, null, extended) - .encoded().bytes(); + return MetadataMutation.rebuild(credential, issuerKey, null, false, null, null, extended).encoded() + .bytes(); } }; diff --git a/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityAcceptanceE2eTest.java b/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityAcceptanceE2eTest.java index e19402f..07f5d7f 100644 --- a/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityAcceptanceE2eTest.java +++ b/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityAcceptanceE2eTest.java @@ -92,39 +92,37 @@ import zeroecho.pki.testkit.H7ProfileDocuments; import zeroecho.pki.testkit.PkiTestRuntime; /** - * End-to-end H7 acceptance evidence from signed PKCS#10 requests through durable - * real X.509 leaf certificates. + * End-to-end H7 acceptance evidence from signed PKCS#10 requests through + * durable real X.509 leaf certificates. */ final class H7EndEntityAcceptanceE2eTest { private static final int POSITIVE_ISSUANCE_CASE_COUNT = 16; private static final int MAIN_BACKEND_MUTATION_CASE_COUNT = 61; private static final int BACKEND_MUTATION_CASE_COUNT = 63; private static final String REDACTION_SENTINEL = "DO-NOT-LOG-H7-SENTINEL"; - private static final Set LEAF_EXTENSION_OIDS = Set.of( - Extension.basicConstraints, Extension.keyUsage, Extension.extendedKeyUsage, - Extension.subjectAlternativeName); + private static final Set LEAF_EXTENSION_OIDS = Set.of(Extension.basicConstraints, + Extension.keyUsage, Extension.extendedKeyUsage, Extension.subjectAlternativeName); @Test - void everyBuiltInTemplateRequiresImportAndActivationBeforeRealIssuance(@TempDir Path tempDir) - throws Exception { + void everyBuiltInTemplateRequiresImportAndActivationBeforeRealIssuance(@TempDir Path tempDir) throws Exception { KeyPair rootKey = rsa(); KeyPair leafKey = rsa(); KeyRef rootKeyRef = new KeyRef("kref:v1:keyring:h7-built-in:root"); try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, tempDir.resolve("bus.log"), Map.of(rootKeyRef, rootKey))) { - List templates = BuiltInCertificateProfileCatalog.load( - H7EndEntityAcceptanceE2eTest.class.getClassLoader()); + List templates = BuiltInCertificateProfileCatalog + .load(H7EndEntityAcceptanceE2eTest.class.getClassLoader()); assertEquals(6, templates.size()); for (BuiltInCertificateProfileTemplate template : templates) { String profileId = template.definition().profileId(); assertTrue(runtime.profileService().getImportedVersion(profileId, 1).isEmpty()); } - PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=H7 Built-in Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet())); + PkiId rootCaId = runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=H7 Built-in Root"), + "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); for (BuiltInCertificateProfileTemplate template : templates.stream() - .filter(template -> template.definition().certificateType() - == zeroecho.pki.api.profile.CertificateProfileKind.END_ENTITY) + .filter(template -> template.definition() + .certificateType() == zeroecho.pki.api.profile.CertificateProfileKind.END_ENTITY) .toList()) { String profileId = template.definition().profileId(); CertificateProfileRef imported = runtime.profileService().importBuiltIn(template); @@ -133,15 +131,16 @@ final class H7EndEntityAcceptanceE2eTest { assertEquals(imported, runtime.profileService().activateProfile(profileId, 1)); GeneralName identity = builtInIdentity(profileId); - ParsedCertificationRequest request = parse(runtime, leafKey, - new X500Name("CN=" + profileId), List.of(identity)); - Credential issued = runtime.issuanceService().issueEndEntity(new IssueEndEntityCommand(rootCaId, - request, profileId, Optional.empty())).credential(); + ParsedCertificationRequest request = parse(runtime, leafKey, new X500Name("CN=" + profileId), + List.of(identity)); + Credential issued = runtime.issuanceService() + .issueEndEntity(new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty())) + .credential(); EndEntityProfileBinding binding = (EndEntityProfileBinding) issued.profileBinding(); assertEquals(imported, binding.reference()); X509CertificateHolder holder = new X509CertificateHolder(issued.encoded().bytes()); - GeneralName[] names = GeneralNames.fromExtensions(holder.getExtensions(), - Extension.subjectAlternativeName).getNames(); + GeneralName[] names = GeneralNames + .fromExtensions(holder.getExtensions(), Extension.subjectAlternativeName).getNames(); assertEquals(1, names.length); assertEquals(identity.getTagNo(), names[0].getTagNo()); } @@ -150,12 +149,10 @@ final class H7EndEntityAcceptanceE2eTest { private static GeneralName builtInIdentity(String profileId) { return switch (profileId) { - case "server-tls", "vpn-server" -> - new GeneralName(GeneralName.dNSName, profileId + ".example.com"); + case "server-tls", "vpn-server" -> new GeneralName(GeneralName.dNSName, profileId + ".example.com"); case "vpn-client" -> new GeneralName(GeneralName.uniformResourceIdentifier, "spiffe://example.test/workload"); - case "email-signing" -> - new GeneralName(GeneralName.rfc822Name, "Signer@example.com"); + case "email-signing" -> new GeneralName(GeneralName.rfc822Name, "Signer@example.com"); default -> throw new IllegalArgumentException("unexpected built-in profile"); }; } @@ -172,24 +169,21 @@ final class H7EndEntityAcceptanceE2eTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, busFile, Map.of(rootKeyRef, rootKey))) { List profiles = acceptanceProfileDocuments(); profiles.forEach(runtime::importAndActivate); - PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=H7 Acceptance Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet())); + PkiId rootCaId = runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=H7 Acceptance Root"), + "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); CredentialIssuerBackend serialCapturingBackend = serialCapturingBackend(runtime.issuerBackend(), allocatedSerials); - List cases = List.of( - new IssuanceCase("h7-dns", new X500Name("CN=DNS Leaf"), false, - List.of(new GeneralName(GeneralName.dNSName, "WWW.Example.COM")), - List.of("2:www.example.com")), + List cases = List.of(new IssuanceCase("h7-dns", new X500Name("CN=DNS Leaf"), false, + List.of(new GeneralName(GeneralName.dNSName, "WWW.Example.COM")), List.of("2:www.example.com")), new IssuanceCase("h7-dns-multiple", new X500Name("CN=Multiple DNS Leaf"), false, List.of(new GeneralName(GeneralName.dNSName, "one.example.com"), new GeneralName(GeneralName.dNSName, "two.example.com"), new GeneralName(GeneralName.dNSName, "three.example.com")), List.of("2:one.example.com", "2:two.example.com", "2:three.example.com")), new IssuanceCase("h7-wildcard", new X500Name("CN=Wildcard Leaf"), false, - List.of(new GeneralName(GeneralName.dNSName, "*.Example.COM")), - List.of("2:*.example.com")), + List.of(new GeneralName(GeneralName.dNSName, "*.Example.COM")), List.of("2:*.example.com")), new IssuanceCase("h7-dns-empty", new X500Name(""), true, List.of(new GeneralName(GeneralName.dNSName, "empty.example.com")), List.of("2:empty.example.com")), @@ -197,8 +191,7 @@ final class H7EndEntityAcceptanceE2eTest { List.of(new GeneralName(GeneralName.dNSName, "critical.example.com")), List.of("2:critical.example.com")), new IssuanceCase("h7-ip", new X500Name("CN=IPv4 Leaf"), false, - List.of(ipName(new byte[] { (byte) 192, 0, 2, 10 })), - List.of("7:c000020a")), + List.of(ipName(new byte[] { (byte) 192, 0, 2, 10 })), List.of("7:c000020a")), new IssuanceCase("h7-ip", new X500Name("CN=IPv6 Leaf"), false, List.of(ipName(HexFormat.of().parseHex("20010db8000000000000000000000001"))), List.of("7:20010db8000000000000000000000001")), @@ -207,19 +200,16 @@ final class H7EndEntityAcceptanceE2eTest { ipName(HexFormat.of().parseHex("20010db8000000000000000000000002"))), List.of("7:c000020b", "7:20010db8000000000000000000000002")), new IssuanceCase("h7-ip-empty", new X500Name(""), true, - List.of(ipName(new byte[] { (byte) 192, 0, 2, 12 })), - List.of("7:c000020c")), + List.of(ipName(new byte[] { (byte) 192, 0, 2, 12 })), List.of("7:c000020c")), new IssuanceCase("h7-uri", new X500Name("CN=URI Leaf"), false, List.of(new GeneralName(GeneralName.uniformResourceIdentifier, "HTTPS://Service.Example.COM/a%2Fb?q=%2F")), List.of("6:https://service.example.com/a%2Fb?q=%2F")), new IssuanceCase("h7-uri-multiple", new X500Name("CN=Multiple URI Leaf"), false, - List.of(new GeneralName(GeneralName.uniformResourceIdentifier, - "https://one.example.com/a"), + List.of(new GeneralName(GeneralName.uniformResourceIdentifier, "https://one.example.com/a"), new GeneralName(GeneralName.uniformResourceIdentifier, "HTTPS://Two.Example.COM/b%2Fc?q=%2F")), - List.of("6:https://one.example.com/a", - "6:https://two.example.com/b%2Fc?q=%2F")), + List.of("6:https://one.example.com/a", "6:https://two.example.com/b%2Fc?q=%2F")), new IssuanceCase("h7-uri-empty", new X500Name(""), true, List.of(new GeneralName(GeneralName.uniformResourceIdentifier, "https://empty.example.com/service")), @@ -240,8 +230,8 @@ final class H7EndEntityAcceptanceE2eTest { new GeneralName(GeneralName.uniformResourceIdentifier, "https://mixed.example.com/service"), new GeneralName(GeneralName.rfc822Name, "Mixed@Example.COM")), - List.of("2:mixed.example.com", "7:c6336407", - "6:https://mixed.example.com/service", "1:Mixed@example.com"))); + List.of("2:mixed.example.com", "7:c6336407", "6:https://mixed.example.com/service", + "1:Mixed@example.com"))); assertEquals(POSITIVE_ISSUANCE_CASE_COUNT, cases.size()); for (IssuanceCase issuanceCase : cases) { @@ -260,8 +250,8 @@ final class H7EndEntityAcceptanceE2eTest { assertTrue(serial.toByteArray().length <= 20); } assertEquals(List.of("issuerCaId", "request", "profileId", "validityOverride"), - Arrays.stream(IssueEndEntityCommand.class.getRecordComponents()) - .map(component -> component.getName()).toList()); + Arrays.stream(IssueEndEntityCommand.class.getRecordComponents()).map(component -> component.getName()) + .toList()); try (PkiTestRuntime reopened = PkiTestRuntime.create(tempDir, busFile, Map.of(rootKeyRef, rootKey))) { for (Map.Entry entry : durableCredentials.entrySet()) { @@ -280,34 +270,28 @@ final class H7EndEntityAcceptanceE2eTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, tempDir.resolve("bus.log"), Map.of(rootKeyRef, rootKey))) { acceptanceProfileDocuments().forEach(runtime::importAndActivate); - PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=H7 Rejection Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet())); + PkiId rootCaId = runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=H7 Rejection Root"), + "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); assertProfileRejected(runtime, rootCaId, leafKey, "h7-dns", - List.of(new GeneralName(GeneralName.dNSName, "*.example.com")), - "SAN_WILDCARD_FORBIDDEN"); + List.of(new GeneralName(GeneralName.dNSName, "*.example.com")), "SAN_WILDCARD_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-ip", - List.of(new GeneralName(GeneralName.dNSName, "wrong-type.example.com")), - "SAN_TYPE_FORBIDDEN"); + List.of(new GeneralName(GeneralName.dNSName, "wrong-type.example.com")), "SAN_TYPE_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-ipv4", List.of(ipName(HexFormat.of().parseHex("20010db8000000000000000000000001"))), "SAN_IP_FAMILY_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-ipv6", - List.of(ipName(new byte[] { (byte) 192, 0, 2, 20 })), - "SAN_IP_FAMILY_FORBIDDEN"); + List.of(ipName(new byte[] { (byte) 192, 0, 2, 20 })), "SAN_IP_FAMILY_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-uri", - List.of(new GeneralName(GeneralName.uniformResourceIdentifier, - "http://service.example.com/path")), + List.of(new GeneralName(GeneralName.uniformResourceIdentifier, "http://service.example.com/path")), "SAN_URI_SCHEME_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-rfc822", - List.of(new GeneralName(GeneralName.dNSName, "mail.example.com")), - "SAN_TYPE_FORBIDDEN"); + List.of(new GeneralName(GeneralName.dNSName, "mail.example.com")), "SAN_TYPE_FORBIDDEN"); assertProfileRejected(runtime, rootCaId, leafKey, "h7-mixed", List.of(new GeneralName(GeneralName.dNSName, "mixed.example.com"), - ipName(new byte[] { (byte) 198, 51, 100, 8 }), - new GeneralName(GeneralName.uniformResourceIdentifier, - "https://mixed.example.com/service")), + ipName(new byte[] { (byte) 198, 51, 100, 8 }), new GeneralName( + GeneralName.uniformResourceIdentifier, "https://mixed.example.com/service")), "SAN_COUNT_INVALID"); } } @@ -325,15 +309,13 @@ final class H7EndEntityAcceptanceE2eTest { Map.of(rootKeyRef, rootKey))) { runtime.importAndActivate(H7ProfileDocuments.backendMutationProfile()); runtime.importAndActivate(H7ProfileDocuments.noSanOrEkuProfile()); - PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=H7 Mutation Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet())); - ParsedCertificationRequest request = parse(runtime, leafKey, - new X500Name("CN=Mutation Leaf,O=Example"), + PkiId rootCaId = runtime.caService().createRoot( + new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=H7 Mutation Root"), + "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); + ParsedCertificationRequest request = parse(runtime, leafKey, new X500Name("CN=Mutation Leaf,O=Example"), List.of(new GeneralName(GeneralName.dNSName, "base.example.com"), ipName(new byte[] { (byte) 203, 0, 113, 9 }), - new GeneralName(GeneralName.uniformResourceIdentifier, - "https://base.example.com/service"), + new GeneralName(GeneralName.uniformResourceIdentifier, "https://base.example.com/service"), new GeneralName(GeneralName.rfc822Name, "Base@example.com"))); assertEquals(BACKEND_MUTATION_CASE_COUNT, LeafMutation.values().length); @@ -341,16 +323,16 @@ final class H7EndEntityAcceptanceE2eTest { Arrays.stream(LeafMutation.values()).filter(LeafMutation::mainProfile).count()); for (LeafMutation mutation : LeafMutation.values()) { if (mutation.mainProfile()) { - assertMaliciousMutationRejected(runtime, rootCaId, request, "h7-backend-mutation", - rootKey, substituteKey, mutation); + assertMaliciousMutationRejected(runtime, rootCaId, request, "h7-backend-mutation", rootKey, + substituteKey, mutation); } } ParsedCertificationRequest noExtensions = parseWithoutExtensions(runtime, leafKey, new X500Name("CN=No Extensions Leaf")); - assertMaliciousMutationRejected(runtime, rootCaId, noExtensions, "h7-no-san-eku", - rootKey, substituteKey, LeafMutation.SUBJECT_ALTERNATIVE_NAME_ADDED_WHEN_FORBIDDEN); - assertMaliciousMutationRejected(runtime, rootCaId, noExtensions, "h7-no-san-eku", - rootKey, substituteKey, LeafMutation.EXTENDED_KEY_USAGE_ADDED_WHEN_FORBIDDEN); + assertMaliciousMutationRejected(runtime, rootCaId, noExtensions, "h7-no-san-eku", rootKey, substituteKey, + LeafMutation.SUBJECT_ALTERNATIVE_NAME_ADDED_WHEN_FORBIDDEN); + assertMaliciousMutationRejected(runtime, rootCaId, noExtensions, "h7-no-san-eku", rootKey, substituteKey, + LeafMutation.EXTENDED_KEY_USAGE_ADDED_WHEN_FORBIDDEN); assertFalse(logHandler.messages().contains(REDACTION_SENTINEL)); } finally { rootLogger.removeHandler(logHandler); @@ -365,8 +347,8 @@ final class H7EndEntityAcceptanceE2eTest { generator.addExtension(oid, true, value); try { generator.addExtension(oid, false, value); - assertEquals(1, Arrays.stream(generator.generate().getExtensionOIDs()) - .filter(oid::equals).count(), oid::getId); + assertEquals(1, Arrays.stream(generator.generate().getExtensionOIDs()).filter(oid::equals).count(), + oid::getId); } catch (RuntimeException expectedRejection) { assertFalse(String.valueOf(expectedRejection.getMessage()).isBlank(), oid::getId); } @@ -391,17 +373,15 @@ final class H7EndEntityAcceptanceE2eTest { H7ProfileDocuments.wildcardDnsProfile(), H7ProfileDocuments.emptySubjectDnsProfile(), H7ProfileDocuments.criticalDnsProfile(), H7ProfileDocuments.ipProfile(), H7ProfileDocuments.mixedIpProfile(), H7ProfileDocuments.emptySubjectIpProfile(), - H7ProfileDocuments.ipv4Profile(), H7ProfileDocuments.ipv6Profile(), - H7ProfileDocuments.uriProfile(), H7ProfileDocuments.multipleUriProfile(), - H7ProfileDocuments.emptySubjectUriProfile(), H7ProfileDocuments.rfc822Profile(), - H7ProfileDocuments.multipleRfc822Profile(), H7ProfileDocuments.emptySubjectRfc822Profile(), - H7ProfileDocuments.mixedSanProfile()); + H7ProfileDocuments.ipv4Profile(), H7ProfileDocuments.ipv6Profile(), H7ProfileDocuments.uriProfile(), + H7ProfileDocuments.multipleUriProfile(), H7ProfileDocuments.emptySubjectUriProfile(), + H7ProfileDocuments.rfc822Profile(), H7ProfileDocuments.multipleRfc822Profile(), + H7ProfileDocuments.emptySubjectRfc822Profile(), H7ProfileDocuments.mixedSanProfile()); } private static Credential issue(PkiTestRuntime runtime, CredentialIssuerBackend backend, PkiId rootCaId, KeyPair leafKey, IssuanceCase issuanceCase) throws Exception { - ParsedCertificationRequest request = parse(runtime, leafKey, issuanceCase.subject(), - issuanceCase.sans()); + ParsedCertificationRequest request = parse(runtime, leafKey, issuanceCase.subject(), issuanceCase.sans()); CredentialBundle bundle = runtime.issuanceService(backend, runtime.statusResolver()).issueEndEntity( new IssueEndEntityCommand(rootCaId, request, issuanceCase.profileId(), Optional.empty())); return bundle.credential(); @@ -414,8 +394,8 @@ final class H7EndEntityAcceptanceE2eTest { private static ParsedCertificationRequest parse(PkiTestRuntime runtime, KeyPair leafKey, X500Name subject, List sans) throws Exception { - PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder( - subject, leafKey.getPublic()); + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, + leafKey.getPublic()); ExtensionsGenerator extensionGenerator = new ExtensionsGenerator(); extensionGenerator.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(sans.toArray(GeneralName[]::new))); @@ -428,8 +408,8 @@ final class H7EndEntityAcceptanceE2eTest { private static ParsedCertificationRequest parseWithoutExtensions(PkiTestRuntime runtime, KeyPair leafKey, X500Name subject) throws Exception { - PKCS10CertificationRequestBuilder builder = - new JcaPKCS10CertificationRequestBuilder(subject, leafKey.getPublic()); + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, + leafKey.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(leafKey.getPrivate()); PKCS10CertificationRequest csr = builder.build(signer); return runtime.certificationRequestService().parse(new CertificationRequest(runtime.framework().formatId(), @@ -447,7 +427,8 @@ final class H7EndEntityAcceptanceE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; @@ -456,8 +437,7 @@ final class H7EndEntityAcceptanceE2eTest { private static void assertExactLeaf(Credential credential, KeyPair rootKey, KeyPair leafKey, PersistedExpectation expectation) throws Exception { assertEquals(Encoding.DER, credential.encoded().encoding()); - EndEntityProfileBinding binding = assertInstanceOf(EndEntityProfileBinding.class, - credential.profileBinding()); + EndEntityProfileBinding binding = assertInstanceOf(EndEntityProfileBinding.class, credential.profileBinding()); assertEquals(expectation.issuanceCase().profileId(), binding.reference().profileId()); assertEquals(expectation.validity(), credential.validity()); assertEquals(expectation.publicKeyId(), credential.publicKeyId()); @@ -479,8 +459,8 @@ final class H7EndEntityAcceptanceE2eTest { assertEquals(credential.validity().notAfter().getEpochSecond(), holder.getNotAfter().toInstant().getEpochSecond()); - assertEquals(LEAF_EXTENSION_OIDS, Arrays.stream(holder.getExtensions().getExtensionOIDs()).collect( - java.util.stream.Collectors.toUnmodifiableSet())); + assertEquals(LEAF_EXTENSION_OIDS, Arrays.stream(holder.getExtensions().getExtensionOIDs()) + .collect(java.util.stream.Collectors.toUnmodifiableSet())); Extension basicConstraints = holder.getExtension(Extension.basicConstraints); assertTrue(basicConstraints.isCritical()); assertFalse(BasicConstraints.getInstance(basicConstraints.getParsedValue()).isCA()); @@ -498,13 +478,13 @@ final class H7EndEntityAcceptanceE2eTest { } private static List encodedSans(X509CertificateHolder holder) { - GeneralName[] names = GeneralNames.fromExtensions(holder.getExtensions(), - Extension.subjectAlternativeName).getNames(); + GeneralName[] names = GeneralNames.fromExtensions(holder.getExtensions(), Extension.subjectAlternativeName) + .getNames(); List encoded = new ArrayList<>(names.length); for (GeneralName name : names) { if (name.getTagNo() == GeneralName.iPAddress) { - encoded.add(name.getTagNo() + ":" + HexFormat.of().formatHex( - DEROctetString.getInstance(name.getName()).getOctets())); + encoded.add(name.getTagNo() + ":" + + HexFormat.of().formatHex(DEROctetString.getInstance(name.getName()).getOctets())); } else { encoded.add(name.getTagNo() + ":" + DERIA5String.getInstance(name.getName()).getString()); } @@ -516,8 +496,8 @@ final class H7EndEntityAcceptanceE2eTest { return new GeneralName(GeneralName.iPAddress, new DEROctetString(address)); } - private static void assertProfileRejected(PkiTestRuntime runtime, PkiId rootCaId, KeyPair leafKey, - String profileId, List sans, String code) throws Exception { + private static void assertProfileRejected(PkiTestRuntime runtime, PkiId rootCaId, KeyPair leafKey, String profileId, + List sans, String code) throws Exception { ParsedCertificationRequest request = parse(runtime, leafKey, "Rejected Leaf", sans); AtomicInteger backendCalls = new AtomicInteger(); CredentialIssuerBackend backend = countingBackend(runtime.issuerBackend(), backendCalls); @@ -525,8 +505,8 @@ final class H7EndEntityAcceptanceE2eTest { int signsBefore = runtime.submittedSignCount(); PkiException rejection = assertThrows(PkiException.class, - () -> runtime.issuanceService(backend, runtime.statusResolver()).issueEndEntity( - new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty()))); + () -> runtime.issuanceService(backend, runtime.statusResolver()) + .issueEndEntity(new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty()))); assertTrue(rejection.getMessage().contains(code), profileId); assertEquals(0, backendCalls.get(), profileId); @@ -545,7 +525,8 @@ final class H7EndEntityAcceptanceE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; @@ -558,25 +539,23 @@ final class H7EndEntityAcceptanceE2eTest { AtomicReference baselineCredential = new AtomicReference<>(); AtomicReference deliveredSerial = new AtomicReference<>(); AtomicInteger backendCalls = new AtomicInteger(); - CredentialIssuerBackend backend = mutatingBackend(runtime.issuerBackend(), rootKey, substituteKey, - mutation, baselineCredential, maliciousCredential, deliveredSerial, backendCalls); + CredentialIssuerBackend backend = mutatingBackend(runtime.issuerBackend(), rootKey, substituteKey, mutation, + baselineCredential, maliciousCredential, deliveredSerial, backendCalls); int auditBefore = runtime.auditSink().snapshot().size(); PkiException rejection = assertThrows(PkiException.class, - () -> runtime.issuanceService(backend, runtime.statusResolver()).issueEndEntity( - new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty())), + () -> runtime.issuanceService(backend, runtime.statusResolver()) + .issueEndEntity(new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty())), mutation.name()); - String expectedCode = mutation == LeafMutation.PROFILE_ID_METADATA - ? "CREDENTIAL_PROFILE_BINDING_MISMATCH" : "BACKEND_CREDENTIAL_MISMATCH"; + String expectedCode = mutation == LeafMutation.PROFILE_ID_METADATA ? "CREDENTIAL_PROFILE_BINDING_MISMATCH" + : "BACKEND_CREDENTIAL_MISMATCH"; assertTrue(rejection.getMessage().contains(expectedCode), mutation.name()); assertEquals(1, backendCalls.get(), mutation.name()); assertTrue(deliveredSerial.get().signum() > 0, mutation.name()); assertTrue(deliveredSerial.get().toByteArray().length <= 20, mutation.name()); - assertTrue(runtime.store().getCredential(maliciousCredential.get().credentialId()).isEmpty(), - mutation.name()); - assertTrue(runtime.store().getCredential(baselineCredential.get().credentialId()).isEmpty(), - mutation.name()); + assertTrue(runtime.store().getCredential(maliciousCredential.get().credentialId()).isEmpty(), mutation.name()); + assertTrue(runtime.store().getCredential(baselineCredential.get().credentialId()).isEmpty(), mutation.name()); assertSingleRejectionAudit(runtime, auditBefore, expectedCode); assertRedacted(rejection, REDACTION_SENTINEL); assertFalse(runtime.auditSink().snapshot().toString().contains(REDACTION_SENTINEL), mutation.name()); @@ -592,8 +571,7 @@ final class H7EndEntityAcceptanceE2eTest { EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { backendCalls.incrementAndGet(); deliveredSerial.set(serial); - CredentialBundle baseline = delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, - serial); + CredentialBundle baseline = delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); baselineCredential.set(baseline.credential()); Credential mutated = mutateCredential(baseline.credential(), rootKey, substituteKey, mutation); maliciousCredential.set(mutated); @@ -601,7 +579,8 @@ final class H7EndEntityAcceptanceE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; @@ -611,8 +590,8 @@ final class H7EndEntityAcceptanceE2eTest { LeafMutation mutation) { try { X509CertificateHolder original = new X509CertificateHolder(credential.encoded().bytes()); - X500Name issuer = mutation == LeafMutation.ISSUER - ? new X500Name("CN=Wrong H7 Issuer") : original.getIssuer(); + X500Name issuer = mutation == LeafMutation.ISSUER ? new X500Name("CN=Wrong H7 Issuer") + : original.getIssuer(); X500Name subject = mutatedSubject(original.getSubject(), mutation); SubjectPublicKeyInfo publicKeyInfo = mutation == LeafMutation.PUBLIC_KEY ? SubjectPublicKeyInfo.getInstance(substituteKey.getPublic().getEncoded()) @@ -624,7 +603,8 @@ final class H7EndEntityAcceptanceE2eTest { default -> original.getSerialNumber(); }; Date notBefore = mutation == LeafMutation.NOT_BEFORE_MOVED - ? Date.from(original.getNotBefore().toInstant().plusSeconds(60)) : original.getNotBefore(); + ? Date.from(original.getNotBefore().toInstant().plusSeconds(60)) + : original.getNotBefore(); Date notAfter = switch (mutation) { case NOT_AFTER_EXTENDED -> Date.from(original.getNotAfter().toInstant().plusSeconds(60)); case VALIDITY_SHORTENED -> Date.from(original.getNotAfter().toInstant().minusSeconds(60)); @@ -643,36 +623,37 @@ final class H7EndEntityAcceptanceE2eTest { X509CertificateHolder mutated = new X509CertificateHolder(der); byte[] spki = mutated.getSubjectPublicKeyInfo().getEncoded(); Validity validity = new Validity(mutated.getNotBefore().toInstant(), mutated.getNotAfter().toInstant()); - PkiId credentialId = mutation == LeafMutation.CREDENTIAL_ID_METADATA - ? new PkiId("x509:wrong-metadata") : new PkiId("x509:" + sha256Hex(der)); - FormatId formatId = mutation == LeafMutation.FORMAT_METADATA - ? new FormatId("wrong-format") : credential.formatId(); + PkiId credentialId = mutation == LeafMutation.CREDENTIAL_ID_METADATA ? new PkiId("x509:wrong-metadata") + : new PkiId("x509:" + sha256Hex(der)); + FormatId formatId = mutation == LeafMutation.FORMAT_METADATA ? new FormatId("wrong-format") + : credential.formatId(); IssuerRef issuerRef = mutation == LeafMutation.ISSUER_REF_METADATA - ? new IssuerRef(new PkiId("ca:wrong-metadata")) : credential.issuerRef(); - SubjectRef subjectRef = mutated.getSubject().getRDNs().length == 0 - ? new SubjectRef("x509:empty-subject") : new SubjectRef(mutated.getSubject().toString()); + ? new IssuerRef(new PkiId("ca:wrong-metadata")) + : credential.issuerRef(); + SubjectRef subjectRef = mutated.getSubject().getRDNs().length == 0 ? new SubjectRef("x509:empty-subject") + : new SubjectRef(mutated.getSubject().toString()); if (mutation == LeafMutation.SUBJECT_REF_METADATA) { subjectRef = new SubjectRef("CN=Wrong Metadata Subject"); } Validity metadataValidity = mutation == LeafMutation.VALIDITY_METADATA - ? new Validity(validity.notBefore().plusSeconds(1), validity.notAfter()) : validity; + ? new Validity(validity.notBefore().plusSeconds(1), validity.notAfter()) + : validity; String metadataSerial = mutation == LeafMutation.SERIAL_METADATA ? original.getSerialNumber().add(BigInteger.TEN).toString() : mutated.getSerialNumber().toString(); - PkiId publicKeyId = mutation == LeafMutation.PUBLIC_KEY_ID_METADATA - ? new PkiId("spki:wrong-metadata") : new PkiId("spki:" + sha256Hex(spki)); + PkiId publicKeyId = mutation == LeafMutation.PUBLIC_KEY_ID_METADATA ? new PkiId("spki:wrong-metadata") + : new PkiId("spki:" + sha256Hex(spki)); EndEntityProfileBinding originalBinding = (EndEntityProfileBinding) credential.profileBinding(); CertificateProfileRef originalRef = originalBinding.reference(); EndEntityProfileBinding profileBinding = mutation == LeafMutation.PROFILE_ID_METADATA ? new EndEntityProfileBinding(new CertificateProfileRef("wrong-profile", originalRef.profileVersion(), originalRef.canonicalSha256())) : originalBinding; - CredentialStatus status = mutation == LeafMutation.STATUS_METADATA - ? CredentialStatus.REVOKED : credential.status(); + CredentialStatus status = mutation == LeafMutation.STATUS_METADATA ? CredentialStatus.REVOKED + : credential.status(); Encoding encoding = mutation == LeafMutation.ENCODING_METADATA ? Encoding.PEM : Encoding.DER; - return new Credential(credentialId, formatId, issuerRef, subjectRef, metadataValidity, - metadataSerial, publicKeyId, profileBinding, status, new EncodedObject(encoding, der), - credential.attributes()); + return new Credential(credentialId, formatId, issuerRef, subjectRef, metadataValidity, metadataSerial, + publicKeyId, profileBinding, status, new EncodedObject(encoding, der), credential.attributes()); } catch (Exception exception) { throw new IllegalStateException("Failed to build controlled H7 mutation " + mutation.name()); } @@ -715,8 +696,8 @@ final class H7EndEntityAcceptanceE2eTest { if (mutation == LeafMutation.BASIC_CONSTRAINTS_MALFORMED) { builder.addExtension(Extension.basicConstraints, true, DERNull.INSTANCE); } else { - boolean critical = mutation == LeafMutation.BASIC_CONSTRAINTS_NONCRITICAL - ? false : originalBasicConstraints.isCritical(); + boolean critical = mutation == LeafMutation.BASIC_CONSTRAINTS_NONCRITICAL ? false + : originalBasicConstraints.isCritical(); BasicConstraints value = switch (mutation) { case BASIC_CONSTRAINTS_CA -> new BasicConstraints(true); case BASIC_CONSTRAINTS_PATH_LENGTH -> new BasicConstraints(0); @@ -731,11 +712,11 @@ final class H7EndEntityAcceptanceE2eTest { if (mutation == LeafMutation.KEY_USAGE_MALFORMED) { builder.addExtension(Extension.keyUsage, true, DERNull.INSTANCE); } else { - boolean critical = mutation == LeafMutation.KEY_USAGE_NONCRITICAL - ? false : originalKeyUsage.isCritical(); + boolean critical = mutation == LeafMutation.KEY_USAGE_NONCRITICAL ? false + : originalKeyUsage.isCritical(); KeyUsage value = switch (mutation) { - case KEY_USAGE_ADD -> new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment - | KeyUsage.dataEncipherment); + case KEY_USAGE_ADD -> + new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment); case KEY_USAGE_REMOVE -> new KeyUsage(KeyUsage.digitalSignature); case KEY_USAGE_REPLACE -> new KeyUsage(KeyUsage.keyAgreement); default -> KeyUsage.getInstance(originalKeyUsage.getParsedValue()); @@ -763,8 +744,8 @@ final class H7EndEntityAcceptanceE2eTest { } else if (mutation == LeafMutation.EXTENDED_KEY_USAGE_REPLACE) { usages = new KeyPurposeId[] { KeyPurposeId.id_kp_codeSigning }; } else if (mutation == LeafMutation.EXTENDED_KEY_USAGE_DUPLICATE) { - usages = new KeyPurposeId[] { KeyPurposeId.id_kp_serverAuth, - KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth }; + usages = new KeyPurposeId[] { KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, + KeyPurposeId.id_kp_serverAuth }; } else { usages = ExtendedKeyUsage.getInstance(originalExtendedKeyUsage.getParsedValue()).getUsages(); } @@ -780,8 +761,8 @@ final class H7EndEntityAcceptanceE2eTest { if (mutation == LeafMutation.SUBJECT_ALTERNATIVE_NAME_MALFORMED) { builder.addExtension(Extension.subjectAlternativeName, false, DERNull.INSTANCE); } else { - GeneralName[] names = GeneralNames.fromExtensions(original.getExtensions(), - Extension.subjectAlternativeName).getNames(); + GeneralName[] names = GeneralNames + .fromExtensions(original.getExtensions(), Extension.subjectAlternativeName).getNames(); if (mutation == LeafMutation.SUBJECT_ALTERNATIVE_NAME_ADD_ONE) { GeneralName[] added = Arrays.copyOf(names, names.length + 1); added[names.length] = new GeneralName(GeneralName.dNSName, "added.example.com"); @@ -805,8 +786,7 @@ final class H7EndEntityAcceptanceE2eTest { names[2] = new GeneralName(GeneralName.uniformResourceIdentifier, "https://" + REDACTION_SENTINEL.toLowerCase(java.util.Locale.ROOT) + ".example.com/"); } else if (mutation == LeafMutation.SUBJECT_ALTERNATIVE_NAME_RFC822_VALUE) { - names[3] = new GeneralName(GeneralName.rfc822Name, - REDACTION_SENTINEL + "@example.com"); + names[3] = new GeneralName(GeneralName.rfc822Name, REDACTION_SENTINEL + "@example.com"); } else if (mutation == LeafMutation.SUBJECT_ALTERNATIVE_NAME_REORDERED) { GeneralName first = names[0]; names[0] = names[1]; @@ -825,8 +805,7 @@ final class H7EndEntityAcceptanceE2eTest { } else if (mutation == LeafMutation.SUBJECT_KEY_IDENTIFIER_EXTENSION) { builder.addExtension(Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(new byte[20])); } else if (mutation == LeafMutation.AUTHORITY_KEY_IDENTIFIER_EXTENSION) { - builder.addExtension(Extension.authorityKeyIdentifier, false, - new AuthorityKeyIdentifier(new byte[20])); + builder.addExtension(Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new byte[20])); } } @@ -867,74 +846,27 @@ final class H7EndEntityAcceptanceE2eTest { } } - private record PersistedExpectation(IssuanceCase issuanceCase, BigInteger allocatedSerial, - Validity validity, PkiId publicKeyId) { + private record PersistedExpectation(IssuanceCase issuanceCase, BigInteger allocatedSerial, Validity validity, + PkiId publicKeyId) { } private enum LeafMutation { - SUBJECT_REPLACED, - SUBJECT_REMOVED, - SUBJECT_ADDED, - SUBJECT_DUPLICATED, - SUBJECT_REORDERED, - SUBJECT_ALTERNATIVE_NAME_ADD_ONE, - SUBJECT_ALTERNATIVE_NAME_REMOVE_ONE, - SUBJECT_ALTERNATIVE_NAME_REPLACE, - SUBJECT_ALTERNATIVE_NAME_CHANGE_TYPE, - SUBJECT_ALTERNATIVE_NAME_DUPLICATE, - SUBJECT_ALTERNATIVE_NAME_DNS_CASE, - SUBJECT_ALTERNATIVE_NAME_IP_BYTES, - SUBJECT_ALTERNATIVE_NAME_URI_VALUE, - SUBJECT_ALTERNATIVE_NAME_RFC822_VALUE, - SUBJECT_ALTERNATIVE_NAME_REORDERED, - SUBJECT_ALTERNATIVE_NAME_CRITICAL, - SUBJECT_ALTERNATIVE_NAME_ABSENT, - SUBJECT_ALTERNATIVE_NAME_MALFORMED, - SUBJECT_ALTERNATIVE_NAME_ADDED_WHEN_FORBIDDEN(false), - PUBLIC_KEY, - ISSUER, - SERIAL, - SERIAL_ZERO, - SERIAL_NEGATIVE, - NOT_BEFORE_MOVED, - NOT_AFTER_EXTENDED, - VALIDITY_SHORTENED, - VALIDITY_EXCEEDS_ISSUER, - PROFILE_ID_METADATA, - CREDENTIAL_ID_METADATA, - FORMAT_METADATA, - ISSUER_REF_METADATA, - SUBJECT_REF_METADATA, - VALIDITY_METADATA, - SERIAL_METADATA, - PUBLIC_KEY_ID_METADATA, - STATUS_METADATA, - ENCODING_METADATA, - BASIC_CONSTRAINTS_ABSENT, - BASIC_CONSTRAINTS_CA, - BASIC_CONSTRAINTS_PATH_LENGTH, - BASIC_CONSTRAINTS_NONCRITICAL, - BASIC_CONSTRAINTS_MALFORMED, - KEY_USAGE_ABSENT, - KEY_USAGE_ADD, - KEY_USAGE_REMOVE, - KEY_USAGE_REPLACE, - KEY_USAGE_NONCRITICAL, - KEY_USAGE_MALFORMED, - EXTENDED_KEY_USAGE_ABSENT, - EXTENDED_KEY_USAGE_ADD, - EXTENDED_KEY_USAGE_REMOVE, - EXTENDED_KEY_USAGE_REPLACE, - EXTENDED_KEY_USAGE_DUPLICATE, - EXTENDED_KEY_USAGE_CRITICAL, - EXTENDED_KEY_USAGE_MALFORMED, - EXTENDED_KEY_USAGE_ADDED_WHEN_FORBIDDEN(false), - UNKNOWN_NONCRITICAL_EXTENSION, - UNKNOWN_CRITICAL_EXTENSION, - SUBJECT_KEY_IDENTIFIER_EXTENSION, - AUTHORITY_KEY_IDENTIFIER_EXTENSION, - CORRUPT_SIGNATURE, - SIGNED_BY_OTHER_KEY; + SUBJECT_REPLACED, SUBJECT_REMOVED, SUBJECT_ADDED, SUBJECT_DUPLICATED, SUBJECT_REORDERED, + SUBJECT_ALTERNATIVE_NAME_ADD_ONE, SUBJECT_ALTERNATIVE_NAME_REMOVE_ONE, SUBJECT_ALTERNATIVE_NAME_REPLACE, + SUBJECT_ALTERNATIVE_NAME_CHANGE_TYPE, SUBJECT_ALTERNATIVE_NAME_DUPLICATE, SUBJECT_ALTERNATIVE_NAME_DNS_CASE, + SUBJECT_ALTERNATIVE_NAME_IP_BYTES, SUBJECT_ALTERNATIVE_NAME_URI_VALUE, SUBJECT_ALTERNATIVE_NAME_RFC822_VALUE, + SUBJECT_ALTERNATIVE_NAME_REORDERED, SUBJECT_ALTERNATIVE_NAME_CRITICAL, SUBJECT_ALTERNATIVE_NAME_ABSENT, + SUBJECT_ALTERNATIVE_NAME_MALFORMED, SUBJECT_ALTERNATIVE_NAME_ADDED_WHEN_FORBIDDEN(false), PUBLIC_KEY, ISSUER, + SERIAL, SERIAL_ZERO, SERIAL_NEGATIVE, NOT_BEFORE_MOVED, NOT_AFTER_EXTENDED, VALIDITY_SHORTENED, + VALIDITY_EXCEEDS_ISSUER, PROFILE_ID_METADATA, CREDENTIAL_ID_METADATA, FORMAT_METADATA, ISSUER_REF_METADATA, + SUBJECT_REF_METADATA, VALIDITY_METADATA, SERIAL_METADATA, PUBLIC_KEY_ID_METADATA, STATUS_METADATA, + ENCODING_METADATA, BASIC_CONSTRAINTS_ABSENT, BASIC_CONSTRAINTS_CA, BASIC_CONSTRAINTS_PATH_LENGTH, + BASIC_CONSTRAINTS_NONCRITICAL, BASIC_CONSTRAINTS_MALFORMED, KEY_USAGE_ABSENT, KEY_USAGE_ADD, KEY_USAGE_REMOVE, + KEY_USAGE_REPLACE, KEY_USAGE_NONCRITICAL, KEY_USAGE_MALFORMED, EXTENDED_KEY_USAGE_ABSENT, + EXTENDED_KEY_USAGE_ADD, EXTENDED_KEY_USAGE_REMOVE, EXTENDED_KEY_USAGE_REPLACE, EXTENDED_KEY_USAGE_DUPLICATE, + EXTENDED_KEY_USAGE_CRITICAL, EXTENDED_KEY_USAGE_MALFORMED, EXTENDED_KEY_USAGE_ADDED_WHEN_FORBIDDEN(false), + UNKNOWN_NONCRITICAL_EXTENSION, UNKNOWN_CRITICAL_EXTENSION, SUBJECT_KEY_IDENTIFIER_EXTENSION, + AUTHORITY_KEY_IDENTIFIER_EXTENSION, CORRUPT_SIGNATURE, SIGNED_BY_OTHER_KEY; private final boolean mainProfile; diff --git a/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityCsrRejectionE2eTest.java b/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityCsrRejectionE2eTest.java index 7ec4592..b1e4075 100644 --- a/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityCsrRejectionE2eTest.java +++ b/pki/src/test/java/zeroecho/pki/e2e/H7EndEntityCsrRejectionE2eTest.java @@ -69,7 +69,8 @@ import zeroecho.pki.testkit.H7ProfileDocuments; import zeroecho.pki.testkit.PkiTestRuntime; /** - * Real signed-CSR rejection evidence for the closed H7 SAN and extension grammar. + * Real signed-CSR rejection evidence for the closed H7 SAN and extension + * grammar. */ final class H7EndEntityCsrRejectionE2eTest { private static final int DNS_AND_IP_REJECTION_CASE_COUNT = 11; @@ -81,52 +82,48 @@ final class H7EndEntityCsrRejectionE2eTest { @Test void signedDnsAndIpInvalidMatrixRejectsWithoutIssuanceSideEffects(@TempDir Path tempDir) throws Exception { try (RejectionFixture fixture = RejectionFixture.create(tempDir)) { - List parserCases = List.of( - sanCase("dns-trailing-dot", dns(REDACTION_SENTINEL + ".example."), "SAN_MALFORMED"), - sanCase("dns-partial-wildcard", dns("www*.example.com"), "SAN_MALFORMED"), - sanCase("dns-multiple-wildcard", dns("*.*.example.com"), "SAN_MALFORMED"), - sanCase("dns-ip-literal", dns("192.0.2.1"), "SAN_MALFORMED"), - sanCase("dns-duplicate-lowercase", - List.of(dns("Example.COM"), dns("example.com")), "SAN_DUPLICATE"), - sanCase("ip-invalid-length", ip(new byte[] { 1, 2, 3, 4, 5 }), "SAN_MALFORMED"), - sanCase("ip-duplicate", - List.of(ip(new byte[] { (byte) 192, 0, 2, 1 }), - ip(new byte[] { (byte) 192, 0, 2, 1 })), - "SAN_DUPLICATE"), - sanCase("ip-address-invalid-length", ip(new byte[15]), "SAN_MALFORMED")); + List parserCases = List + .of(sanCase("dns-trailing-dot", dns(REDACTION_SENTINEL + ".example."), "SAN_MALFORMED"), + sanCase("dns-partial-wildcard", dns("www*.example.com"), "SAN_MALFORMED"), + sanCase("dns-multiple-wildcard", dns("*.*.example.com"), "SAN_MALFORMED"), + sanCase("dns-ip-literal", dns("192.0.2.1"), "SAN_MALFORMED"), + sanCase("dns-duplicate-lowercase", List.of(dns("Example.COM"), dns("example.com")), + "SAN_DUPLICATE"), + sanCase("ip-invalid-length", ip(new byte[] { 1, 2, 3, 4, 5 }), "SAN_MALFORMED"), + sanCase("ip-duplicate", + List.of(ip(new byte[] { (byte) 192, 0, 2, 1 }), + ip(new byte[] { (byte) 192, 0, 2, 1 })), + "SAN_DUPLICATE"), + sanCase("ip-address-invalid-length", ip(new byte[15]), "SAN_MALFORMED")); assertParserCases(fixture, parserCases); fixture.assertProfileRejected("dns-wildcard-disabled", "h7-dns", - signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), - List.of(dns("*.example.com"))), + signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), List.of(dns("*.example.com"))), "SAN_WILDCARD_FORBIDDEN"); - fixture.assertProfileRejected("ipv6-forbidden-by-ipv4-profile", "h7-ipv4", - signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), - List.of(ip(hex("20010db8000000000000000000000001")))), + fixture.assertProfileRejected( + "ipv6-forbidden-by-ipv4-profile", "h7-ipv4", signedSanCsr(fixture.leafKey(), + new X500Name("CN=Leaf"), List.of(ip(hex("20010db8000000000000000000000001")))), "SAN_IP_FAMILY_FORBIDDEN"); - fixture.assertProfileRejected("ipv4-forbidden-by-ipv6-profile", "h7-ipv6", - signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), - List.of(ip(new byte[] { (byte) 192, 0, 2, 2 }))), + fixture.assertProfileRejected( + "ipv4-forbidden-by-ipv6-profile", "h7-ipv6", signedSanCsr(fixture.leafKey(), + new X500Name("CN=Leaf"), List.of(ip(new byte[] { (byte) 192, 0, 2, 2 }))), "SAN_IP_FAMILY_FORBIDDEN"); - assertEquals(CSR_REJECTION_CASE_COUNT, DNS_AND_IP_REJECTION_CASE_COUNT - + URI_AND_RFC822_REJECTION_CASE_COUNT + CSR_STRUCTURE_REJECTION_CASE_COUNT); + assertEquals(CSR_REJECTION_CASE_COUNT, DNS_AND_IP_REJECTION_CASE_COUNT + URI_AND_RFC822_REJECTION_CASE_COUNT + + CSR_STRUCTURE_REJECTION_CASE_COUNT); assertEquals(DNS_AND_IP_REJECTION_CASE_COUNT, fixture.assertedCaseCount()); } } @Test - void signedUriAndRfc822InvalidMatrixRejectsWithoutIssuanceSideEffects(@TempDir Path tempDir) - throws Exception { + void signedUriAndRfc822InvalidMatrixRejectsWithoutIssuanceSideEffects(@TempDir Path tempDir) throws Exception { try (RejectionFixture fixture = RejectionFixture.create(tempDir)) { String oversizedUri = "https://oversize.example.com/" + "a".repeat(2050); - List parserCases = List.of( - sanCase("uri-relative", uri("/relative/path"), "SAN_MALFORMED"), + List parserCases = List.of(sanCase("uri-relative", uri("/relative/path"), "SAN_MALFORMED"), sanCase("uri-missing-host", uri("https:///missing-host"), "SAN_MALFORMED"), sanCase("uri-opaque", uri("mailto:user@example.com"), "SAN_MALFORMED"), sanCase("uri-userinfo", uri("https://user@example.com/path"), "SAN_MALFORMED"), sanCase("uri-fragment", uri("https://example.com/path#fragment"), "SAN_MALFORMED"), - sanCase("uri-duplicate", - List.of(uri("HTTPS://Example.COM/path"), uri("https://example.com/path")), + sanCase("uri-duplicate", List.of(uri("HTTPS://Example.COM/path"), uri("https://example.com/path")), "SAN_DUPLICATE"), sanCase("uri-oversize", uri(oversizedUri), "SAN_MALFORMED"), sanCase("rfc822-display-name", rfc822("Display "), "SAN_MALFORMED"), @@ -136,26 +133,23 @@ final class H7EndEntityCsrRejectionE2eTest { sanCase("rfc822-empty-local", rfc822("@example.com"), "SAN_MALFORMED"), sanCase("rfc822-empty-domain", rfc822("user@"), "SAN_MALFORMED"), sanCase("rfc822-invalid-domain", rfc822("user@-example.com"), "SAN_MALFORMED"), - sanCase("rfc822-duplicate", - List.of(rfc822("Local@Example.COM"), rfc822("Local@example.com")), + sanCase("rfc822-duplicate", List.of(rfc822("Local@Example.COM"), rfc822("Local@example.com")), "SAN_DUPLICATE")); assertParserCases(fixture, parserCases); fixture.assertProfileRejected("uri-forbidden-scheme", "h7-uri", - signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), - List.of(uri("http://example.com/path"))), + signedSanCsr(fixture.leafKey(), new X500Name("CN=Leaf"), List.of(uri("http://example.com/path"))), "SAN_URI_SCHEME_FORBIDDEN"); PKCS10CertificationRequest subjectEmail = signedCsr(fixture.leafKey(), new X500Name("CN=Leaf,E=user@example.com"), List.of()); - fixture.assertProfileRejected("subject-email-does-not-replace-rfc822-san", - "h7-subject-email-rfc822", subjectEmail, "SAN_COUNT_INVALID"); + fixture.assertProfileRejected("subject-email-does-not-replace-rfc822-san", "h7-subject-email-rfc822", + subjectEmail, "SAN_COUNT_INVALID"); assertEquals(URI_AND_RFC822_REJECTION_CASE_COUNT, fixture.assertedCaseCount()); } } @Test - void signedCsrExtensionStructureMatrixRejectsBeforeBackendAndPersistence(@TempDir Path tempDir) - throws Exception { + void signedCsrExtensionStructureMatrixRejectsBeforeBackendAndPersistence(@TempDir Path tempDir) throws Exception { try (RejectionFixture fixture = RejectionFixture.create(tempDir)) { Extension san = sanExtension(false, List.of(dns("structure.example.com"))); Extension keyUsage = new Extension(Extension.keyUsage, true, @@ -179,19 +173,16 @@ final class H7EndEntityCsrRejectionE2eTest { extensionCase("san-plus-key-usage", new DERSequence(new ASN1Encodable[] { san, keyUsage }), "EXTENSION_UNSUPPORTED"), extensionCase("san-plus-basic-constraints", - new DERSequence(new ASN1Encodable[] { san, basicConstraints }), - "EXTENSION_UNSUPPORTED"), + new DERSequence(new ASN1Encodable[] { san, basicConstraints }), "EXTENSION_UNSUPPORTED"), extensionCase("unknown-critical", new Extensions(unknownCritical), "EXTENSION_UNSUPPORTED"), - extensionCase("unknown-noncritical", new Extensions(unknownNoncritical), - "EXTENSION_UNSUPPORTED"), + extensionCase("unknown-noncritical", new Extensions(unknownNoncritical), "EXTENSION_UNSUPPORTED"), extensionCase("two-san-extensions", duplicateSan, "EXTENSION_REQUEST_MALFORMED"), new SignedCsrCase("two-extension-request-attributes", new X500Name("CN=Leaf"), List.of(new Extensions(san), new Extensions(san)), "CSR_ATTRIBUTE_UNSUPPORTED"), extensionCase("empty-san", new Extensions(emptySan), "SAN_COUNT_INVALID"), extensionCase("other-name", new Extensions(otherName), "SAN_MALFORMED"), extensionCase("directory-name", new Extensions(directoryName), "SAN_TYPE_UNSUPPORTED"), - extensionCase("criticality-mismatch", new Extensions(criticalSan), - "SAN_CRITICALITY_REQUESTED")); + extensionCase("criticality-mismatch", new Extensions(criticalSan), "SAN_CRITICALITY_REQUESTED")); assertParserCases(fixture, cases); assertEquals(CSR_STRUCTURE_REJECTION_CASE_COUNT, fixture.assertedCaseCount()); } @@ -217,15 +208,15 @@ final class H7EndEntityCsrRejectionE2eTest { return new SignedCsrCase(name, new X500Name("CN=Leaf"), List.of(extensionRequest), code); } - private static PKCS10CertificationRequest signedSanCsr(KeyPair keyPair, X500Name subject, - List names) throws Exception { + private static PKCS10CertificationRequest signedSanCsr(KeyPair keyPair, X500Name subject, List names) + throws Exception { return signedCsr(keyPair, subject, List.of(new Extensions(sanExtension(false, names)))); } private static PKCS10CertificationRequest signedCsr(KeyPair keyPair, X500Name subject, List extensionRequests) throws Exception { - PKCS10CertificationRequestBuilder builder = - new JcaPKCS10CertificationRequestBuilder(subject, keyPair.getPublic()); + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, + keyPair.getPublic()); for (ASN1Encodable extensionRequest : extensionRequests) { builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionRequest); } @@ -292,9 +283,10 @@ final class H7EndEntityCsrRejectionE2eTest { runtime.importAndActivate(H7ProfileDocuments.ipv6Profile()); runtime.importAndActivate(H7ProfileDocuments.uriProfile()); runtime.importAndActivate(H7ProfileDocuments.subjectEmailAndRfc822Profile()); - PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=H7 CSR Rejection Root"), "root-ca", Optional.of(rootRef), - new SimpleAttributeSet())); + PkiId rootCaId = runtime.caService() + .createRoot(new CaCreateCommand(runtime.framework().formatId(), + new SubjectRef("CN=H7 CSR Rejection Root"), "root-ca", Optional.of(rootRef), + new SimpleAttributeSet())); PKCS10CertificationRequest valid = signedSanCsr(leafKey, new X500Name("CN=Template"), List.of(dns("template.example.com"))); ParsedCertificationRequest template = parse(runtime, valid); @@ -311,8 +303,9 @@ final class H7EndEntityCsrRejectionE2eTest { private void assertParserAndGateRejected(String name, PKCS10CertificationRequest csr, String parserCode) throws Exception { - assertTrue(csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build( - csr.getSubjectPublicKeyInfo())), name); + assertTrue( + csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build(csr.getSubjectPublicKeyInfo())), + name); int signsBefore = runtime.submittedSignCount(); int auditBefore = runtime.auditSink().snapshot().size(); PkiException parserRejection = assertThrows(PkiException.class, () -> parse(runtime, csr), name); @@ -326,10 +319,11 @@ final class H7EndEntityCsrRejectionE2eTest { assertedCases++; } - private void assertProfileRejected(String name, String profileId, PKCS10CertificationRequest csr, - String code) throws Exception { - assertTrue(csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build( - csr.getSubjectPublicKeyInfo())), name); + private void assertProfileRejected(String name, String profileId, PKCS10CertificationRequest csr, String code) + throws Exception { + assertTrue( + csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build(csr.getSubjectPublicKeyInfo())), + name); ParsedCertificationRequest parsed = parse(runtime, csr); int signsBefore = runtime.submittedSignCount(); int auditBefore = runtime.auditSink().snapshot().size(); @@ -337,19 +331,18 @@ final class H7EndEntityCsrRejectionE2eTest { assertedCases++; } - private void assertGateRejected(String name, ParsedCertificationRequest request, String profileId, - String code, int signsBefore, int auditBefore) { + private void assertGateRejected(String name, ParsedCertificationRequest request, String profileId, String code, + int signsBefore, int auditBefore) { AtomicInteger backendCalls = new AtomicInteger(); AtomicInteger persistenceCalls = new AtomicInteger(); CredentialIssuerBackend backend = countingBackend(runtime.issuerBackend(), backendCalls); PkiStore trackingStore = trackingStore(runtime.store(), persistenceCalls); - DefaultIssuanceService service = new DefaultIssuanceService(trackingStore, runtime.framework(), - backend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), - Clock.systemUTC()); + DefaultIssuanceService service = new DefaultIssuanceService(trackingStore, runtime.framework(), backend, + runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); PkiException rejection = assertThrows(PkiException.class, - () -> service.issueEndEntity(new IssueEndEntityCommand(rootCaId, request, profileId, - Optional.empty())), + () -> service + .issueEndEntity(new IssueEndEntityCommand(rootCaId, request, profileId, Optional.empty())), name); assertTrue(rejection.getMessage().contains(code), name); @@ -381,9 +374,8 @@ final class H7EndEntityCsrRejectionE2eTest { AttributeSet attributes = SimpleAttributeSet.builder() .put(BcX509Attributes.CSR_DER, new AttributeValue.BytesValue(csrDer)).build(); return new ParsedCertificationRequest(source.requestId(), source.formatId(), source.subjectRef(), - source.publicKeyInfo(), source.requestedValidity(), source.requestedProfileId(), - source.subjectRdns(), source.subjectAlternativeNames(), source.subjectAlternativeNamePresent(), - attributes); + source.publicKeyInfo(), source.requestedValidity(), source.requestedProfileId(), source.subjectRdns(), + source.subjectAlternativeNames(), source.subjectAlternativeNamePresent(), attributes); } private static CredentialIssuerBackend countingBackend(CredentialIssuerBackend delegate, @@ -397,7 +389,8 @@ final class H7EndEntityCsrRejectionE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; diff --git a/pki/src/test/java/zeroecho/pki/e2e/PkiCoreE2eTest.java b/pki/src/test/java/zeroecho/pki/e2e/PkiCoreE2eTest.java index 7e6ec0b..a711a6d 100644 --- a/pki/src/test/java/zeroecho/pki/e2e/PkiCoreE2eTest.java +++ b/pki/src/test/java/zeroecho/pki/e2e/PkiCoreE2eTest.java @@ -112,8 +112,7 @@ public final class PkiCoreE2eTest { @Test void nonUsableAndIndeterminateCredentialsFailClosedAcrossReachableTrustPaths(@TempDir Path tempDir) throws Exception { - assertAll( - () -> assertRejectedAcrossTrustPaths(tempDir.resolve("held"), EffectiveCredentialStatus.HELD, false), + assertAll(() -> assertRejectedAcrossTrustPaths(tempDir.resolve("held"), EffectiveCredentialStatus.HELD, false), () -> assertRejectedAcrossTrustPaths(tempDir.resolve("expired"), EffectiveCredentialStatus.EXPIRED, false), () -> assertRejectedAcrossTrustPaths(tempDir.resolve("not-yet-valid"), @@ -124,8 +123,7 @@ public final class PkiCoreE2eTest { } @Test - void everyIssuerPathSkipsEarlierUnusableCredentialAndSelectsLaterUsable(@TempDir Path tempDir) - throws Exception { + void everyIssuerPathSkipsEarlierUnusableCredentialAndSelectsLaterUsable(@TempDir Path tempDir) throws Exception { KeyPair rootKey = genRsa(); KeyPair intermediateKey = genRsa(); KeyPair nextIntermediateKey = genRsa(); @@ -148,15 +146,15 @@ public final class PkiCoreE2eTest { List resolved = new ArrayList<>(); EffectiveCredentialStatusResolver resolver = scriptedResolver(credential -> { resolved.add(credential.credentialId()); - return credential.credentialId().equals(unusable.credentialId()) - ? EffectiveCredentialStatus.HELD : EffectiveCredentialStatus.USABLE; + return credential.credentialId().equals(unusable.credentialId()) ? EffectiveCredentialStatus.HELD + : EffectiveCredentialStatus.USABLE; }, false); CountingIssuerBackend backend = new CountingIssuerBackend(runtime.issuerBackend()); IssuanceService issuance = runtime.issuanceService(backend, resolver); CaService caService = runtime.caService(backend, resolver); StatusObjectService statusService = runtime.statusObjectService(resolver); - ParsedCertificationRequest leafRequest = runtime.certificationRequestService().parse( - new CertificationRequest(runtime.framework().formatId(), + ParsedCertificationRequest leafRequest = runtime.certificationRequestService() + .parse(new CertificationRequest(runtime.framework().formatId(), new EncodedObject(Encoding.DER, makeCsr(leafKey, "CN=Matrix Leaf").getEncoded()))); issuance.issueEndEntity(new IssueEndEntityCommand(rootCaId, leafRequest, "default", Optional.empty())); @@ -164,8 +162,8 @@ public final class PkiCoreE2eTest { resolved.clear(); PkiId intermediateCaId = caService.createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootCaId, new SubjectRef("CN=Matrix Intermediate"), "intermediate-ca", - Optional.of(intermediateKeyRef), emptyAttributes())); + runtime.framework().formatId(), rootCaId, new SubjectRef("CN=Matrix Intermediate"), + "intermediate-ca", Optional.of(intermediateKeyRef), emptyAttributes())); assertEquals(List.of(unusable.credentialId(), usable.credentialId()), List.copyOf(resolved)); resolved.clear(); @@ -211,9 +209,8 @@ public final class PkiCoreE2eTest { CertificationRequest request = new CertificationRequest(runtime.framework().formatId(), new EncodedObject(Encoding.DER, makeCsr(leafKey, "CN=H6 Leaf").getEncoded())); ParsedCertificationRequest parsed = runtime.certificationRequestService().parse(request); - PkiException endEntityFailure = assertThrows(PkiException.class, - () -> runtime.issuanceService().issueEndEntity(new IssueEndEntityCommand(rootCaId, parsed, - "default", Optional.empty()))); + PkiException endEntityFailure = assertThrows(PkiException.class, () -> runtime.issuanceService() + .issueEndEntity(new IssueEndEntityCommand(rootCaId, parsed, "default", Optional.empty()))); assertTrue(endEntityFailure.getMessage().contains("ISSUER_CREDENTIAL_UNAVAILABLE")); assertThrows(PkiException.class, @@ -223,14 +220,13 @@ public final class PkiCoreE2eTest { Optional.of(nextIntermediateKeyRef), emptyAttributes()))); assertThrows(PkiException.class, () -> runtime.caService().issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, - intermediateCaId, "intermediate-ca", Optional.empty(), emptyAttributes()))); + new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, intermediateCaId, + "intermediate-ca", Optional.empty(), emptyAttributes()))); assertThrows(PkiException.class, () -> runtime.statusObjectService().generate(new StatusObjectGenerateCommand(rootCaId, StatusObjectType.CRL, runtime.framework().formatId(), emptyAttributes()))); - assertThrows(PkiException.class, - () -> runtime.issuanceService().buildBundle( - new BundleCommand(rootCredentialId, Optional.empty(), Optional.empty()))); + assertThrows(PkiException.class, () -> runtime.issuanceService() + .buildBundle(new BundleCommand(rootCredentialId, Optional.empty(), Optional.empty()))); assertEquals(submissionsBeforeRejections, runtime.submittedSignCount()); assertTrue(runtime.store().getCredential(rootCredentialId).isPresent()); @@ -279,8 +275,8 @@ public final class PkiCoreE2eTest { reqSvc.store(parsed, RequestStorePolicy.STORE_ALWAYS); - CredentialBundle bundle = issSvc.issueEndEntity(new IssueEndEntityCommand(rootCaId, parsed, "default", - Optional.empty())); + CredentialBundle bundle = issSvc + .issueEndEntity(new IssueEndEntityCommand(rootCaId, parsed, "default", Optional.empty())); assertNotNull(bundle); System.out.println("...issuedCredentialId=" + bundle.credential().credentialId().value()); @@ -319,40 +315,38 @@ public final class PkiCoreE2eTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, tempDir.resolve("bus.log"), keys)) { PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Rejected Root"), "root-ca", Optional.of(rootKeyRef), emptyAttributes())); - PkiId intermediateCaId = runtime.caService().createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootCaId, new SubjectRef("CN=Rejected Intermediate"), "intermediate-ca", - Optional.of(intermediateKeyRef), emptyAttributes())); + PkiId intermediateCaId = runtime.caService() + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootCaId, + new SubjectRef("CN=Rejected Intermediate"), "intermediate-ca", + Optional.of(intermediateKeyRef), emptyAttributes())); Credential rootCredential = runtime.caService().getCa(rootCaId).caCredentials().get(0); EffectiveCredentialStatusResolver resolver = scriptedResolver(credential -> status, resolutionFailure); CountingIssuerBackend backend = new CountingIssuerBackend(runtime.issuerBackend()); IssuanceService issuance = runtime.issuanceService(backend, resolver); CaService caService = runtime.caService(backend, resolver); StatusObjectService statusService = runtime.statusObjectService(resolver); - ParsedCertificationRequest leafRequest = runtime.certificationRequestService().parse( - new CertificationRequest(runtime.framework().formatId(), + ParsedCertificationRequest leafRequest = runtime.certificationRequestService() + .parse(new CertificationRequest(runtime.framework().formatId(), new EncodedObject(Encoding.DER, makeCsr(leafKey, "CN=Rejected Leaf").getEncoded()))); int signCount = runtime.submittedSignCount(); int caCount = runtime.store().listCas().size(); int statusCount = runtime.store().listStatusObjects(rootCaId).size(); int intermediateCredentialCount = runtime.caService().getCa(intermediateCaId).caCredentials().size(); - assertThrows(PkiException.class, - () -> issuance.issueEndEntity(new IssueEndEntityCommand(rootCaId, leafRequest, "default", - Optional.empty()))); + assertThrows(PkiException.class, () -> issuance + .issueEndEntity(new IssueEndEntityCommand(rootCaId, leafRequest, "default", Optional.empty()))); assertThrows(PkiException.class, () -> caService.createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootCaId, new SubjectRef("CN=Rejected Next"), "intermediate-ca", Optional.of(nextIntermediateKeyRef), emptyAttributes()))); assertThrows(PkiException.class, - () -> caService.issueIntermediateCertificate(new IntermediateCertIssueCommand( - runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", Optional.empty(), - emptyAttributes()))); - assertThrows(PkiException.class, - () -> statusService.generate(new StatusObjectGenerateCommand(rootCaId, StatusObjectType.CRL, - runtime.framework().formatId(), emptyAttributes()))); - assertThrows(PkiException.class, - () -> issuance.buildBundle(new BundleCommand(rootCredential.credentialId(), Optional.empty(), - Optional.empty()))); + () -> caService.issueIntermediateCertificate( + new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, intermediateCaId, + "intermediate-ca", Optional.empty(), emptyAttributes()))); + assertThrows(PkiException.class, () -> statusService.generate(new StatusObjectGenerateCommand(rootCaId, + StatusObjectType.CRL, runtime.framework().formatId(), emptyAttributes()))); + assertThrows(PkiException.class, () -> issuance + .buildBundle(new BundleCommand(rootCredential.credentialId(), Optional.empty(), Optional.empty()))); assertEquals(0, backend.endEntityCalls.get()); assertEquals(0, backend.intermediateCalls.get()); @@ -396,8 +390,7 @@ public final class PkiCoreE2eTest { private static Credential copyWithId(Credential source, PkiId id) { return new Credential(id, source.formatId(), source.issuerRef(), source.subjectRef(), source.validity(), source.serialOrUniqueId(), source.publicKeyId(), source.profileBinding(), source.status(), - source.encoded(), - source.attributes()); + source.encoded(), source.attributes()); } private static final class CountingIssuerBackend implements CredentialIssuerBackend { @@ -412,13 +405,15 @@ public final class PkiCoreE2eTest { } @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, + KeyRef issuerKeyRef, BigInteger serial) { endEntityCalls.incrementAndGet(); return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { intermediateCalls.incrementAndGet(); return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } diff --git a/pki/src/test/java/zeroecho/pki/e2e/PkiProofGateE2eTest.java b/pki/src/test/java/zeroecho/pki/e2e/PkiProofGateE2eTest.java index a104282..15d2370 100644 --- a/pki/src/test/java/zeroecho/pki/e2e/PkiProofGateE2eTest.java +++ b/pki/src/test/java/zeroecho/pki/e2e/PkiProofGateE2eTest.java @@ -146,11 +146,9 @@ final class PkiProofGateE2eTest { .noneMatch(method -> method.getName().equals("issuerBackend"))); Method endEntity = java.util.Arrays.stream(CredentialIssuerBackend.class.getMethods()) - .filter(method -> method.getName().equals("issueEndEntity")) - .findFirst().orElseThrow(); + .filter(method -> method.getName().equals("issueEndEntity")).findFirst().orElseThrow(); Method intermediate = java.util.Arrays.stream(CredentialIssuerBackend.class.getMethods()) - .filter(method -> method.getName().equals("issueIntermediateCertificate")) - .findFirst().orElseThrow(); + .filter(method -> method.getName().equals("issueIntermediateCertificate")).findFirst().orElseThrow(); assertArrayEquals(new Class[] { ValidatedCertificateRequest.class, EncodedObject.class, KeyRef.class, BigInteger.class }, endEntity.getParameterTypes()); assertArrayEquals(new Class[] { ValidatedCaCertificateRequest.class, EncodedObject.class, KeyRef.class }, @@ -179,9 +177,8 @@ final class PkiProofGateE2eTest { Map keys = Map.of(rootKeyRef, rootKey, subjectKeyRef, subjectKey); try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, tempDir.resolve("bus.log"), keys)) { CountingIssuerBackend counting = new CountingIssuerBackend(runtime.issuerBackend()); - DefaultIssuanceService issuance = new DefaultIssuanceService(runtime.store(), runtime.framework(), - counting, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), - Clock.systemUTC()); + DefaultIssuanceService issuance = new DefaultIssuanceService(runtime.store(), runtime.framework(), counting, + runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); ParsedCertificationRequest valid = parse(runtime, makeCsr(subjectKey, subjectKey, "CN=Leaf")); assertThrows(PkiException.class, @@ -191,8 +188,8 @@ final class PkiProofGateE2eTest { PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); - CredentialBundle issued = issuance.issueEndEntity(new IssueEndEntityCommand(rootCaId, valid, "default", - Optional.empty())); + CredentialBundle issued = issuance + .issueEndEntity(new IssueEndEntityCommand(rootCaId, valid, "default", Optional.empty())); assertEquals(1, counting.endEntityCalls.get()); assertArrayEquals(subjectKey.getPublic().getEncoded(), new X509CertificateHolder(issued.credential().encoded().bytes()).getSubjectPublicKeyInfo() @@ -211,8 +208,8 @@ final class PkiProofGateE2eTest { new SimpleAttributeSet()))); assertEquals(1, counting.intermediateCalls.get()); - System.out.println("...backend calls=" + counting.endEntityCalls.get() + "/" - + counting.intermediateCalls.get()); + System.out.println( + "...backend calls=" + counting.endEntityCalls.get() + "/" + counting.intermediateCalls.get()); } System.out.println("proofGatesAloneCanReachIssuerBackend...ok"); @@ -224,9 +221,8 @@ final class PkiProofGateE2eTest { try (PkiTestRuntime runtime = PkiTestRuntime.create(tempDir, tempDir.resolve("bus.log"), Map.of())) { CountingIssuerBackend counting = new CountingIssuerBackend(runtime.issuerBackend()); - DefaultIssuanceService issuance = new DefaultIssuanceService(runtime.store(), runtime.framework(), - counting, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), - Clock.systemUTC()); + DefaultIssuanceService issuance = new DefaultIssuanceService(runtime.store(), runtime.framework(), counting, + runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); CaService caService = runtime.caService(counting); int auditCount = runtime.auditSink().snapshot().size(); ParsedCertificationRequest request = new ParsedCertificationRequest(new PkiId("csr:unsupported"), @@ -242,16 +238,13 @@ final class PkiProofGateE2eTest { assertThrows(PkiException.class, () -> issuance.renew(new RenewCommand(objectId, Optional.empty(), new SimpleAttributeSet()))); assertThrows(PkiException.class, - () -> issuance.replace(new ReplaceCommand(objectId, request, "default", - new SimpleAttributeSet()))); + () -> issuance.replace(new ReplaceCommand(objectId, request, "default", new SimpleAttributeSet()))); assertThrows(PkiException.class, () -> issuance.reissue(new ReissueCommand(objectId, new SimpleAttributeSet()))); - assertThrows(PkiException.class, - () -> caService.rolloverCaCertificate(new CaRolloverCommand(objectId, Optional.empty(), - Optional.empty(), new SimpleAttributeSet()))); - assertThrows(PkiException.class, - () -> caService.rotateCaKey(new CaKeyRotationCommand(objectId, Optional.empty(), - Optional.empty(), new SimpleAttributeSet()))); + assertThrows(PkiException.class, () -> caService.rolloverCaCertificate( + new CaRolloverCommand(objectId, Optional.empty(), Optional.empty(), new SimpleAttributeSet()))); + assertThrows(PkiException.class, () -> caService.rotateCaKey( + new CaKeyRotationCommand(objectId, Optional.empty(), Optional.empty(), new SimpleAttributeSet()))); long filesAfter; try (java.util.stream.Stream files = Files.walk(tempDir)) { @@ -293,12 +286,10 @@ final class PkiProofGateE2eTest { assertEquals(ProofOfPossessionStatus.VERIFIED, pssProof.status()); org.bouncycastle.asn1.pkcs.CertificationRequest original = validCsr.toASN1Structure(); CertificationRequestInfo requestInfo = original.getCertificationRequestInfo(); - org.bouncycastle.asn1.pkcs.CertificationRequest unknownAlgorithm = - new org.bouncycastle.asn1.pkcs.CertificationRequest(requestInfo, - new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.3.4.5.6.7")), - original.getSignature()); - ParsedCertificationRequest unsupported = parse(runtime, - new PKCS10CertificationRequest(unknownAlgorithm)); + org.bouncycastle.asn1.pkcs.CertificationRequest unknownAlgorithm = new org.bouncycastle.asn1.pkcs.CertificationRequest( + requestInfo, new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.3.4.5.6.7")), + original.getSignature()); + ParsedCertificationRequest unsupported = parse(runtime, new PKCS10CertificationRequest(unknownAlgorithm)); ProofOfPossessionResult unsupportedProof = new BcX509ProofOfPossessionVerifier().verify(unsupported, new zeroecho.pki.api.issuance.VerificationPolicy(true, Optional.empty())); assertEquals(ProofOfPossessionStatus.FAILED, unsupportedProof.status()); @@ -311,21 +302,20 @@ final class PkiProofGateE2eTest { ParsedCertificationRequest tamperedParsed = parse(runtime, new PKCS10CertificationRequest(tampered)); assertRejected(runtime, rootCaId, tamperedParsed, "PROOF_FAILED"); - ParsedCertificationRequest wrongSigner = parse(runtime, - makeCsr(subjectKey, otherKey, "CN=Subject")); + ParsedCertificationRequest wrongSigner = parse(runtime, makeCsr(subjectKey, otherKey, "CN=Subject")); assertRejected(runtime, rootCaId, wrongSigner, "PROOF_FAILED"); assertRejected(runtime, rootCaId, new ParsedCertificationRequest(new PkiId("csr:substituted"), valid.formatId(), valid.subjectRef(), valid.publicKeyInfo(), valid.requestedValidity(), valid.requestedProfileId(), - valid.subjectRdns(), valid.subjectAlternativeNames(), - valid.subjectAlternativeNamePresent(), valid.attributes()), + valid.subjectRdns(), valid.subjectAlternativeNames(), valid.subjectAlternativeNamePresent(), + valid.attributes()), "REQUEST_ID_MISMATCH"); assertRejected(runtime, rootCaId, new ParsedCertificationRequest(valid.requestId(), valid.formatId(), new SubjectRef("CN=Other"), valid.publicKeyInfo(), valid.requestedValidity(), valid.requestedProfileId(), - valid.subjectRdns(), valid.subjectAlternativeNames(), - valid.subjectAlternativeNamePresent(), valid.attributes()), + valid.subjectRdns(), valid.subjectAlternativeNames(), valid.subjectAlternativeNamePresent(), + valid.attributes()), "SUBJECT_MISMATCH"); assertRejected(runtime, rootCaId, new ParsedCertificationRequest(valid.requestId(), valid.formatId(), valid.subjectRef(), @@ -336,8 +326,8 @@ final class PkiProofGateE2eTest { assertRejected(runtime, rootCaId, new ParsedCertificationRequest(valid.requestId(), new FormatId("unsupported"), valid.subjectRef(), valid.publicKeyInfo(), valid.requestedValidity(), valid.requestedProfileId(), - valid.subjectRdns(), valid.subjectAlternativeNames(), - valid.subjectAlternativeNamePresent(), valid.attributes()), + valid.subjectRdns(), valid.subjectAlternativeNames(), valid.subjectAlternativeNamePresent(), + valid.attributes()), "FORMAT_UNSUPPORTED"); byte[] maximum = new byte[1024 * 1024]; System.arraycopy(csrDer(valid), 0, maximum, 0, csrDer(valid).length); @@ -363,10 +353,8 @@ final class PkiProofGateE2eTest { KeyPair subjectKey = generateRsa(); KeyRef rootKeyRef = new KeyRef("kref:v1:keyring:test:root"); KeyRef subjectKeyRef = new KeyRef("kref:v1:keyring:test:subject"); - for (ProofOfPossessionStatus status : new ProofOfPossessionStatus[] { - ProofOfPossessionStatus.NOT_PRESENT, - ProofOfPossessionStatus.NOT_SUPPORTED, - ProofOfPossessionStatus.FAILED }) { + for (ProofOfPossessionStatus status : new ProofOfPossessionStatus[] { ProofOfPossessionStatus.NOT_PRESENT, + ProofOfPossessionStatus.NOT_SUPPORTED, ProofOfPossessionStatus.FAILED }) { AtomicBoolean required = new AtomicBoolean(); Path caseDir = tempDir.resolve(status.name()); try (PkiTestRuntime runtime = PkiTestRuntime.create(caseDir, caseDir.resolve("bus.log"), @@ -423,8 +411,7 @@ final class PkiProofGateE2eTest { AttributeSet hostileOverrides = SimpleAttributeSet.builder() .put(BcX509Attributes.ISSUER_CERT_DER, new AttributeValue.BytesValue(new byte[] { 0x01 })) - .put(BcX509Attributes.ISSUER_KEYREF, new AttributeValue.StringValue("attacker-key")) - .build(); + .put(BcX509Attributes.ISSUER_KEYREF, new AttributeValue.StringValue("attacker-key")).build(); CredentialBundle bundle = runtime.issuanceService() .issueEndEntity(new IssueEndEntityCommand(rootCaId, parsed, "default", Optional.empty())); @@ -450,8 +437,7 @@ final class PkiProofGateE2eTest { new BcX509ProofOfPossessionVerifier())) { assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet()))); + new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet()))); assertTrue(runtime.store().listCas().isEmpty()); assertTrue(runtime.store().listWorkflowStates().isEmpty()); assertEquals(1, runtime.submittedSignCount()); @@ -463,8 +449,7 @@ final class PkiProofGateE2eTest { Map.of(rootKeyRef, expectedRoot.getPublic()), new BcX509ProofOfPossessionVerifier())) { assertThrows(PkiException.class, () -> runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), - new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet()))); + new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet()))); assertTrue(runtime.store().listWorkflowStates().isEmpty()); assertEquals(1, runtime.submittedSignCount()); } @@ -482,8 +467,8 @@ final class PkiProofGateE2eTest { AttributeSet approved = new SimpleAttributeSet(); PkiId intermediateCaId = runtime.caService() .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootCaId, - new SubjectRef("CN=Intermediate"), "intermediate-ca", - Optional.of(intermediateKeyRef), approved)); + new SubjectRef("CN=Intermediate"), "intermediate-ca", Optional.of(intermediateKeyRef), + approved)); Credential first = runtime.caService().getCa(intermediateCaId).caCredentials().get(0); X509CertificateHolder firstHolder = new X509CertificateHolder(first.encoded().bytes()); @@ -524,15 +509,14 @@ final class PkiProofGateE2eTest { runtime.replaceManagedKey(rootKeyRef, replacementRootKey); assertThrows(PkiException.class, () -> runtime.caService() - .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), - rootCaId, new SubjectRef("CN=Intermediate"), "intermediate-ca", + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), rootCaId, + new SubjectRef("CN=Intermediate"), "intermediate-ca", Optional.of(intermediateKeyRef), new SimpleAttributeSet()))); assertEquals(4, runtime.submittedSignCount()); assertEquals(1, runtime.store().listCas().size()); assertTrue(runtime.store().listWorkflowStates().isEmpty()); - assertEquals("ISSUER_MANAGED_KEY_MISMATCH", - runtime.auditSink().snapshot().get(runtime.auditSink().snapshot().size() - 1).details() - .get("code")); + assertEquals("ISSUER_MANAGED_KEY_MISMATCH", runtime.auditSink().snapshot() + .get(runtime.auditSink().snapshot().size() - 1).details().get("code")); } Path additionalDir = tempDir.resolve("additional-issuer-mismatch"); @@ -547,9 +531,10 @@ final class PkiProofGateE2eTest { assertEquals(5, runtime.submittedSignCount()); runtime.replaceManagedKey(rootKeyRef, replacementRootKey); assertThrows(PkiException.class, - () -> runtime.caService().issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, - intermediateCaId, "intermediate-ca", Optional.empty(), new SimpleAttributeSet()))); + () -> runtime.caService() + .issueIntermediateCertificate(new IntermediateCertIssueCommand( + runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", + Optional.empty(), new SimpleAttributeSet()))); assertEquals(7, runtime.submittedSignCount()); assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size()); assertTrue(runtime.store().listWorkflowStates().isEmpty()); @@ -572,15 +557,14 @@ final class PkiProofGateE2eTest { .bytes().clone(); ParsedCertificationRequest leaf = parse(source, makeCsr(subjectKey, subjectKey, "CN=Leaf")); leafCertificate = source.issuanceService() - .issueEndEntity(new IssueEndEntityCommand(rootCaId, leaf, "default", Optional.empty())) - .credential().encoded().bytes().clone(); + .issueEndEntity(new IssueEndEntityCommand(rootCaId, leaf, "default", Optional.empty())).credential() + .encoded().bytes().clone(); } Path importDir = tempDir.resolve("import-mismatch"); try (PkiTestRuntime target = PkiTestRuntime.create(importDir, importDir.resolve("bus.log"), Map.of(rootKeyRef, replacementRootKey))) { CaImportCommand command = new CaImportCommand(target.framework().formatId(), new SubjectRef("CN=Root"), - "root-ca", rootKeyRef, new EncodedObject(Encoding.DER, rootCertificate), - new SimpleAttributeSet()); + "root-ca", rootKeyRef, new EncodedObject(Encoding.DER, rootCertificate), new SimpleAttributeSet()); assertThrows(PkiException.class, () -> target.caService().importRoot(command)); assertTrue(target.store().listCas().isEmpty()); assertTrue(target.store().listWorkflowStates().isEmpty()); @@ -594,9 +578,10 @@ final class PkiProofGateE2eTest { try (PkiTestRuntime target = PkiTestRuntime.create(mutationDir, mutationDir.resolve("bus.log"), Map.of(rootKeyRef, rootKey))) { target.onPublicKeyResolve(() -> callerOwnedCertificate[callerOwnedCertificate.length - 1] ^= 0x01); - PkiId importedCaId = target.caService().importRoot(new CaImportCommand(target.framework().formatId(), - new SubjectRef("CN=Root"), "root-ca", rootKeyRef, - new EncodedObject(Encoding.DER, callerOwnedCertificate), new SimpleAttributeSet())); + PkiId importedCaId = target.caService() + .importRoot(new CaImportCommand(target.framework().formatId(), new SubjectRef("CN=Root"), "root-ca", + rootKeyRef, new EncodedObject(Encoding.DER, callerOwnedCertificate), + new SimpleAttributeSet())); assertTrue(target.caService().getCa(importedCaId).caCredentials().get(0) .profileBinding() instanceof CaProfileBinding); assertArrayEquals(expectedImportedCertificate, @@ -616,19 +601,18 @@ final class PkiProofGateE2eTest { try (PkiTestRuntime signingFailure = PkiTestRuntime.create(signingFailureDir, signingFailureDir.resolve("bus.log"), Map.of())) { assertThrows(PkiException.class, - () -> signingFailure.caService().createRoot(new CaCreateCommand( - signingFailure.framework().formatId(), - new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), - new SimpleAttributeSet()))); + () -> signingFailure.caService() + .createRoot(new CaCreateCommand(signingFailure.framework().formatId(), + new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), + new SimpleAttributeSet()))); assertTrue(signingFailure.store().listCas().isEmpty()); assertTrue(signingFailure.store().listWorkflowStates().isEmpty()); assertTrue(signingFailure.store().listSignRecords().isEmpty()); assertTrue(signingFailure.store().listPublicationRecords().isEmpty()); assertFalse(signingFailure.hasRunningSignatureOperations()); assertEquals(0, signingFailure.submittedSignCount()); - assertEquals("MANAGED_KEY_UNAVAILABLE", - signingFailure.auditSink().snapshot() - .get(signingFailure.auditSink().snapshot().size() - 1).details().get("code")); + assertEquals("MANAGED_KEY_UNAVAILABLE", signingFailure.auditSink().snapshot() + .get(signingFailure.auditSink().snapshot().size() - 1).details().get("code")); } System.out.println("caIssuerImportAndWorkflowFailuresLeaveNoDurableSideEffects...ok"); @@ -643,8 +627,8 @@ final class PkiProofGateE2eTest { assertThrows(PkiException.class, () -> runtime.caService().importRoot(command)); assertTrue(runtime.store().listCas().isEmpty()); assertEquals(0, runtime.submittedSignCount()); - assertEquals("ROOT_CREDENTIAL_INVALID", - runtime.auditSink().snapshot().get(runtime.auditSink().snapshot().size() - 1).details().get("code")); + assertEquals("ROOT_CREDENTIAL_INVALID", runtime.auditSink().snapshot() + .get(runtime.auditSink().snapshot().size() - 1).details().get("code")); } } @@ -661,33 +645,35 @@ final class PkiProofGateE2eTest { PkiId rootCaId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Root"), "root-ca", Optional.of(rootKeyRef), new SimpleAttributeSet())); ParsedCertificationRequest subject = parse(runtime, makeCsr(subjectKey, subjectKey, "CN=Subject")); - ParsedCertificationRequest substitute = parse(runtime, - makeCsr(subjectKey, subjectKey, "CN=Substitute")); + ParsedCertificationRequest substitute = parse(runtime, makeCsr(subjectKey, subjectKey, "CN=Substitute")); AtomicReference substitutedBundle = new AtomicReference<>(); CredentialIssuerBackend delegateBackend = runtime.issuerBackend(); CredentialIssuerBackend throwingBackend = new CredentialIssuerBackend() { @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { throw new IllegalStateException("DO_NOT_LOG_SIGNATURE_SENTINEL"); } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { throw new IllegalStateException("DO_NOT_LOG_SIGNATURE_SENTINEL"); } }; DefaultIssuanceService throwingBackendService = new DefaultIssuanceService(runtime.store(), runtime.framework(), throwingBackend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); - PkiException backendRejection = assertThrows(PkiException.class, - () -> throwingBackendService.issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty()))); + PkiException backendRejection = assertThrows(PkiException.class, () -> throwingBackendService + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); assertThrowableRedacted(backendRejection, "DO_NOT_LOG_SIGNATURE_SENTINEL"); CredentialIssuerBackend maliciousBackend = new CredentialIssuerBackend() { @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - CredentialBundle bundle = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + CredentialBundle bundle = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, + serial); Credential raw = bundle.credential(); Credential forgedMetadata = new Credential(raw.credentialId(), raw.formatId(), raw.issuerRef(), substitute.subjectRef(), raw.validity(), raw.serialOrUniqueId(), raw.publicKeyId(), @@ -698,20 +684,20 @@ final class PkiProofGateE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; DefaultIssuanceService service = new DefaultIssuanceService(runtime.store(), runtime.framework(), maliciousBackend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); - assertThrows(PkiException.class, - () -> service.issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", - Optional.empty()))); + assertThrows(PkiException.class, () -> service + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); Credential substitutedCredential = substitutedBundle.get().credential(); assertTrue(runtime.store().getCredential(substitutedCredential.credentialId()).isEmpty()); - assertEquals("BACKEND_CREDENTIAL_MISMATCH", - runtime.auditSink().snapshot().get(runtime.auditSink().snapshot().size() - 1).details().get("code")); + assertEquals("BACKEND_CREDENTIAL_MISMATCH", runtime.auditSink().snapshot() + .get(runtime.auditSink().snapshot().size() - 1).details().get("code")); AtomicReference wrongBindingCredential = new AtomicReference<>(); CredentialIssuerBackend wrongEndEntityBindingBackend = new CredentialIssuerBackend() { @@ -721,78 +707,81 @@ final class PkiProofGateE2eTest { CredentialBundle rawBundle = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); Credential raw = rawBundle.credential(); - Credential wrongBinding = copyWithBinding(raw, - new CaProfileBinding(candidate.profileReference())); + Credential wrongBinding = copyWithBinding(raw, new CaProfileBinding(candidate.profileReference())); wrongBindingCredential.set(wrongBinding); return new CredentialBundle(wrongBinding, rawBundle.supportingObjects()); } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } }; DefaultIssuanceService wrongBindingService = new DefaultIssuanceService(runtime.store(), runtime.framework(), wrongEndEntityBindingBackend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); - PkiException wrongBinding = assertThrows(PkiException.class, - () -> wrongBindingService.issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty()))); + PkiException wrongBinding = assertThrows(PkiException.class, () -> wrongBindingService + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); assertTrue(wrongBinding.getMessage().contains("CREDENTIAL_PROFILE_BINDING_MISMATCH")); assertTrue(runtime.store().getCredential(wrongBindingCredential.get().credentialId()).isEmpty()); CredentialIssuerBackend invalidSignatureBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - CredentialBundle rawBundle = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - Credential raw = rawBundle.credential(); - byte[] invalid = raw.encoded().bytes().clone(); - invalid[invalid.length - 1] ^= 0x01; - Credential invalidCredential = new Credential(raw.credentialId(), raw.formatId(), - raw.issuerRef(), raw.subjectRef(), raw.validity(), raw.serialOrUniqueId(), - raw.publicKeyId(), raw.profileBinding(), raw.status(), - new EncodedObject(Encoding.DER, invalid), raw.attributes()); - return new CredentialBundle(invalidCredential, rawBundle.supportingObjects()); - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + CredentialBundle rawBundle = delegateBackend.issueEndEntity(candidate, issuerCertificate, + issuerKeyRef, serial); + Credential raw = rawBundle.credential(); + byte[] invalid = raw.encoded().bytes().clone(); + invalid[invalid.length - 1] ^= 0x01; + Credential invalidCredential = new Credential(raw.credentialId(), raw.formatId(), raw.issuerRef(), + raw.subjectRef(), raw.validity(), raw.serialOrUniqueId(), raw.publicKeyId(), + raw.profileBinding(), raw.status(), new EncodedObject(Encoding.DER, invalid), + raw.attributes()); + return new CredentialBundle(invalidCredential, rawBundle.supportingObjects()); + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - } - }; + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + } + }; DefaultIssuanceService invalidSignatureService = new DefaultIssuanceService(runtime.store(), runtime.framework(), invalidSignatureBackend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); - assertThrows(PkiException.class, - () -> invalidSignatureService.issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty()))); + assertThrows(PkiException.class, () -> invalidSignatureService + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); AtomicReference rawBundle = new AtomicReference<>(); CredentialIssuerBackend mutableBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - CredentialBundle raw = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - rawBundle.set(raw); - return raw; - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + CredentialBundle raw = delegateBackend.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, + serial); + rawBundle.set(raw); + return raw; + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - } - }; - DefaultIssuanceService snapshotService = new DefaultIssuanceService(runtime.store(), - runtime.framework(), mutableBackend, runtime.auditSink(), runtime.statusResolver(), - runtime.profileService(), Clock.systemUTC()); - CredentialBundle returned = snapshotService.issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty())); + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + return delegateBackend.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + } + }; + DefaultIssuanceService snapshotService = new DefaultIssuanceService(runtime.store(), runtime.framework(), + mutableBackend, runtime.auditSink(), runtime.statusResolver(), runtime.profileService(), + Clock.systemUTC()); + CredentialBundle returned = snapshotService + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty())); byte[] expectedLeaf = returned.credential().encoded().bytes().clone(); rawBundle.get().credential().encoded().bytes()[0] ^= 0x01; rawBundle.get().supportingObjects().get(0).bytes()[0] ^= 0x01; assertArrayEquals(expectedLeaf, returned.credential().encoded().bytes()); - assertArrayEquals(expectedLeaf, - runtime.store().getCredential(returned.credential().credentialId()).orElseThrow().encoded() - .bytes()); + assertArrayEquals(expectedLeaf, runtime.store().getCredential(returned.credential().credentialId()) + .orElseThrow().encoded().bytes()); CaRecord root = runtime.caService().getCa(rootCaId); Credential original = root.caCredentials().get(0); @@ -802,9 +791,8 @@ final class PkiProofGateE2eTest { runtime.store().putCa(new CaRecord(root.caId(), root.kind(), root.state(), root.issuerKeyRef(), root.subjectRef(), List.of(revoked))); int before = runtime.submittedSignCount(); - assertThrows(PkiException.class, - () -> runtime.issuanceService().issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty()))); + assertThrows(PkiException.class, () -> runtime.issuanceService() + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); assertEquals(before, runtime.submittedSignCount()); Validity expiredValidity = new Validity(Instant.now().minus(Duration.ofDays(2)), @@ -814,9 +802,8 @@ final class PkiProofGateE2eTest { original.profileBinding(), CredentialStatus.ISSUED, original.encoded(), original.attributes()); runtime.store().putCa(new CaRecord(root.caId(), root.kind(), root.state(), root.issuerKeyRef(), root.subjectRef(), List.of(expired))); - assertThrows(PkiException.class, - () -> runtime.issuanceService().issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, - "default", Optional.empty()))); + assertThrows(PkiException.class, () -> runtime.issuanceService() + .issueEndEntity(new IssueEndEntityCommand(rootCaId, subject, "default", Optional.empty()))); assertEquals(before, runtime.submittedSignCount()); runtime.store().putCa(new CaRecord(root.caId(), root.kind(), root.state(), root.issuerKeyRef(), @@ -847,9 +834,8 @@ final class PkiProofGateE2eTest { runtime.issuerBackend(), event -> { throw new IllegalStateException("DO_NOT_LOG_PAYLOAD_SENTINEL"); }, runtime.statusResolver(), runtime.profileService(), Clock.systemUTC()); - PkiException rejection = assertThrows(PkiException.class, - () -> failingAudit.issueEndEntity(new IssueEndEntityCommand(rootCaId, missing, "default", - Optional.empty()))); + PkiException rejection = assertThrows(PkiException.class, () -> failingAudit + .issueEndEntity(new IssueEndEntityCommand(rootCaId, missing, "default", Optional.empty()))); assertTrue(rejection.getMessage().contains("CSR_MISSING")); assertThrowableRedacted(rejection, "DO_NOT_LOG_PAYLOAD_SENTINEL"); } @@ -873,38 +859,39 @@ final class PkiProofGateE2eTest { CredentialIssuerBackend delegate = runtime.issuerBackend(); for (BindingVariantMutation mutation : BindingVariantMutation.values()) { AtomicReference produced = new AtomicReference<>(); - CaService wrongBindingService = runtime.caService(bindingMutationBackend(delegate, mutation, - produced)); + CaService wrongBindingService = runtime.caService(bindingMutationBackend(delegate, mutation, produced)); PkiException rejected = assertThrows(PkiException.class, - () -> wrongBindingService.createIntermediate(new IntermediateCreateCommand( - runtime.framework().formatId(), rootCaId, - new SubjectRef("CN=BindingRejectedIntermediate"), "intermediate-ca", - Optional.of(intermediateKeyRef), new SimpleAttributeSet())), mutation.name()); + () -> wrongBindingService + .createIntermediate(new IntermediateCreateCommand(runtime.framework().formatId(), + rootCaId, new SubjectRef("CN=BindingRejectedIntermediate"), "intermediate-ca", + Optional.of(intermediateKeyRef), new SimpleAttributeSet())), + mutation.name()); assertTrue(rejected.getMessage().contains("CREDENTIAL_PROFILE_BINDING_MISMATCH"), mutation.name()); assertEquals(1, runtime.store().listCas().size(), mutation.name()); if (produced.get() != null) { - assertTrue(runtime.store().getCredential(produced.get().credentialId()).isEmpty(), - mutation.name()); + assertTrue(runtime.store().getCredential(produced.get().credentialId()).isEmpty(), mutation.name()); } } CredentialIssuerBackend wrongKeyBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - return rebuildIntermediateIdentity(raw, rootKey, Optional.of(wrongKey.getPublic()), - Optional.empty()); - } - }; + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + return rebuildIntermediateIdentity(raw, rootKey, Optional.of(wrongKey.getPublic()), + Optional.empty()); + } + }; CaService wrongKeyService = runtime.caService(wrongKeyBackend); assertThrows(PkiException.class, - () -> wrongKeyService.createIntermediate(new IntermediateCreateCommand(runtime.framework() - .formatId(), rootCaId, new SubjectRef("CN=Intermediate"), "intermediate-ca", - Optional.of(intermediateKeyRef), new SimpleAttributeSet()))); + () -> wrongKeyService.createIntermediate(new IntermediateCreateCommand( + runtime.framework().formatId(), rootCaId, new SubjectRef("CN=Intermediate"), + "intermediate-ca", Optional.of(intermediateKeyRef), new SimpleAttributeSet()))); assertEquals(1, runtime.store().listCas().size()); PkiId intermediateCaId = runtime.caService() @@ -913,115 +900,111 @@ final class PkiProofGateE2eTest { new SimpleAttributeSet())); for (BindingVariantMutation mutation : BindingVariantMutation.values()) { AtomicReference produced = new AtomicReference<>(); - CaService wrongBindingService = runtime.caService(bindingMutationBackend(delegate, mutation, - produced)); + CaService wrongBindingService = runtime.caService(bindingMutationBackend(delegate, mutation, produced)); PkiException rejected = assertThrows(PkiException.class, () -> wrongBindingService.issueIntermediateCertificate(new IntermediateCertIssueCommand( runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", - Optional.empty(), new SimpleAttributeSet())), mutation.name()); - assertTrue(rejected.getMessage().contains("CREDENTIAL_PROFILE_BINDING_MISMATCH"), mutation.name()); - assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size(), + Optional.empty(), new SimpleAttributeSet())), mutation.name()); + assertTrue(rejected.getMessage().contains("CREDENTIAL_PROFILE_BINDING_MISMATCH"), mutation.name()); + assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size(), mutation.name()); if (produced.get() != null) { - assertTrue(runtime.store().getCredential(produced.get().credentialId()).isEmpty(), - mutation.name()); + assertTrue(runtime.store().getCredential(produced.get().credentialId()).isEmpty(), mutation.name()); } } CredentialIssuerBackend wrongSubjectBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - return rebuildIntermediateIdentity(raw, rootKey, Optional.empty(), - Optional.of("CN=WrongIntermediate")); - } - }; + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + return rebuildIntermediateIdentity(raw, rootKey, Optional.empty(), + Optional.of("CN=WrongIntermediate")); + } + }; CaService wrongSubjectService = runtime.caService(wrongSubjectBackend); assertThrows(PkiException.class, - () -> wrongSubjectService.issueIntermediateCertificate(new IntermediateCertIssueCommand( - runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", Optional.empty(), - new SimpleAttributeSet()))); + () -> wrongSubjectService.issueIntermediateCertificate( + new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, intermediateCaId, + "intermediate-ca", Optional.empty(), new SimpleAttributeSet()))); assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size()); CredentialIssuerBackend invalidSignatureBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - byte[] invalid = raw.encoded().bytes().clone(); - invalid[invalid.length - 1] ^= 0x01; - return new Credential(raw.credentialId(), raw.formatId(), raw.issuerRef(), - raw.subjectRef(), raw.validity(), raw.serialOrUniqueId(), raw.publicKeyId(), - raw.profileBinding(), raw.status(), new EncodedObject(Encoding.DER, invalid), - raw.attributes()); - } - }; + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + byte[] invalid = raw.encoded().bytes().clone(); + invalid[invalid.length - 1] ^= 0x01; + return new Credential(raw.credentialId(), raw.formatId(), raw.issuerRef(), raw.subjectRef(), + raw.validity(), raw.serialOrUniqueId(), raw.publicKeyId(), raw.profileBinding(), + raw.status(), new EncodedObject(Encoding.DER, invalid), raw.attributes()); + } + }; CaService invalidSignatureService = runtime.caService(invalidSignatureBackend); assertThrows(PkiException.class, - () -> invalidSignatureService.issueIntermediateCertificate(new IntermediateCertIssueCommand( - runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", Optional.empty(), - new SimpleAttributeSet()))); + () -> invalidSignatureService.issueIntermediateCertificate( + new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, intermediateCaId, + "intermediate-ca", Optional.empty(), new SimpleAttributeSet()))); assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size()); for (IntermediateExtensionVariant variant : IntermediateExtensionVariant.values()) { - CaService maliciousExtensionService = runtime.caService( - extensionVariantBackend(delegate, rootKey, variant)); + CaService maliciousExtensionService = runtime + .caService(extensionVariantBackend(delegate, rootKey, variant)); assertThrows(PkiException.class, - () -> maliciousExtensionService.issueIntermediateCertificate( - new IntermediateCertIssueCommand(runtime.framework().formatId(), rootCaId, - intermediateCaId, "intermediate-ca", Optional.empty(), new SimpleAttributeSet())), + () -> maliciousExtensionService.issueIntermediateCertificate(new IntermediateCertIssueCommand( + runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", + Optional.empty(), new SimpleAttributeSet())), variant.name()); assertEquals(1, runtime.caService().getCa(intermediateCaId).caCredentials().size(), variant.name()); } AtomicReference rawCredential = new AtomicReference<>(); CredentialIssuerBackend mutableBackend = new CredentialIssuerBackend() { - @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { - return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); - } + @Override + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); + } - @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); - rawCredential.set(raw); - return raw; - } - }; + @Override + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + rawCredential.set(raw); + return raw; + } + }; CaService snapshotService = runtime.caService(mutableBackend); - Credential returned = snapshotService.issueIntermediateCertificate(new IntermediateCertIssueCommand( - runtime.framework().formatId(), rootCaId, intermediateCaId, "intermediate-ca", Optional.empty(), - new SimpleAttributeSet())); + Credential returned = snapshotService + .issueIntermediateCertificate(new IntermediateCertIssueCommand(runtime.framework().formatId(), + rootCaId, intermediateCaId, "intermediate-ca", Optional.empty(), new SimpleAttributeSet())); byte[] expected = returned.encoded().bytes().clone(); rawCredential.get().encoded().bytes()[0] ^= 0x01; assertArrayEquals(expected, returned.encoded().bytes()); - assertArrayEquals(expected, runtime.store().getCredential(returned.credentialId()).orElseThrow() - .encoded().bytes()); + assertArrayEquals(expected, + runtime.store().getCredential(returned.credentialId()).orElseThrow().encoded().bytes()); } } private enum IntermediateExtensionVariant { - ABSENT_BASIC_CONSTRAINTS, - NONCRITICAL_BASIC_CONSTRAINTS, - UNLIMITED_BASIC_CONSTRAINTS, - ABSENT_KEY_USAGE, - NONCRITICAL_KEY_USAGE, - INCOMPATIBLE_KEY_USAGE + ABSENT_BASIC_CONSTRAINTS, NONCRITICAL_BASIC_CONSTRAINTS, UNLIMITED_BASIC_CONSTRAINTS, ABSENT_KEY_USAGE, + NONCRITICAL_KEY_USAGE, INCOMPATIBLE_KEY_USAGE } private enum BindingVariantMutation { - END_ENTITY_SAME_ID, - END_ENTITY_OTHER_ID, - CA_OTHER_ID, - NULL_CREDENTIAL + END_ENTITY_SAME_ID, END_ENTITY_OTHER_ID, CA_OTHER_ID, NULL_CREDENTIAL } private static final class CountingIssuerBackend implements CredentialIssuerBackend { @@ -1037,30 +1020,34 @@ final class PkiProofGateE2eTest { } @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, + KeyRef issuerKeyRef, BigInteger serial) { endEntityCalls.incrementAndGet(); return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { intermediateCalls.incrementAndGet(); return delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); } } - private static CredentialIssuerBackend extensionVariantBackend( - CredentialIssuerBackend delegate, KeyPair issuerKey, + private static CredentialIssuerBackend extensionVariantBackend(CredentialIssuerBackend delegate, KeyPair issuerKey, IntermediateExtensionVariant variant) { return new CredentialIssuerBackend() { @Override - public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { + public CredentialBundle issueEndEntity(ValidatedCertificateRequest candidate, + EncodedObject issuerCertificate, KeyRef issuerKeyRef, BigInteger serial) { return delegate.issueEndEntity(candidate, issuerCertificate, issuerKeyRef, serial); } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { - Credential credential = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + Credential credential = delegate.issueIntermediateCertificate(issuance, issuerCertificate, + issuerKeyRef); return rebuildIntermediateExtensions(credential, issuerKey, variant); } }; @@ -1076,7 +1063,8 @@ final class PkiProofGateE2eTest { } @Override - public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, EncodedObject issuerCertificate, KeyRef issuerKeyRef) { + public Credential issueIntermediateCertificate(ValidatedCaCertificateRequest issuance, + EncodedObject issuerCertificate, KeyRef issuerKeyRef) { Credential raw = delegate.issueIntermediateCertificate(issuance, issuerCertificate, issuerKeyRef); produced.set(raw); if (mutation == BindingVariantMutation.NULL_CREDENTIAL) { @@ -1090,22 +1078,21 @@ final class PkiProofGateE2eTest { private static CredentialProfileBinding bindingFor(BindingVariantMutation mutation, CertificateProfileRef profileReference) { return switch (mutation) { - case END_ENTITY_SAME_ID -> new EndEntityProfileBinding(new CertificateProfileRef( - profileReference.profileId(), 1, - new byte[CertificateProfileRef.HASH_BYTES])); - case END_ENTITY_OTHER_ID -> new EndEntityProfileBinding(new CertificateProfileRef("other", 1, - new byte[CertificateProfileRef.HASH_BYTES])); - case CA_OTHER_ID -> new CaProfileBinding(new CertificateProfileRef("other", 1, - new byte[CertificateProfileRef.HASH_BYTES])); + case END_ENTITY_SAME_ID -> + new EndEntityProfileBinding(new CertificateProfileRef(profileReference.profileId(), 1, + new byte[CertificateProfileRef.HASH_BYTES])); + case END_ENTITY_OTHER_ID -> new EndEntityProfileBinding( + new CertificateProfileRef("other", 1, new byte[CertificateProfileRef.HASH_BYTES])); + case CA_OTHER_ID -> + new CaProfileBinding(new CertificateProfileRef("other", 1, new byte[CertificateProfileRef.HASH_BYTES])); case NULL_CREDENTIAL -> throw new IllegalStateException("null credential has no binding"); }; } private static Credential copyWithBinding(Credential credential, CredentialProfileBinding binding) { return new Credential(credential.credentialId(), credential.formatId(), credential.issuerRef(), - credential.subjectRef(), credential.validity(), credential.serialOrUniqueId(), - credential.publicKeyId(), binding, credential.status(), credential.encoded(), - credential.attributes()); + credential.subjectRef(), credential.validity(), credential.serialOrUniqueId(), credential.publicKeyId(), + binding, credential.status(), credential.encoded(), credential.attributes()); } private static Credential rebuildIntermediateIdentity(Credential credential, KeyPair issuerKey, @@ -1168,9 +1155,8 @@ final class PkiProofGateE2eTest { private static ParsedCertificationRequest parse(PkiTestRuntime runtime, PKCS10CertificationRequest csr) throws Exception { - return runtime.certificationRequestService() - .parse(new CertificationRequest(runtime.framework().formatId(), - new EncodedObject(Encoding.DER, csr.getEncoded()))); + return runtime.certificationRequestService().parse(new CertificationRequest(runtime.framework().formatId(), + new EncodedObject(Encoding.DER, csr.getEncoded()))); } private static PKCS10CertificationRequest makeCsr(KeyPair publicKeyPair, KeyPair signingKeyPair, String dn) @@ -1220,8 +1206,8 @@ final class PkiProofGateE2eTest { source.publicKeyInfo(), source.requestedValidity(), source.requestedProfileId(), attributes); } - private static void assertRejected(PkiTestRuntime runtime, PkiId issuerCaId, - ParsedCertificationRequest request, String code) { + private static void assertRejected(PkiTestRuntime runtime, PkiId issuerCaId, ParsedCertificationRequest request, + String code) { int before = runtime.auditSink().snapshot().size(); assertThrows(PkiException.class, () -> issue(runtime, issuerCaId, request)); assertEquals(before + 1, runtime.auditSink().snapshot().size()); @@ -1229,16 +1215,15 @@ final class PkiProofGateE2eTest { } private static void issue(PkiTestRuntime runtime, PkiId issuerCaId, ParsedCertificationRequest request) { - runtime.issuanceService().issueEndEntity(new IssueEndEntityCommand(issuerCaId, request, "default", - Optional.empty())); + runtime.issuanceService() + .issueEndEntity(new IssueEndEntityCommand(issuerCaId, request, "default", Optional.empty())); } private static AttributeSet hostileIntermediateAttributes(PublicKey wrongPublicKey) { return SimpleAttributeSet.builder() .put(BcX509Attributes.ISSUER_CERT_DER, new AttributeValue.BytesValue(new byte[] { 0x01 })) .put(BcX509Attributes.ISSUER_KEYREF, new AttributeValue.StringValue("attacker-key")) - .put(BcX509Attributes.SUBJECT_SPKI_DER, - new AttributeValue.BytesValue(wrongPublicKey.getEncoded())) + .put(BcX509Attributes.SUBJECT_SPKI_DER, new AttributeValue.BytesValue(wrongPublicKey.getEncoded())) .put(BcX509Attributes.SUBJECT_DN, new AttributeValue.StringValue("CN=Attacker")).build(); } diff --git a/pki/src/test/java/zeroecho/pki/impl/core/DefaultProfileServiceTest.java b/pki/src/test/java/zeroecho/pki/impl/core/DefaultProfileServiceTest.java index 31a5cee..f7d0fe3 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/DefaultProfileServiceTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/DefaultProfileServiceTest.java @@ -46,8 +46,7 @@ final class DefaultProfileServiceTest { private static final Clock CLOCK = Clock.fixed(Instant.parse("2026-07-30T10:00:00Z"), ZoneOffset.UTC); @Test - void builtInImportIsInactiveUntilExplicitActivationAndSurvivesRestart(@TempDir Path directory) - throws Exception { + void builtInImportIsInactiveUntilExplicitActivationAndSurvivesRestart(@TempDir Path directory) throws Exception { BuiltInCertificateProfileTemplate template = builtIn("server-tls"); Path root = directory.resolve("store"); CertificateProfileRef reference; @@ -69,8 +68,8 @@ final class DefaultProfileServiceTest { @Test void repeatImportIsIdempotentAndConflictingVersionIsRejected(@TempDir Path directory) throws Exception { BuiltInCertificateProfileTemplate template = builtIn("server-tls"); - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); CertificateProfileRef first = service.importProfile(template.canonicalJson()); CertificateProfileRef repeated = service.importProfile(template.canonicalJson()); @@ -91,8 +90,8 @@ final class DefaultProfileServiceTest { .replace("\"profileVersion\":1", "\"profileVersion\":2") .replace("\"displayName\":\"VPN Client\"", "\"displayName\":\"VPN Client v2\""); byte[] versionTwo = versionTwoText.getBytes(StandardCharsets.UTF_8); - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); CertificateProfileRef one = service.importProfile(template.canonicalJson()); service.activateProfile("vpn-client", 1); @@ -107,11 +106,10 @@ final class DefaultProfileServiceTest { } @Test - void historicalSnapshotFailsBeforeTargetCreationWhenActivePointerExists(@TempDir Path directory) - throws Exception { + void historicalSnapshotFailsBeforeTargetCreationWhenActivePointerExists(@TempDir Path directory) throws Exception { Path target = directory.resolve("snapshot"); - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); service.importBuiltIn(builtIn("email-signing")); service.activateProfile("email-signing", 1); @@ -123,11 +121,10 @@ final class DefaultProfileServiceTest { } @Test - void concurrentIdenticalAndConflictingImportsAreSerializedPerProfile(@TempDir Path directory) - throws Exception { + void concurrentIdenticalAndConflictingImportsAreSerializedPerProfile(@TempDir Path directory) throws Exception { BuiltInCertificateProfileTemplate template = builtIn("server-tls"); - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); List identical = runConcurrently( () -> service.importProfile(template.canonicalJson()), @@ -136,17 +133,15 @@ final class DefaultProfileServiceTest { assertEquals(1, service.listImportedVersions("server-tls").size()); byte[] versionTwo = new String(template.canonicalJson(), StandardCharsets.UTF_8) - .replace("\"profileVersion\":1", "\"profileVersion\":2") - .getBytes(StandardCharsets.UTF_8); + .replace("\"profileVersion\":1", "\"profileVersion\":2").getBytes(StandardCharsets.UTF_8); byte[] conflictingVersionTwo = new String(versionTwo, StandardCharsets.UTF_8) .replace("\"displayName\":\"Server TLS\"", "\"displayName\":\"Changed v2\"") .getBytes(StandardCharsets.UTF_8); - List conflicting = runAttempts( - () -> service.importProfile(versionTwo), + List conflicting = runAttempts(() -> service.importProfile(versionTwo), () -> service.importProfile(conflictingVersionTwo)); assertEquals(1L, conflicting.stream().filter(Attempt::succeeded).count()); - assertEquals(1L, conflicting.stream().filter(attempt -> attempt.message() - .contains("PROFILE_VERSION_CONFLICT")).count()); + assertEquals(1L, conflicting.stream() + .filter(attempt -> attempt.message().contains("PROFILE_VERSION_CONFLICT")).count()); assertEquals(2, service.listImportedVersions("server-tls").size()); } } @@ -158,13 +153,12 @@ final class DefaultProfileServiceTest { .replace("\"profileVersion\":1", "\"profileVersion\":2") .replace("\"displayName\":\"VPN Client\"", "\"displayName\":\"VPN Client v2\"") .getBytes(StandardCharsets.UTF_8); - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); CertificateProfileRef one = service.importProfile(template.canonicalJson()); CertificateProfileRef two = service.importProfile(versionTwo); - List results = runConcurrently( - () -> service.activateProfile("vpn-client", 1), + List results = runConcurrently(() -> service.activateProfile("vpn-client", 1), () -> service.activateProfile("vpn-client", 2)); assertTrue(results.contains(one)); assertTrue(results.contains(two)); @@ -177,11 +171,10 @@ final class DefaultProfileServiceTest { @Test void activationRequiresAnImportedVersionAndRepeatedActivationIsIdempotent(@TempDir Path directory) throws Exception { - try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), - FsPkiStoreOptions.defaults(), CLOCK)) { + try (FilesystemPkiStore store = new FilesystemPkiStore(directory.resolve("store"), FsPkiStoreOptions.defaults(), + CLOCK)) { DefaultProfileService service = service(store); - PkiException missing = assertThrows(PkiException.class, - () -> service.activateProfile("server-tls", 1)); + PkiException missing = assertThrows(PkiException.class, () -> service.activateProfile("server-tls", 1)); assertTrue(missing.getMessage().contains("PROFILE_VERSION_NOT_FOUND")); assertTrue(service.getActiveReference("server-tls").isEmpty()); @@ -204,20 +197,15 @@ final class DefaultProfileServiceTest { Logger root = Logger.getLogger(""); root.addHandler(logs); try { - assertSanitized(() -> service.importProfile(new byte[] { 1, 2, 3 }), - "PROFILE_IMPORT_VALIDATION_FAILED", sentinel); - assertSanitized(() -> service.importProfile(builtIn("server-tls").canonicalJson()), - "PROFILE_IMPORT_FAILED", sentinel); - assertSanitized(() -> service.activateProfile("server-tls", 1), - "PROFILE_ACTIVATION_FAILED", sentinel); - assertSanitized(() -> service.requireActiveProfile("server-tls"), - "PROFILE_STORE_FAILURE", sentinel); - assertSanitized(() -> service.getImportedVersion("server-tls", 1), - "PROFILE_STORE_FAILURE", sentinel); - assertSanitized(() -> service.listImportedVersions("server-tls"), - "PROFILE_STORE_FAILURE", sentinel); - assertSanitized(() -> service.getActiveReference("server-tls"), - "PROFILE_STORE_FAILURE", sentinel); + assertSanitized(() -> service.importProfile(new byte[] { 1, 2, 3 }), "PROFILE_IMPORT_VALIDATION_FAILED", + sentinel); + assertSanitized(() -> service.importProfile(builtIn("server-tls").canonicalJson()), "PROFILE_IMPORT_FAILED", + sentinel); + assertSanitized(() -> service.activateProfile("server-tls", 1), "PROFILE_ACTIVATION_FAILED", sentinel); + assertSanitized(() -> service.requireActiveProfile("server-tls"), "PROFILE_STORE_FAILURE", sentinel); + assertSanitized(() -> service.getImportedVersion("server-tls", 1), "PROFILE_STORE_FAILURE", sentinel); + assertSanitized(() -> service.listImportedVersions("server-tls"), "PROFILE_STORE_FAILURE", sentinel); + assertSanitized(() -> service.getActiveReference("server-tls"), "PROFILE_STORE_FAILURE", sentinel); } finally { root.removeHandler(logs); logs.close(); @@ -233,13 +221,12 @@ final class DefaultProfileServiceTest { private static BuiltInCertificateProfileTemplate builtIn(String profileId) { return BuiltInCertificateProfileCatalog.load(DefaultProfileServiceTest.class.getClassLoader()).stream() - .filter(template -> profileId.equals(template.definition().profileId())) - .findFirst().orElseThrow(); + .filter(template -> profileId.equals(template.definition().profileId())).findFirst().orElseThrow(); } private static PkiStore failingStore(String sentinel) { - return (PkiStore) Proxy.newProxyInstance(PkiStore.class.getClassLoader(), - new Class[] { PkiStore.class }, (proxy, method, arguments) -> { + return (PkiStore) Proxy.newProxyInstance(PkiStore.class.getClassLoader(), new Class[] { PkiStore.class }, + (proxy, method, arguments) -> { if (method.getDeclaringClass() == Object.class) { return switch (method.getName()) { case "hashCode" -> System.identityHashCode(proxy); diff --git a/pki/src/test/java/zeroecho/pki/impl/core/DefaultRevocationServiceTest.java b/pki/src/test/java/zeroecho/pki/impl/core/DefaultRevocationServiceTest.java index 8768153..5f751e2 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/DefaultRevocationServiceTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/DefaultRevocationServiceTest.java @@ -38,9 +38,8 @@ final class DefaultRevocationServiceTest { @Test void successAuditsCommittedStateAndRevisionOnce() { - RevocationJournal committed = new RevocationJournal(CREDENTIAL_ID, - List.of(new RevocationTransition(1L, RevocationState.HELD, NOW, Optional.empty(), - new SimpleAttributeSet()))); + RevocationJournal committed = new RevocationJournal(CREDENTIAL_ID, List.of( + new RevocationTransition(1L, RevocationState.HELD, NOW, Optional.empty(), new SimpleAttributeSet()))); List events = new ArrayList<>(); DefaultRevocationService service = new DefaultRevocationService(store((method, arguments) -> { if ("transitionRevocation".equals(method)) { @@ -49,8 +48,7 @@ final class DefaultRevocationServiceTest { throw new UnsupportedOperationException(method); }), Clock.fixed(NOW, ZoneOffset.UTC), events::add); - assertEquals(committed, - service.hold(new RevocationCommand.Hold(CREDENTIAL_ID, new SimpleAttributeSet()))); + assertEquals(committed, service.hold(new RevocationCommand.Hold(CREDENTIAL_ID, new SimpleAttributeSet()))); assertEquals(1, events.size()); assertEquals("COMMITTED", events.get(0).details().get("result")); assertEquals("HELD", events.get(0).details().get("state")); diff --git a/pki/src/test/java/zeroecho/pki/impl/core/DefaultStatusObjectServiceCrlTest.java b/pki/src/test/java/zeroecho/pki/impl/core/DefaultStatusObjectServiceCrlTest.java index 01204bd..43e8f8c 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/DefaultStatusObjectServiceCrlTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/DefaultStatusObjectServiceCrlTest.java @@ -97,8 +97,7 @@ final class DefaultStatusObjectServiceCrlTest { List entries = new ArrayList<>(); for (int index = 0; index < reasons.size(); index++) { entries.add(new CrlEntry(serials.get(index), - EVALUATION_TIME.minusSeconds(index + 1L).plusNanos(987_654_321L), - reasons.get(index))); + EVALUATION_TIME.minusSeconds(index + 1L).plusNanos(987_654_321L), reasons.get(index))); } StatusObject status = runtime.framework().statusObjectGenerator().generate(command, entries); @@ -127,24 +126,21 @@ final class DefaultStatusObjectServiceCrlTest { Credential permanent = issue(runtime, caId, permanentKey, "Permanent"); Credential clear = issue(runtime, caId, clearKey, "Clear"); - RevocationJournal heldJournal = runtime.revocationService().hold( - new RevocationCommand.Hold(held.credentialId(), emptyAttributes())); - RevocationJournal permanentJournal = runtime.revocationService().revokePermanently( - new RevocationCommand.RevokePermanently(permanent.credentialId(), + RevocationJournal heldJournal = runtime.revocationService() + .hold(new RevocationCommand.Hold(held.credentialId(), emptyAttributes())); + RevocationJournal permanentJournal = runtime.revocationService() + .revokePermanently(new RevocationCommand.RevokePermanently(permanent.credentialId(), RevocationReason.AA_COMPROMISE, emptyAttributes())); - runtime.revocationService().hold( - new RevocationCommand.Hold(clear.credentialId(), emptyAttributes())); - runtime.revocationService().unhold( - new RevocationCommand.Unhold(clear.credentialId(), emptyAttributes())); + runtime.revocationService().hold(new RevocationCommand.Hold(clear.credentialId(), emptyAttributes())); + runtime.revocationService().unhold(new RevocationCommand.Unhold(clear.credentialId(), emptyAttributes())); - StatusObject status = runtime.statusObjectService().generate(new StatusObjectGenerateCommand( - caId, StatusObjectType.CRL, runtime.framework().formatId(), emptyAttributes())); + StatusObject status = runtime.statusObjectService().generate(new StatusObjectGenerateCommand(caId, + StatusObjectType.CRL, runtime.framework().formatId(), emptyAttributes())); X509CRLHolder crl = new X509CRLHolder(status.encoded().bytes()); X509CertificateHolderView heldCertificate = certificate(held); X509CertificateHolderView permanentCertificate = certificate(permanent); X509CertificateHolderView clearCertificate = certificate(clear); - assertEntry(crl, heldCertificate.serial(), heldJournal.latest().time(), - RevocationReason.CERTIFICATE_HOLD); + assertEntry(crl, heldCertificate.serial(), heldJournal.latest().time(), RevocationReason.CERTIFICATE_HOLD); assertEntry(crl, permanentCertificate.serial(), permanentJournal.latest().time(), RevocationReason.AA_COMPROMISE); assertNull(crl.getRevokedCertificate(clearCertificate.serial())); @@ -162,30 +158,26 @@ final class DefaultStatusObjectServiceCrlTest { StatusObjectGenerateCommand command = new StatusObjectGenerateCommand(caId, StatusObjectType.CRL, runtime.framework().formatId(), emptyAttributes()); - assertCrlFailure(runtime, command, - List.of(journal(new PkiId("credential:missing"), RevocationState.HELD, - EVALUATION_TIME.minusSeconds(1), Optional.empty())), - Map.of(), false); + assertCrlFailure(runtime, command, List.of(journal(new PkiId("credential:missing"), RevocationState.HELD, + EVALUATION_TIME.minusSeconds(1), Optional.empty())), Map.of(), false); - Credential wrongFormat = copy(template, "wrong-format", new FormatId("not-x509"), - template.encoded()); - assertCrlFailure(runtime, command, - List.of(journal(wrongFormat.credentialId(), RevocationState.HELD, + Credential wrongFormat = copy(template, "wrong-format", new FormatId("not-x509"), template.encoded()); + assertCrlFailure( + runtime, command, List.of(journal(wrongFormat.credentialId(), RevocationState.HELD, EVALUATION_TIME.minusSeconds(1), Optional.empty())), Map.of(wrongFormat.credentialId(), wrongFormat), false); - Credential wrongEncoding = copy(template, "wrong-encoding", - BcX509CredentialFramework.FORMAT_ID, + Credential wrongEncoding = copy(template, "wrong-encoding", BcX509CredentialFramework.FORMAT_ID, new EncodedObject(Encoding.PEM, template.encoded().bytes())); - assertCrlFailure(runtime, command, - List.of(journal(wrongEncoding.credentialId(), RevocationState.HELD, + assertCrlFailure( + runtime, command, List.of(journal(wrongEncoding.credentialId(), RevocationState.HELD, EVALUATION_TIME.minusSeconds(1), Optional.empty())), Map.of(wrongEncoding.credentialId(), wrongEncoding), false); Credential malformed = copy(template, "malformed", BcX509CredentialFramework.FORMAT_ID, new EncodedObject(Encoding.DER, new byte[] { 1, 2, 3 })); - assertCrlFailure(runtime, command, - List.of(journal(malformed.credentialId(), RevocationState.HELD, + assertCrlFailure( + runtime, command, List.of(journal(malformed.credentialId(), RevocationState.HELD, EVALUATION_TIME.minusSeconds(1), Optional.empty())), Map.of(malformed.credentialId(), malformed), false); @@ -193,18 +185,17 @@ final class DefaultStatusObjectServiceCrlTest { template.encoded()); Credential duplicateTwo = copy(template, "duplicate-two", BcX509CredentialFramework.FORMAT_ID, template.encoded()); - assertCrlFailure(runtime, command, List.of( - journal(duplicateOne.credentialId(), RevocationState.HELD, - EVALUATION_TIME.minusSeconds(1), Optional.empty()), - journal(duplicateTwo.credentialId(), RevocationState.PERMANENTLY_REVOKED, - EVALUATION_TIME.minusSeconds(1), Optional.of(RevocationReason.KEY_COMPROMISE))), - Map.of(duplicateOne.credentialId(), duplicateOne, - duplicateTwo.credentialId(), duplicateTwo), false); - - Credential future = copy(template, "future", BcX509CredentialFramework.FORMAT_ID, - template.encoded()); assertCrlFailure(runtime, command, - List.of(journal(future.credentialId(), RevocationState.HELD, + List.of(journal(duplicateOne.credentialId(), RevocationState.HELD, EVALUATION_TIME.minusSeconds(1), + Optional.empty()), + journal(duplicateTwo.credentialId(), RevocationState.PERMANENTLY_REVOKED, + EVALUATION_TIME.minusSeconds(1), Optional.of(RevocationReason.KEY_COMPROMISE))), + Map.of(duplicateOne.credentialId(), duplicateOne, duplicateTwo.credentialId(), duplicateTwo), + false); + + Credential future = copy(template, "future", BcX509CredentialFramework.FORMAT_ID, template.encoded()); + assertCrlFailure( + runtime, command, List.of(journal(future.credentialId(), RevocationState.HELD, EVALUATION_TIME.plusSeconds(1), Optional.empty())), Map.of(future.credentialId(), future), false); @@ -213,8 +204,7 @@ final class DefaultStatusObjectServiceCrlTest { } @Test - void generatorAndPersistenceFailuresExposeNoPartialCrlOrHostileDiagnostics(@TempDir Path root) - throws Exception { + void generatorAndPersistenceFailuresExposeNoPartialCrlOrHostileDiagnostics(@TempDir Path root) throws Exception { KeyPair rootKey = generateRsa(); KeyRef rootKeyRef = new KeyRef("kref:v1:keyring:test:crl-boundary"); try (PkiTestRuntime runtime = PkiTestRuntime.create(root, root.resolve("bus.log"), @@ -230,9 +220,8 @@ final class DefaultStatusObjectServiceCrlTest { generatorCalls.incrementAndGet(); throw new IllegalStateException(SENTINEL); }; - DefaultStatusObjectService generatorFailureService = new DefaultStatusObjectService( - runtime.store(), frameworkView(runtime.framework(), hostileGenerator), - runtime.auditSink(), usableResolver()); + DefaultStatusObjectService generatorFailureService = new DefaultStatusObjectService(runtime.store(), + frameworkView(runtime.framework(), hostileGenerator), runtime.auditSink(), usableResolver()); AtomicReference generatorResult = new AtomicReference<>(); PkiException generatorFailure = assertThrows(PkiException.class, () -> generatorResult.set(generatorFailureService.generate(command))); @@ -243,15 +232,13 @@ final class DefaultStatusObjectServiceCrlTest { assertEquals(signCount, runtime.submittedSignCount()); AtomicInteger persistenceCalls = new AtomicInteger(); - StatusObject generated = new StatusObject(new PkiId("status:controlled-crl"), - command.formatId(), caId, StatusObjectType.CRL, EVALUATION_TIME, - Optional.empty(), new EncodedObject(Encoding.DER, new byte[] { 1 }), - emptyAttributes()); + StatusObject generated = new StatusObject(new PkiId("status:controlled-crl"), command.formatId(), caId, + StatusObjectType.CRL, EVALUATION_TIME, Optional.empty(), + new EncodedObject(Encoding.DER, new byte[] { 1 }), emptyAttributes()); StatusObjectGenerator controlledGenerator = (ignoredCommand, ignoredEntries) -> generated; DefaultStatusObjectService persistenceFailureService = new DefaultStatusObjectService( failingPersistenceStore(runtime.store(), persistenceCalls), - frameworkView(runtime.framework(), controlledGenerator), - runtime.auditSink(), usableResolver()); + frameworkView(runtime.framework(), controlledGenerator), runtime.auditSink(), usableResolver()); AtomicReference persistenceResult = new AtomicReference<>(); PkiException persistenceFailure = assertThrows(PkiException.class, () -> persistenceResult.set(persistenceFailureService.generate(command))); @@ -318,8 +305,7 @@ final class DefaultStatusObjectServiceCrlTest { }); } - private static CredentialFramework frameworkView(CredentialFramework delegate, - StatusObjectGenerator generator) { + private static CredentialFramework frameworkView(CredentialFramework delegate, StatusObjectGenerator generator) { return (CredentialFramework) Proxy.newProxyInstance(CredentialFramework.class.getClassLoader(), new Class[] { CredentialFramework.class }, (proxy, method, arguments) -> { if (method.getName().equals("statusObjectGenerator")) { @@ -361,46 +347,40 @@ final class DefaultStatusObjectServiceCrlTest { private static RevocationJournal journal(PkiId credentialId, RevocationState state, Instant time, Optional reason) { - return new RevocationJournal(credentialId, List.of(new RevocationTransition( - 1L, state, time, reason, emptyAttributes()))); + return new RevocationJournal(credentialId, + List.of(new RevocationTransition(1L, state, time, reason, emptyAttributes()))); } private static Credential copy(Credential template, String suffix, FormatId formatId, EncodedObject encoded) { CaProfileBinding binding = assertInstanceOf(CaProfileBinding.class, template.profileBinding()); - return new Credential(new PkiId("credential:" + suffix), formatId, template.issuerRef(), - template.subjectRef(), template.validity(), template.serialOrUniqueId(), - template.publicKeyId(), new CaProfileBinding(binding.reference()), CredentialStatus.ISSUED, encoded, - template.attributes()); + return new Credential(new PkiId("credential:" + suffix), formatId, template.issuerRef(), template.subjectRef(), + template.validity(), template.serialOrUniqueId(), template.publicKeyId(), + new CaProfileBinding(binding.reference()), CredentialStatus.ISSUED, encoded, template.attributes()); } private static StatusObjectGenerateCommand crlCommand(PkiId caId, Credential issuer, KeyRef keyRef) { AttributeSet attributes = SimpleAttributeSet.builder() - .put(BcX509Attributes.ISSUER_CERT_DER, - new AttributeValue.BytesValue(issuer.encoded().bytes())) - .put(BcX509Attributes.ISSUER_KEYREF, new AttributeValue.StringValue(keyRef.value())) - .build(); - return new StatusObjectGenerateCommand(caId, StatusObjectType.CRL, - BcX509CredentialFramework.FORMAT_ID, attributes); + .put(BcX509Attributes.ISSUER_CERT_DER, new AttributeValue.BytesValue(issuer.encoded().bytes())) + .put(BcX509Attributes.ISSUER_KEYREF, new AttributeValue.StringValue(keyRef.value())).build(); + return new StatusObjectGenerateCommand(caId, StatusObjectType.CRL, BcX509CredentialFramework.FORMAT_ID, + attributes); } private static Credential issue(PkiTestRuntime runtime, PkiId caId, KeyPair subjectKey, String commonName) throws Exception { PKCS10CertificationRequest request = certificationRequest(subjectKey, commonName); - ParsedCertificationRequest parsed = runtime.certificationRequestService().parse( - new CertificationRequest(runtime.framework().formatId(), - new EncodedObject(Encoding.DER, request.getEncoded()))); + ParsedCertificationRequest parsed = runtime.certificationRequestService().parse(new CertificationRequest( + runtime.framework().formatId(), new EncodedObject(Encoding.DER, request.getEncoded()))); runtime.certificationRequestService().verifyProofOfPossession(parsed, new VerificationPolicy(true, Optional.empty())); IssuanceService issuance = runtime.issuanceService(); - return issuance.issueEndEntity(new IssueEndEntityCommand(caId, parsed, "default", - Optional.empty())).credential(); + return issuance.issueEndEntity(new IssueEndEntityCommand(caId, parsed, "default", Optional.empty())) + .credential(); } - private static PKCS10CertificationRequest certificationRequest(KeyPair pair, String commonName) - throws Exception { + private static PKCS10CertificationRequest certificationRequest(KeyPair pair, String commonName) throws Exception { X500Name subject = new X500Name("CN=" + commonName); - PKCS10CertificationRequestBuilder builder = - new JcaPKCS10CertificationRequestBuilder(subject, pair.getPublic()); + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, pair.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(pair.getPrivate()); return builder.build(signer); } @@ -417,15 +397,12 @@ final class DefaultStatusObjectServiceCrlTest { } private static List activeReasons() { - return Arrays.stream(RevocationReason.values()) - .filter(reason -> reason != RevocationReason.REMOVE_FROM_CRL) + return Arrays.stream(RevocationReason.values()).filter(reason -> reason != RevocationReason.REMOVE_FROM_CRL) .toList(); } private static List serials(int size) { - List values = new ArrayList<>(List.of( - BigInteger.ONE, - BigInteger.valueOf(Long.MAX_VALUE), + List values = new ArrayList<>(List.of(BigInteger.ONE, BigInteger.valueOf(Long.MAX_VALUE), BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), BigInteger.ONE.shiftLeft(159).subtract(BigInteger.ONE))); for (long value = 2L; values.size() < size; value++) { @@ -457,8 +434,7 @@ final class DefaultStatusObjectServiceCrlTest { }; } - private static void assertEntry(X509CRLHolder crl, BigInteger serial, Instant time, - RevocationReason reason) { + private static void assertEntry(X509CRLHolder crl, BigInteger serial, Instant time, RevocationReason reason) { X509CRLEntryHolder entry = crl.getRevokedCertificate(serial); assertTrue(entry != null); assertEquals(Date.from(time.truncatedTo(ChronoUnit.SECONDS)), entry.getRevocationDate()); diff --git a/pki/src/test/java/zeroecho/pki/impl/core/H7ProfileEnforcementTest.java b/pki/src/test/java/zeroecho/pki/impl/core/H7ProfileEnforcementTest.java index 7bb65e9..db9f952 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/H7ProfileEnforcementTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/H7ProfileEnforcementTest.java @@ -131,19 +131,16 @@ final class H7ProfileEnforcementTest { @Test void profileConstructionRejectsUnsatisfiableAndOpenPolicy() { - SubjectAlternativeNameRule dns = - new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 0, 1, false, false); + SubjectAlternativeNameRule dns = new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 0, 1, + false, false); assertThrows(IllegalArgumentException.class, - () -> new SubjectAlternativeNamePolicy(2, 2, List.of(dns), false, Set.of(), false, - false, false)); + () -> new SubjectAlternativeNamePolicy(2, 2, List.of(dns), false, Set.of(), false, false, false)); assertThrows(IllegalArgumentException.class, - () -> new SubjectAlternativeNamePolicy(0, 1, List.of(), false, Set.of(), false, - true, false)); + () -> new SubjectAlternativeNamePolicy(0, 1, List.of(), false, Set.of(), false, true, false)); + assertThrows(IllegalArgumentException.class, () -> new SubjectAlternativeNamePolicy(0, 1, List.of(dns), false, + Set.of("https"), false, false, false)); assertThrows(IllegalArgumentException.class, - () -> new SubjectAlternativeNamePolicy(0, 1, List.of(dns), false, Set.of("https"), false, - false, false)); - assertThrows(IllegalArgumentException.class, () -> leaf(Set.of(LeafKeyUsage.ENCIPHER_ONLY), - Set.of("RSA"), Set.of())); + () -> leaf(Set.of(LeafKeyUsage.ENCIPHER_ONLY), Set.of("RSA"), Set.of())); assertThrows(IllegalArgumentException.class, () -> leaf(Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), Set.of("rsa"), Set.of())); assertThrows(IllegalArgumentException.class, () -> new ExtendedKeyUsageId("1.40.1")); @@ -155,22 +152,19 @@ final class H7ProfileEnforcementTest { KeyPair keyPair = rsa(); X500NameBuilder subject = new X500NameBuilder(BCStyle.INSTANCE); subject.addRDN(BCStyle.CN, new DERUTF8String("#literal\\name")); - Extensions extensions = extensions(false, - new GeneralName(GeneralName.dNSName, "WWW.Example.COM"), + Extensions extensions = extensions(false, new GeneralName(GeneralName.dNSName, "WWW.Example.COM"), new GeneralName(GeneralName.iPAddress, new DEROctetString(new byte[] { 10, 0, 0, 1 })), new GeneralName(GeneralName.uniformResourceIdentifier, "HTTPS://Example.COM/a%2Fb"), new GeneralName(GeneralName.rfc822Name, "Local@Example.COM")); ParsedCertificationRequest parsed = parse(csr(keyPair, subject.build(), List.of(extensions))); - assertEquals(List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "#literal\\name")), - parsed.subjectRdns()); + assertEquals(List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "#literal\\name")), parsed.subjectRdns()); assertEquals(BcX509ProfileSupport.subject(parsed.subjectRdns()).toString(), parsed.subjectRef().value()); assertEquals(List.of(new SubjectAlternativeName.DnsName("www.example.com"), new SubjectAlternativeName.IpAddress(new byte[] { 10, 0, 0, 1 }), new SubjectAlternativeName.UriName("https://example.com/a%2Fb"), - new SubjectAlternativeName.Rfc822Name("Local@example.com")), - parsed.subjectAlternativeNames()); + new SubjectAlternativeName.Rfc822Name("Local@example.com")), parsed.subjectAlternativeNames()); } @Test @@ -180,8 +174,8 @@ final class H7ProfileEnforcementTest { unsupportedSubject.addRDN(BCStyle.CN, new DERBMPString("unsupported")); assertCode("SUBJECT_VALUE_UNSUPPORTED", () -> parse(csr(keyPair, unsupportedSubject.build(), List.of()))); - Extensions unknown = new Extensions(new Extension(new ASN1ObjectIdentifier("1.2.3.4"), false, - new byte[] { 0x05, 0x00 })); + Extensions unknown = new Extensions( + new Extension(new ASN1ObjectIdentifier("1.2.3.4"), false, new byte[] { 0x05, 0x00 })); assertCode("EXTENSION_UNSUPPORTED", () -> parse(csr(keyPair, new X500Name("CN=Leaf"), List.of(unknown)))); Extensions criticalSan = extensions(true, new GeneralName(GeneralName.dNSName, "example.com")); @@ -189,8 +183,7 @@ final class H7ProfileEnforcementTest { () -> parse(csr(keyPair, new X500Name("CN=Leaf"), List.of(criticalSan)))); Extensions san = extensions(false, new GeneralName(GeneralName.dNSName, "example.com")); - assertCode("CSR_ATTRIBUTE_UNSUPPORTED", - () -> parse(csr(keyPair, new X500Name("CN=Leaf"), List.of(san, san)))); + assertCode("CSR_ATTRIBUTE_UNSUPPORTED", () -> parse(csr(keyPair, new X500Name("CN=Leaf"), List.of(san, san)))); Extension sanExtension = san.getExtension(Extension.subjectAlternativeName); DERSequence duplicateSan = new DERSequence(new ASN1Encodable[] { sanExtension, sanExtension }); @@ -215,8 +208,7 @@ final class H7ProfileEnforcementTest { KeyPair rsa = rsa(); SubjectPublicKeyInfo original = SubjectPublicKeyInfo.getInstance(rsa.getPublic().getEncoded()); SubjectPublicKeyInfo missingNull = new SubjectPublicKeyInfo( - new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption), - original.getPublicKeyData().getBytes()); + new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption), original.getPublicKeyData().getBytes()); assertCode("SUBJECT_KEY_PARAMETERS_UNSUPPORTED", () -> validate(missingNull.getEncoded(), "RSA", policyWithFixedOrganization("RSA"))); @@ -243,13 +235,12 @@ final class H7ProfileEnforcementTest { @Test void activeProfileCannotSubstitutePolicyForItsCanonicalReference() { - CertificateProfileDefinition definition = - CertificateProfileDocumentCodec.parse(H7ProfileDocuments.defaultProfile()); + CertificateProfileDefinition definition = CertificateProfileDocumentCodec + .parse(H7ProfileDocuments.defaultProfile()); CertificateProfileRef substituted = new CertificateProfileRef(definition.profileId(), definition.profileVersion(), new byte[CertificateProfileRef.HASH_BYTES]); - assertThrows(IllegalArgumentException.class, - () -> new ActiveCertificateProfile(substituted, definition)); + assertThrows(IllegalArgumentException.class, () -> new ActiveCertificateProfile(substituted, definition)); } @Test @@ -273,10 +264,8 @@ final class H7ProfileEnforcementTest { () -> CredentialProfileBindings.requireEndEntityBinding(ca, reference)); assertCode(CredentialProfileBindings.MISMATCH_CODE, () -> CredentialProfileBindings.requireCaBinding(endEntity, reference)); - assertCode(CredentialProfileBindings.MISMATCH_CODE, - () -> CredentialProfileBindings.requireCaBinding( - new CaProfileBinding(new CertificateProfileRef("other", 1, new byte[32])), - reference)); + assertCode(CredentialProfileBindings.MISMATCH_CODE, () -> CredentialProfileBindings.requireCaBinding( + new CaProfileBinding(new CertificateProfileRef("other", 1, new byte[32])), reference)); assertCode(CredentialProfileBindings.MISMATCH_CODE, () -> CredentialProfileBindings.requireCaBinding(null, reference)); } @@ -290,10 +279,9 @@ final class H7ProfileEnforcementTest { Map.of(rootRef, root))) { PkiId caId = runtime.caService().createRoot(new CaCreateCommand(runtime.framework().formatId(), new SubjectRef("CN=Root"), "root-ca", Optional.of(rootRef), new SimpleAttributeSet())); - ParsedCertificationRequest request = runtime.certificationRequestService().parse( - new CertificationRequest(runtime.framework().formatId(), - new EncodedObject(Encoding.DER, - csr(leaf, new X500Name("CN=Leaf"), List.of()).getEncoded()))); + ParsedCertificationRequest request = runtime.certificationRequestService().parse(new CertificationRequest( + runtime.framework().formatId(), + new EncodedObject(Encoding.DER, csr(leaf, new X500Name("CN=Leaf"), List.of()).getEncoded()))); int signs = runtime.submittedSignCount(); assertRejectedWithoutSideEffects(runtime, caId, request, "missing", Optional.empty(), signs); @@ -312,8 +300,8 @@ final class H7ProfileEnforcementTest { DefaultIssuanceService service = new DefaultIssuanceService(runtime.store(), runtime.framework(), runtime.issuerBackend(), runtime.auditSink(), runtime.statusResolver(), mismatched, Clock.systemUTC()); - assertThrows(PkiException.class, () -> service.issueEndEntity( - new IssueEndEntityCommand(caId, request, "requested", Optional.empty()))); + assertThrows(PkiException.class, () -> service + .issueEndEntity(new IssueEndEntityCommand(caId, request, "requested", Optional.empty()))); assertEquals(signs, runtime.submittedSignCount()); assertEquals(List.of("issuerCaId", "request", "profileId", "validityOverride"), @@ -331,11 +319,10 @@ final class H7ProfileEnforcementTest { private static ProfileService mismatchedProfileService(ProfileService delegate) { return (ProfileService) Proxy.newProxyInstance(ProfileService.class.getClassLoader(), - new Class[] { ProfileService.class }, - (proxy, method, args) -> { + new Class[] { ProfileService.class }, (proxy, method, args) -> { if ("requireActiveProfile".equals(method.getName())) { - CertificateProfileDefinition definition = - CertificateProfileDocumentCodec.parse(H7ProfileDocuments.defaultProfile()); + CertificateProfileDefinition definition = CertificateProfileDocumentCodec + .parse(H7ProfileDocuments.defaultProfile()); return new ActiveCertificateProfile(profileRef(definition), definition); } try { @@ -356,11 +343,10 @@ final class H7ProfileEnforcementTest { } } - private static ValidatedCertificateRequest validate(byte[] encoded, String algorithm, - CertificateProfile profile) { + private static ValidatedCertificateRequest validate(byte[] encoded, String algorithm, CertificateProfile profile) { ParsedCertificationRequest request = parsedRequest(encoded); - IssueEndEntityCommand command = - new IssueEndEntityCommand(new PkiId("ca:h7"), request, profile.profileId(), Optional.empty()); + IssueEndEntityCommand command = new IssueEndEntityCommand(new PkiId("ca:h7"), request, profile.profileId(), + Optional.empty()); VerifiedIssuanceCandidate candidate = new VerifiedIssuanceCandidate(request, request.requestId(), request.publicKeyInfo(), ProofOfPossessionStatus.VERIFIED, command); CertificateProfileRef reference = new CertificateProfileRef(profile.profileId(), 1, @@ -372,41 +358,36 @@ final class H7ProfileEnforcementTest { AttributeSet attributes = SimpleAttributeSet.builder() .put(BcX509Attributes.CSR_DER, new AttributeValue.BytesValue(new byte[] { 1 })).build(); return new ParsedCertificationRequest(new PkiId("csr:h7"), BcX509CredentialFramework.FORMAT_ID, - new SubjectRef("CN=Leaf"), new EncodedObject(Encoding.DER, encoded), Optional.empty(), - Optional.empty(), List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "Leaf")), - List.of(), false, attributes); + new SubjectRef("CN=Leaf"), new EncodedObject(Encoding.DER, encoded), Optional.empty(), Optional.empty(), + List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "Leaf")), List.of(), false, attributes); } private static Credential issuerCredential() { return new Credential(new PkiId("credential:issuer"), BcX509CredentialFramework.FORMAT_ID, new IssuerRef(new PkiId("ca:issuer")), new SubjectRef("CN=Issuer"), new Validity(NOW.minus(Duration.ofDays(1)), NOW.plus(Duration.ofDays(1000))), "1", - new PkiId("spki:issuer"), - new CaProfileBinding(new CertificateProfileRef("root", 1, new byte[32])), - CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, new byte[] { 1 }), new SimpleAttributeSet()); + new PkiId("spki:issuer"), new CaProfileBinding(new CertificateProfileRef("root", 1, new byte[32])), + CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, new byte[] { 1 }), new SimpleAttributeSet()); } private static CertificateProfile policyWithFixedOrganization(String algorithm) { SubjectPolicy subject = new SubjectPolicy(false, List.of( new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 256, Optional.empty(), true), - new SubjectRdnRule(SubjectRdnType.ORGANIZATION_NAME, 1, 1, 256, - Optional.of("Profile Fixed"), false))); - SubjectAlternativeNamePolicy sans = - new SubjectAlternativeNamePolicy(0, 0, List.of(), false, Set.of(), false, false, false); - LeafCertificatePolicy leaf = new LeafCertificatePolicy(sans, - Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), Set.of(), true, false, true, - Set.of(algorithm)); - return new CertificateProfile("h7", BcX509CredentialFramework.FORMAT_ID, "H7", - Duration.ofDays(365), subject, leaf); + new SubjectRdnRule(SubjectRdnType.ORGANIZATION_NAME, 1, 1, 256, Optional.of("Profile Fixed"), false))); + SubjectAlternativeNamePolicy sans = new SubjectAlternativeNamePolicy(0, 0, List.of(), false, Set.of(), false, + false, false); + LeafCertificatePolicy leaf = new LeafCertificatePolicy(sans, Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), Set.of(), + true, false, true, Set.of(algorithm)); + return new CertificateProfile("h7", BcX509CredentialFramework.FORMAT_ID, "H7", Duration.ofDays(365), subject, + leaf); } private static LeafCertificatePolicy leaf(Set usages, Set algorithms, Set ekus) { return new LeafCertificatePolicy( new SubjectAlternativeNamePolicy(1, 1, - List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 1, 1, - false, false)), + List.of(new SubjectAlternativeNameRule(SubjectAlternativeNameType.DNS_NAME, 1, 1, false, + false)), false, Set.of(), false, true, false), usages, ekus, true, false, true, algorithms); } @@ -419,8 +400,7 @@ final class H7ProfileEnforcementTest { private static PKCS10CertificationRequest csr(KeyPair pair, X500Name subject, List extensionRequests) throws Exception { - PKCS10CertificationRequestBuilder builder = - new JcaPKCS10CertificationRequestBuilder(subject, pair.getPublic()); + PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subject, pair.getPublic()); for (ASN1Encodable extensions : extensionRequests) { builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions); } @@ -451,8 +431,7 @@ final class H7ProfileEnforcementTest { private static ValidatedCertificateRequest validatedRequest(KeyPair pair) { return new ValidatedCertificateRequest(new PkiId("ca:h7"), new CertificateProfileRef("h7", 1, new byte[CertificateProfileRef.HASH_BYTES]), - new SubjectRef("CN=Leaf"), - List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "Leaf")), + new SubjectRef("CN=Leaf"), List.of(new SubjectRdn(SubjectRdnType.COMMON_NAME, "Leaf")), List.of(new SubjectAlternativeName.DnsName("example.com")), false, new EncodedObject(Encoding.DER, pair.getPublic().getEncoded()), new Validity(NOW, NOW.plus(Duration.ofDays(1))), Set.of(LeafKeyUsage.DIGITAL_SIGNATURE), diff --git a/pki/src/test/java/zeroecho/pki/impl/core/StoreBackedEffectiveCredentialStatusResolverTest.java b/pki/src/test/java/zeroecho/pki/impl/core/StoreBackedEffectiveCredentialStatusResolverTest.java index 0968729..e0d8fb4 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/StoreBackedEffectiveCredentialStatusResolverTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/StoreBackedEffectiveCredentialStatusResolverTest.java @@ -96,19 +96,16 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { assertStatus(expiredMetadata, Optional.empty(), EffectiveCredentialStatus.EXPIRED); Credential held = credential("held", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); - assertStatus(held, revocation(held, RevocationReason.CERTIFICATE_HOLD, NOW), - EffectiveCredentialStatus.HELD); + assertStatus(held, revocation(held, RevocationReason.CERTIFICATE_HOLD, NOW), EffectiveCredentialStatus.HELD); - Credential revoked = credential("revoked", CredentialStatus.ISSUED, NOW.minusSeconds(60), - NOW.plusSeconds(60)); + Credential revoked = credential("revoked", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); assertStatus(revoked, revocation(revoked, RevocationReason.KEY_COMPROMISE, NOW), EffectiveCredentialStatus.PERMANENTLY_REVOKED); } @Test void embeddedRevokedIsPermanentFloorAcrossHoldAndUnholdRecords() { - Credential revoked = credential("floor", CredentialStatus.REVOKED, NOW.minusSeconds(60), - NOW.plusSeconds(60)); + Credential revoked = credential("floor", CredentialStatus.REVOKED, NOW.minusSeconds(60), NOW.plusSeconds(60)); assertStatus(revoked, Optional.empty(), EffectiveCredentialStatus.PERMANENTLY_REVOKED); assertStatus(revoked, revocation(revoked, RevocationReason.CERTIFICATE_HOLD, NOW), EffectiveCredentialStatus.PERMANENTLY_REVOKED); @@ -120,8 +117,7 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { void futureAndMismatchedJournalsFailClosed() { Credential credential = credential("invalid", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); - assertResolutionFailure(credential, - revocation(credential, RevocationReason.KEY_COMPROMISE, NOW.plusNanos(1))); + assertResolutionFailure(credential, revocation(credential, RevocationReason.KEY_COMPROMISE, NOW.plusNanos(1))); RevocationJournal mismatch = new RevocationJournal(new PkiId("credential:other"), List.of(new RevocationTransition(1L, RevocationState.PERMANENTLY_REVOKED, NOW, Optional.of(RevocationReason.KEY_COMPROMISE), new SimpleAttributeSet()))); @@ -135,17 +131,15 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { PkiStore failing = store(id -> { throw new IllegalStateException("DO_NOT_EXPOSE_REVOCATION_SENTINEL"); }, new AtomicInteger()); - StoreBackedEffectiveCredentialStatusResolver resolver = - new StoreBackedEffectiveCredentialStatusResolver(failing, Clock.fixed(NOW, ZoneOffset.UTC)); + StoreBackedEffectiveCredentialStatusResolver resolver = new StoreBackedEffectiveCredentialStatusResolver( + failing, Clock.fixed(NOW, ZoneOffset.UTC)); - PkiException failure = assertThrows(PkiException.class, - () -> resolver.beginEvaluation().resolve(credential)); + PkiException failure = assertThrows(PkiException.class, () -> resolver.beginEvaluation().resolve(credential)); assertEquals("Credential status resolution failed: code=CREDENTIAL_STATUS_RESOLUTION_FAILED", failure.getMessage()); assertFalse(messages(failure).contains("DO_NOT_EXPOSE_REVOCATION_SENTINEL")); - Credential held = credential("not-usable", CredentialStatus.ISSUED, NOW.minusSeconds(60), - NOW.plusSeconds(60)); + Credential held = credential("not-usable", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); EffectiveCredentialStatusResolver.Evaluation evaluation = resolver( revocation(held, RevocationReason.CERTIFICATE_HOLD, NOW)).beginEvaluation(); PkiException rejected = assertThrows(PkiException.class, @@ -157,8 +151,8 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { void oneEvaluationCapturesClockOnceAndLooksUpEachCredentialOnce() { CountingClock clock = new CountingClock(NOW); AtomicInteger lookups = new AtomicInteger(); - StoreBackedEffectiveCredentialStatusResolver resolver = - new StoreBackedEffectiveCredentialStatusResolver(store(id -> Optional.empty(), lookups), clock); + StoreBackedEffectiveCredentialStatusResolver resolver = new StoreBackedEffectiveCredentialStatusResolver( + store(id -> Optional.empty(), lookups), clock); EffectiveCredentialStatusResolver.Evaluation evaluation = resolver.beginEvaluation(); Credential first = credential("first", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); Credential second = credential("second", CredentialStatus.ISSUED, NOW.minusSeconds(60), NOW.plusSeconds(60)); @@ -176,9 +170,8 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { Credential credential = new Credential(new PkiId("credential:audit"), new FormatId(sentinel), new IssuerRef(new PkiId("ca:issuer")), new SubjectRef("CN=Audit"), new Validity(NOW.minusSeconds(60), NOW.plusSeconds(60)), "audit", new PkiId("key:audit"), - new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, new byte[] { 1 }), - new SimpleAttributeSet()); + new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), + CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, new byte[] { 1 }), new SimpleAttributeSet()); AtomicReference recorded = new AtomicReference<>(); CredentialTrustAudit.rejected(recorded::set, NOW, credential, CredentialUse.END_ENTITY_ISSUER, @@ -221,16 +214,16 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { private static Optional revocation(Credential credential, RevocationReason reason, Instant time) { if (reason == RevocationReason.REMOVE_FROM_CRL) { - return Optional.of(new RevocationJournal(credential.credentialId(), List.of( - new RevocationTransition(1L, RevocationState.HELD, time.minusNanos(1), Optional.empty(), + return Optional.of(new RevocationJournal(credential.credentialId(), + List.of(new RevocationTransition(1L, RevocationState.HELD, time.minusNanos(1), Optional.empty(), new SimpleAttributeSet()), - new RevocationTransition(2L, RevocationState.CLEAR, time, Optional.empty(), - new SimpleAttributeSet())))); + new RevocationTransition(2L, RevocationState.CLEAR, time, Optional.empty(), + new SimpleAttributeSet())))); } - RevocationState state = reason == RevocationReason.CERTIFICATE_HOLD - ? RevocationState.HELD : RevocationState.PERMANENTLY_REVOKED; - Optional permanentReason = state == RevocationState.PERMANENTLY_REVOKED - ? Optional.of(reason) : Optional.empty(); + RevocationState state = reason == RevocationReason.CERTIFICATE_HOLD ? RevocationState.HELD + : RevocationState.PERMANENTLY_REVOKED; + Optional permanentReason = state == RevocationState.PERMANENTLY_REVOKED ? Optional.of(reason) + : Optional.empty(); return Optional.of(new RevocationJournal(credential.credentialId(), List.of(new RevocationTransition(1L, state, time, permanentReason, new SimpleAttributeSet())))); } @@ -239,8 +232,8 @@ final class StoreBackedEffectiveCredentialStatusResolverTest { return new Credential(new PkiId("credential:" + suffix), new FormatId("x509"), new IssuerRef(new PkiId("ca:issuer")), new SubjectRef("CN=" + suffix), new Validity(notBefore, notAfter), suffix, new PkiId("key:" + suffix), - new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), status, - new EncodedObject(Encoding.DER, new byte[] { 1, 2, 3 }), new SimpleAttributeSet()); + new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), + status, new EncodedObject(Encoding.DER, new byte[] { 1, 2, 3 }), new SimpleAttributeSet()); } private static String messages(Throwable throwable) { diff --git a/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusFailureTest.java b/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusFailureTest.java index 2102192..862c5b5 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusFailureTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusFailureTest.java @@ -87,8 +87,7 @@ final class PkiSigningBusFailureTest { @Test void acceptedCancellationWaitsForObservedTerminalProviderState(@TempDir Path tempDir) throws Exception { AcceptedDelayedCancellationWorkflow signer = new AcceptedDelayedCancellationWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { PkiId id = submit(bus); assertEquals(AsyncState.RUNNING, bus.status(id).orElseThrow().state()); @@ -110,14 +109,12 @@ final class PkiSigningBusFailureTest { } @Test - void deadlineAdvisoryStillRequiresProviderCancellationBeforeRetirement(@TempDir Path tempDir) - throws Exception { + void deadlineAdvisoryStillRequiresProviderCancellationBeforeRetirement(@TempDir Path tempDir) throws Exception { Instant createdAt = Instant.parse("2026-06-07T08:09:10Z"); MutableClock clock = new MutableClock(createdAt); ControlledWorkflow signer = new ControlledWorkflow(clock); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults(), clock); - PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults(), + clock); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { PkiId id = submit(bus, Duration.ofSeconds(10)); assertEquals(AsyncState.RUNNING, bus.status(id).orElseThrow().state()); clock.set(createdAt.plusSeconds(10)); @@ -201,8 +198,7 @@ final class PkiSigningBusFailureTest { @Test void providerCallbackReturnsWhileSameOperationSubmissionIsBlocked(@TempDir Path tempDir) throws Exception { BlockingWorkflow signer = new BlockingWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { PkiId id = submit(bus); @@ -228,10 +224,8 @@ final class PkiSigningBusFailureTest { id = submit(bus); assertEquals(AsyncState.RUNNING, bus.status(id).orElseThrow().state()); SignWorkflowStore.Record dispatched = store.getSignRecord(id).orElseThrow(); - store.transitionSign(id, dispatched.revision(), dispatched.fence(), - SignWorkflowStore.State.CANCELLING, Optional.of("CANCEL_REQUESTED"), Optional.empty(), - Optional.empty()) - .orElseThrow(); + store.transitionSign(id, dispatched.revision(), dispatched.fence(), SignWorkflowStore.State.CANCELLING, + Optional.of("CANCEL_REQUESTED"), Optional.empty(), Optional.empty()).orElseThrow(); } try (FilesystemPkiStore reopened = new FilesystemPkiStore(storeRoot, FsPkiStoreOptions.defaults()); PkiSigningBus replayed = new PkiSigningBus(reopened, signer, busLog)) { @@ -245,8 +239,7 @@ final class PkiSigningBusFailureTest { @Test void retirementReconcilesCompletionAndAdvisoryCallbackWithoutResultLoss(@TempDir Path tempDir) throws Exception { RetirementRaceWorkflow signer = new RetirementRaceWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { PkiId id = submit(bus); @@ -271,8 +264,7 @@ final class PkiSigningBusFailureTest { @Test void pollAndRetirementSerializeAndPreserveProviderCompletion(@TempDir Path tempDir) throws Exception { RetirementRaceWorkflow signer = new RetirementRaceWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { PkiId id = submit(bus); @@ -295,8 +287,7 @@ final class PkiSigningBusFailureTest { @Test void consumeAndRetirementShareCoordinatorWithoutResultLoss(@TempDir Path tempDir) throws Exception { RetirementRaceWorkflow signer = new RetirementRaceWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { PkiId id = submit(bus); @@ -322,14 +313,12 @@ final class PkiSigningBusFailureTest { } @Test - void preDispatchExpiryMakesNoProviderCallAndPurgesOnlyAfterRetirement(@TempDir Path tempDir) - throws Exception { + void preDispatchExpiryMakesNoProviderCallAndPurgesOnlyAfterRetirement(@TempDir Path tempDir) throws Exception { Instant createdAt = Instant.parse("2026-05-06T07:08:09Z"); MutableClock clock = new MutableClock(createdAt); ControlledWorkflow signer = new ControlledWorkflow(clock); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults(), clock); - PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults(), + clock); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { PkiId id = submit(bus, Duration.ofSeconds(10)); assertEquals(1, store.listWorkflowStates().size()); clock.set(createdAt.plusSeconds(10)); @@ -347,14 +336,12 @@ final class PkiSigningBusFailureTest { } @Test - void postDispatchExpiryRetainsAuthorityUntilProviderTerminalReconciliation(@TempDir Path tempDir) - throws Exception { + void postDispatchExpiryRetainsAuthorityUntilProviderTerminalReconciliation(@TempDir Path tempDir) throws Exception { Instant createdAt = Instant.parse("2026-05-06T07:08:09Z"); MutableClock clock = new MutableClock(createdAt); AcceptedDelayedCancellationWorkflow signer = new AcceptedDelayedCancellationWorkflow(clock); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults(), clock); - PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults(), + clock); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { PkiId id = submit(bus, Duration.ofSeconds(10)); assertEquals(AsyncState.RUNNING, bus.status(id).orElseThrow().state()); assertEquals(1, signer.submissions.get()); @@ -362,8 +349,7 @@ final class PkiSigningBusFailureTest { clock.set(createdAt.plusSeconds(10)); bus.sweep(clock.instant()); assertEquals(SignWorkflowStore.State.CANCELLING, store.getSignRecord(id).orElseThrow().state()); - assertEquals("CANCEL_SUBMITTED", - store.getSignRecord(id).orElseThrow().detailCode().orElseThrow()); + assertEquals("CANCEL_SUBMITTED", store.getSignRecord(id).orElseThrow().detailCode().orElseThrow()); assertEquals(1, signer.cancellations.get()); assertEquals(1, store.listWorkflowStates().size()); assertTrue(bus.consumeResult(id).isEmpty()); @@ -388,16 +374,16 @@ final class PkiSigningBusFailureTest { void blockedProviderCannotRedispatchAfterLeaseExpiry(@TempDir Path tempDir) throws Exception { MutableClock clock = new MutableClock(Instant.parse("2026-05-06T07:08:09Z")); BlockingWorkflow signer = new BlockingWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults(), clock); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults(), + clock); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { Principal owner = new Principal("TEST", "owner"); PkiId id = bus.newSubmissionId(); EncodedObject payload = new EncodedObject(Encoding.BINARY, new byte[] { 1 }); AccessContext access = new AccessContext(owner, new Purpose("TEST"), Optional.empty(), Optional.empty()); - PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, - "SHA256withRSA", payload, new KeyRef("test"), Encoding.BINARY, Optional.empty()); + PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, "SHA256withRSA", + payload, new KeyRef("test"), Encoding.BINARY, Optional.empty()); bus.submitSign(id, owner, new KeyRef("test"), "SHA256withRSA", payload, Duration.ofMinutes(5), Optional.of(continuation.encode())); Future first = executor.submit(() -> bus.status(id)); @@ -420,8 +406,7 @@ final class PkiSigningBusFailureTest { @Test void providerCallsPermitCrossThreadReentryOutsideOperationLock(@TempDir Path tempDir) throws Exception { CrossThreadReentrantWorkflow signer = new CrossThreadReentrantWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { signer.attach(bus); PkiId id = submit(bus); @@ -439,8 +424,7 @@ final class PkiSigningBusFailureTest { @Test void blockedSubmissionDoesNotBlockAnotherOperation(@TempDir Path tempDir) throws Exception { SelectiveBlockingWorkflow signer = new SelectiveBlockingWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log")); ExecutorService executor = Executors.newFixedThreadPool(2)) { PkiId blocked = submit(bus); @@ -448,8 +432,8 @@ final class PkiSigningBusFailureTest { assertTrue(signer.blockedEntered.await(5, TimeUnit.SECONDS)); PkiId independent = submit(bus); - Future> second = - executor.submit(() -> bus.status(independent)); + Future> second = executor + .submit(() -> bus.status(independent)); assertEquals(AsyncState.RUNNING, second.get(5, TimeUnit.SECONDS).orElseThrow().state()); assertEquals(2, signer.submissions.get()); @@ -461,8 +445,7 @@ final class PkiSigningBusFailureTest { @Test void providerStatusExceptionReleasesSingleFlightReservation(@TempDir Path tempDir) throws Exception { ThrowOnceStatusWorkflow signer = new ThrowOnceStatusWorkflow(); - try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore store = new FilesystemPkiStore(tempDir.resolve("store"), FsPkiStoreOptions.defaults()); PkiSigningBus bus = new PkiSigningBus(store, signer, tempDir.resolve("bus.log"))) { PkiId id = submit(bus); PkiException sanitized = assertThrows(PkiException.class, () -> bus.status(id)); @@ -482,10 +465,9 @@ final class PkiSigningBusFailureTest { Principal owner = new Principal("SYSTEM", "pki"); Instant now = Instant.now(); EncodedObject unsupported = new EncodedObject(Encoding.BINARY, new byte[] { 99 }); - store.putWorkflowState(new WorkflowStateRecord(new PkiId("unsupported-sign"), - PkiSigningBus.TYPE_SIGN, owner, - OrchestrationDurabilityPolicy.DURABLE_MIN_STATE, now, now, now.plusSeconds(60), Encoding.BINARY, - Optional.of(unsupported))); + store.putWorkflowState(new WorkflowStateRecord(new PkiId("unsupported-sign"), PkiSigningBus.TYPE_SIGN, + owner, OrchestrationDurabilityPolicy.DURABLE_MIN_STATE, now, now, now.plusSeconds(60), + Encoding.BINARY, Optional.of(unsupported))); InMemorySignatureWorkflow signer = new InMemorySignatureWorkflow(Map.of(), false); try { assertThrows(PkiException.class, @@ -522,8 +504,8 @@ final class PkiSigningBusFailureTest { operationId = bus.canonicalizeOperationId(new PkiId("sign:failure"), owner); EncodedObject payload = new EncodedObject(Encoding.BINARY, new byte[] { 1, 2, 3 }); AccessContext access = new AccessContext(owner, new Purpose("TEST"), Optional.empty(), Optional.empty()); - PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, - "SHA256withRSA", payload, keyRef, Encoding.BINARY, Optional.empty()); + PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, "SHA256withRSA", + payload, keyRef, Encoding.BINARY, Optional.empty()); bus.submitSign(operationId, owner, keyRef, "SHA256withRSA", payload, Duration.ofSeconds(5), Optional.of(continuation.encode())); bus.sweep(java.time.Instant.now()); @@ -552,8 +534,8 @@ final class PkiSigningBusFailureTest { PkiId id = bus.newSubmissionId(); EncodedObject payload = new EncodedObject(Encoding.BINARY, new byte[] { 1 }); AccessContext access = new AccessContext(owner, new Purpose("TEST"), Optional.empty(), Optional.empty()); - PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, - "SHA256withRSA", payload, new KeyRef("test"), Encoding.BINARY, Optional.empty()); + PkiSigningBus.SignContinuation continuation = new PkiSigningBus.SignContinuation(access, "SHA256withRSA", + payload, new KeyRef("test"), Encoding.BINARY, Optional.empty()); bus.submitSign(id, owner, new KeyRef("test"), "SHA256withRSA", payload, ttl, Optional.of(continuation.encode())); return id; @@ -581,8 +563,8 @@ final class PkiSigningBusFailureTest { @Override public PkiId submitSign(SignRequest request) { submissions.incrementAndGet(); - statuses.put(request.submissionId(), new OperationStatus(State.RUNNING, clock.instant(), - Optional.of("RUNNING"), Optional.empty())); + statuses.put(request.submissionId(), + new OperationStatus(State.RUNNING, clock.instant(), Optional.of("RUNNING"), Optional.empty())); return request.submissionId(); } @@ -622,13 +604,13 @@ final class PkiSigningBusFailureTest { private void completeSuccessfully(PkiId operationId) { OperationResult result = new OperationResult( Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 21 })), Optional.empty()); - statuses.put(operationId, new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), - Optional.of(result))); + statuses.put(operationId, + new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), Optional.of(result))); } private void completeCancelled(PkiId operationId, Instant completedAt) { - statuses.put(operationId, new OperationStatus(State.CANCELLED, completedAt, - Optional.of("CANCELLED"), Optional.empty())); + statuses.put(operationId, + new OperationStatus(State.CANCELLED, completedAt, Optional.of("CANCELLED"), Optional.empty())); } } @@ -650,8 +632,8 @@ final class PkiSigningBusFailureTest { @Override public PkiId submitSign(SignRequest request) { submissions.incrementAndGet(); - statuses.put(request.submissionId(), new OperationStatus(State.RUNNING, clock.instant(), - Optional.of("RUNNING"), Optional.empty())); + statuses.put(request.submissionId(), + new OperationStatus(State.RUNNING, clock.instant(), Optional.of("RUNNING"), Optional.empty())); return request.submissionId(); } @@ -668,8 +650,8 @@ final class PkiSigningBusFailureTest { @Override public boolean cancel(PkiId operationId, long fencingToken, String reason) { cancellations.incrementAndGet(); - statuses.put(operationId, new OperationStatus(State.CANCELLED, clock.instant(), - Optional.of("CANCELLED"), Optional.empty())); + statuses.put(operationId, + new OperationStatus(State.CANCELLED, clock.instant(), Optional.of("CANCELLED"), Optional.empty())); return true; } @@ -693,8 +675,8 @@ final class PkiSigningBusFailureTest { private void succeedAt(PkiId operationId, Instant completedAt, byte value) { OperationResult result = new OperationResult( Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { value })), Optional.empty()); - statuses.put(operationId, new OperationStatus(State.SUCCEEDED, completedAt, Optional.of("SIGNED"), - Optional.of(result))); + statuses.put(operationId, + new OperationStatus(State.SUCCEEDED, completedAt, Optional.of("SIGNED"), Optional.of(result))); } } @@ -748,8 +730,7 @@ final class PkiSigningBusFailureTest { return false; } cancellations.incrementAndGet(); - status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), - Optional.empty())); + status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), Optional.empty())); return true; } @@ -776,8 +757,7 @@ final class PkiSigningBusFailureTest { private void completeWithoutNotification() { OperationResult result = new OperationResult( Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 7 })), Optional.empty()); - status.set(new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), - Optional.of(result))); + status.set(new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), Optional.of(result))); statusRelease.countDown(); } @@ -822,8 +802,7 @@ final class PkiSigningBusFailureTest { cancellations.incrementAndGet(); OperationResult result = new OperationResult( Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 11, 12 })), Optional.empty()); - status.set(new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), - Optional.of(result))); + status.set(new OperationStatus(State.SUCCEEDED, Instant.now(), Optional.of("SIGNED"), Optional.of(result))); return false; } @@ -860,8 +839,8 @@ final class PkiSigningBusFailureTest { @Override public PkiId submitSign(SignRequest request) { submissions.incrementAndGet(); - statuses.put(request.submissionId(), new OperationStatus(State.RUNNING, Instant.now(), - Optional.of("RUNNING"), Optional.empty())); + statuses.put(request.submissionId(), + new OperationStatus(State.RUNNING, Instant.now(), Optional.of("RUNNING"), Optional.empty())); entered.countDown(); try { release.await(); @@ -936,8 +915,7 @@ final class PkiSigningBusFailureTest { @Override public PkiId submitSign(SignRequest request) { submissions.incrementAndGet(); - status.set(new OperationStatus(State.RUNNING, Instant.now(), Optional.of("RUNNING"), - Optional.empty())); + status.set(new OperationStatus(State.RUNNING, Instant.now(), Optional.of("RUNNING"), Optional.empty())); reenterStatus(request.submissionId()); return request.submissionId(); } @@ -958,8 +936,7 @@ final class PkiSigningBusFailureTest { public boolean cancel(PkiId requested, long fencingToken, String reason) { cancellations.incrementAndGet(); reenterStatus(requested); - status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), - Optional.empty())); + status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), Optional.empty())); return true; } @@ -1004,8 +981,8 @@ final class PkiSigningBusFailureTest { @Override public PkiId submitSign(SignRequest request) { submissions.incrementAndGet(); - statuses.put(request.submissionId(), new OperationStatus(State.RUNNING, Instant.now(), - Optional.of("RUNNING"), Optional.empty())); + statuses.put(request.submissionId(), + new OperationStatus(State.RUNNING, Instant.now(), Optional.of("RUNNING"), Optional.empty())); if (blockedOperation.compareAndSet(null, request.submissionId()) || blockedOperation.get().equals(request.submissionId())) { blockedEntered.countDown(); @@ -1031,8 +1008,8 @@ final class PkiSigningBusFailureTest { @Override public boolean cancel(PkiId operationId, long fencingToken, String reason) { - statuses.put(operationId, new OperationStatus(State.CANCELLED, Instant.now(), - Optional.of("CANCELLED"), Optional.empty())); + statuses.put(operationId, + new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), Optional.empty())); return true; } @@ -1084,8 +1061,7 @@ final class PkiSigningBusFailureTest { @Override public boolean cancel(PkiId operationId, long fencingToken, String reason) { - status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), - Optional.empty())); + status.set(new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), Optional.empty())); return true; } diff --git a/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusOperatorApprovalTest.java b/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusOperatorApprovalTest.java index c8c400f..d9f765e 100644 --- a/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusOperatorApprovalTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/core/async/PkiSigningBusOperatorApprovalTest.java @@ -77,10 +77,9 @@ public final class PkiSigningBusOperatorApprovalTest { Map.of(keyRef.value(), pair.getPrivate()))) { AccessContext access = new AccessContext(new Principal("USER", "late"), new Purpose("ISSUANCE"), Optional.empty(), Optional.empty()); - SignatureWorkflow.SignRequest request = SignatureWorkflow.SignRequest.create( - new PkiId("late-approval"), "test", 1L, access, keyRef, "SHA256withRSA", - new EncodedObject(Encoding.BINARY, new byte[] { 1 }), Optional.of(Encoding.BINARY), - Optional.of(Instant.EPOCH)); + SignatureWorkflow.SignRequest request = SignatureWorkflow.SignRequest.create(new PkiId("late-approval"), + "test", 1L, access, keyRef, "SHA256withRSA", new EncodedObject(Encoding.BINARY, new byte[] { 1 }), + Optional.of(Encoding.BINARY), Optional.of(Instant.EPOCH)); PkiId operationId = signer.submitSign(request); signer.approve(operationId); diff --git a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/TestKeyringUnlocks.java b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/TestKeyringUnlocks.java index 2109caa..8cd2fb4 100644 --- a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/TestKeyringUnlocks.java +++ b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/TestKeyringUnlocks.java @@ -8,7 +8,6 @@ public final class TestKeyringUnlocks { } public static KeyringUnlockProvider provider() { - return () -> new KeyringPassword( - new char[] { 'p', 'k', 'i', '-', 't', 'e', 's', 't', '-', 'k', 'e', 'y' }); + return () -> new KeyringPassword(new char[] { 'p', 'k', 'i', '-', 't', 'e', 's', 't', '-', 'k', 'e', 'y' }); } } diff --git a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibKeyRefParsingTest.java b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibKeyRefParsingTest.java index 0204b54..289869e 100644 --- a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibKeyRefParsingTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibKeyRefParsingTest.java @@ -67,16 +67,16 @@ public final class ZeroEchoLibKeyRefParsingTest { System.out.println("signing_requires_prv_suffix_in_strict_mode_ok"); try (ZeroEchoLibSignatureWorkflow wf = new ZeroEchoLibSignatureWorkflow("zeroecho-lib", Path.of("nonexistent"), - tempDir.resolve("operations-1"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider())) { + tempDir.resolve("operations-1"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, + TestKeyringUnlocks.provider())) { AccessContext ctx = new AccessContext(new Principal("TEST", "unit"), new Purpose("UNIT_TEST"), Optional.empty(), Optional.empty()); PkiId submissionId = SigningSubmissionId.create(NAMESPACE, Instant.now(), new SecureRandom()).id(); - SignatureWorkflow.SignRequest req = SignatureWorkflow.SignRequest.create(submissionId, NAMESPACE, 1L, - ctx, new KeyRef("zeroecho-lib:abc"), "ECDSA", - new EncodedObject(Encoding.BINARY, new byte[] { 0x01 }), Optional.of(Encoding.BINARY), - Optional.of(Instant.now())); + SignatureWorkflow.SignRequest req = SignatureWorkflow.SignRequest.create(submissionId, NAMESPACE, 1L, ctx, + new KeyRef("zeroecho-lib:abc"), "ECDSA", new EncodedObject(Encoding.BINARY, new byte[] { 0x01 }), + Optional.of(Encoding.BINARY), Optional.of(Instant.now())); PkiId opId = wf.submitSign(req); SignatureWorkflow.OperationStatus st = wf.status(opId); @@ -95,7 +95,8 @@ public final class ZeroEchoLibKeyRefParsingTest { System.out.println("verify_with_publicKeyEncoded_invalid_spki_fails_with_crypto_failure_ok"); try (ZeroEchoLibSignatureWorkflow wf = new ZeroEchoLibSignatureWorkflow("zeroecho-lib", Path.of("nonexistent"), - tempDir.resolve("operations-2"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider())) { + tempDir.resolve("operations-2"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, + TestKeyringUnlocks.provider())) { AccessContext ctx = new AccessContext(new Principal("TEST", "unit"), new Purpose("UNIT_TEST"), Optional.empty(), Optional.empty()); @@ -123,7 +124,8 @@ public final class ZeroEchoLibKeyRefParsingTest { System.out.println("status_unknown_operation_is_deterministic_ok"); try (ZeroEchoLibSignatureWorkflow wf = new ZeroEchoLibSignatureWorkflow("zeroecho-lib", Path.of("nonexistent"), - tempDir.resolve("operations-3"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider())) { + tempDir.resolve("operations-3"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, + TestKeyringUnlocks.provider())) { PkiId unknown = new PkiId("00000000-0000-0000-0000-000000000000"); SignatureWorkflow.OperationStatus st = wf.status(unknown); diff --git a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowPersistenceTest.java b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowPersistenceTest.java index 40c03fc..a3b9575 100644 --- a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowPersistenceTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowPersistenceTest.java @@ -88,8 +88,7 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { Instant now = Instant.parse("2026-02-03T04:05:06.789Z"); Path keyring = root.resolve("keyring.txt"); KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore keyringStore = KeyringStore.create(keyring, password)) { keyringStore.putPrivate("test.prv", "RSA", pair.getPrivate()); keyringStore.putPublic("test.pub", "RSA", pair.getPublic()); @@ -120,7 +119,8 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { logger.addHandler(handler); try (ZeroEchoLibSignatureWorkflow workflow = new ZeroEchoLibSignatureWorkflow("zeroecho-lib", keyring, root.resolve("cleanup-operations"), Clock.fixed(now, ZoneOffset.UTC), Duration.ofDays(90), - "zeroecho-lib:", true, TestKeyringUnlocks.provider(), (category, bytes) -> cleared.add(new ObservedBuffer(category, bytes))); + "zeroecho-lib:", true, TestKeyringUnlocks.provider(), + (category, bytes) -> cleared.add(new ObservedBuffer(category, bytes))); SignatureWorkflow.Registration registration = workflow.register((operationId, status) -> { throw new IllegalStateException("DO_NOT_LOG_SIGNATURE_SENTINEL"); })) { @@ -133,22 +133,20 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { assertTrue(signature.bytes().length > 0); AccessContext access = signRequest.accessContext(); - SignatureWorkflow.VerifyRequest verifyRequest = new SignatureWorkflow.VerifyRequest(access, - "SHA256withRSA", new EncodedObject(Encoding.BINARY, message), signature, + SignatureWorkflow.VerifyRequest verifyRequest = new SignatureWorkflow.VerifyRequest(access, "SHA256withRSA", + new EncodedObject(Encoding.BINARY, message), signature, Optional.of(new KeyRef("zeroecho-lib:test.pub")), Optional.empty(), Optional.empty()); PkiId verifyId = workflow.submitVerify(verifyRequest); - assertEquals(Optional.of(true), - workflow.status(verifyId).result().orElseThrow().verified()); + assertEquals(Optional.of(true), workflow.status(verifyId).result().orElseThrow().verified()); byte[] invalidBytes = signature.bytes(); invalidBytes[0] ^= 0x01; SignatureWorkflow.VerifyRequest invalidRequest = new SignatureWorkflow.VerifyRequest(access, "SHA256withRSA", new EncodedObject(Encoding.BINARY, message), - new EncodedObject(Encoding.BINARY, invalidBytes), - Optional.of(new KeyRef("zeroecho-lib:test.pub")), Optional.empty(), Optional.empty()); + new EncodedObject(Encoding.BINARY, invalidBytes), Optional.of(new KeyRef("zeroecho-lib:test.pub")), + Optional.empty(), Optional.empty()); PkiId invalidId = workflow.submitVerify(invalidRequest); - assertEquals(Optional.of(false), - workflow.status(invalidId).result().orElseThrow().verified()); + assertEquals(Optional.of(false), workflow.status(invalidId).result().orElseThrow().verified()); assertTrue(cleared.stream().anyMatch(value -> "sign-payload".equals(value.category()))); assertTrue(cleared.stream().anyMatch(value -> "sign-result-copy".equals(value.category()))); @@ -157,8 +155,8 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { assertTrue(cleared.stream().anyMatch(value -> "persisted-operation-buffer".equals(value.category()))); assertTrue(cleared.stream().allMatch(value -> isCleared(value.bytes()))); assertTrue(records.stream().allMatch(record -> record.getThrown() == null)); - assertTrue(records.stream().noneMatch(record -> String.valueOf(record.getMessage()) - .contains("DO_NOT_LOG_SIGNATURE_SENTINEL"))); + assertTrue(records.stream().noneMatch( + record -> String.valueOf(record.getMessage()).contains("DO_NOT_LOG_SIGNATURE_SENTINEL"))); System.out.println("...clearedBuffers=" + cleared.size()); } finally { logger.removeHandler(handler); @@ -194,8 +192,7 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { Instant base = Instant.parse("2026-02-03T04:05:06Z"); Path keyring = root.resolve("keyring.txt"); KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore keyringStore = KeyringStore.create(keyring, password)) { keyringStore.putPrivate("test.prv", "RSA", pair.getPrivate()); keyringStore.putPublic("test.pub", "RSA", pair.getPublic()); @@ -248,15 +245,16 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { assertEquals(1, encoded.bytes()[0]); assertEquals(2, encoded.bytes()[1]); - PkiId id = SigningSubmissionId.create(NAMESPACE, Instant.parse("2026-02-03T04:05:06Z"), - new SecureRandom()).id(); + PkiId id = SigningSubmissionId.create(NAMESPACE, Instant.parse("2026-02-03T04:05:06Z"), new SecureRandom()) + .id(); SignatureWorkflow.SignRequest request = request(id, 1L, encoded.bytes()); byte[] requestBytes = request.payload().bytes(); requestBytes[2] = 9; assertEquals(3, request.payload().bytes()[2]); - assertEquals(request.semanticFingerprint(), SignatureWorkflow.SignRequest.fingerprint(NAMESPACE, - request.accessContext(), request.keyRef(), request.algorithmId(), request.payload(), - request.preferredSignatureEncoding(), request.deadline())); + assertEquals(request.semanticFingerprint(), + SignatureWorkflow.SignRequest.fingerprint(NAMESPACE, request.accessContext(), request.keyRef(), + request.algorithmId(), request.payload(), request.preferredSignatureEncoding(), + request.deadline())); } @Test @@ -345,14 +343,14 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { clock.release.countDown(); highRead.get(5, TimeUnit.SECONDS); rollbackRead.get(5, TimeUnit.SECONDS); - assertEquals(high.toEpochMilli(), Long.parseLong(Files.readString( - operations.resolve("TIME_WATERMARK")).trim())); + assertEquals(high.toEpochMilli(), + Long.parseLong(Files.readString(operations.resolve("TIME_WATERMARK")).trim())); } clock.set(base.minusSeconds(120)); try (ZeroEchoLibSignatureWorkflow restarted = workflow(root, operations, clock)) { restarted.status(new PkiId("unknown-restart")); - assertEquals(high.toEpochMilli(), Long.parseLong(Files.readString( - operations.resolve("TIME_WATERMARK")).trim())); + assertEquals(high.toEpochMilli(), + Long.parseLong(Files.readString(operations.resolve("TIME_WATERMARK")).trim())); } } @@ -361,8 +359,8 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { } private static ZeroEchoLibSignatureWorkflow workflow(Path root, Path operations, Path keyring, Clock clock) { - return new ZeroEchoLibSignatureWorkflow("zeroecho-lib", keyring, operations, clock, - Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider()); + return new ZeroEchoLibSignatureWorkflow("zeroecho-lib", keyring, operations, clock, Duration.ofDays(90), + "zeroecho-lib:", true, TestKeyringUnlocks.provider()); } private static SignatureWorkflow.SignRequest request(PkiId id, long fence, byte[] payload) { @@ -371,11 +369,10 @@ final class ZeroEchoLibSignatureWorkflowPersistenceTest { private static SignatureWorkflow.SignRequest request(PkiId id, long fence, byte[] payload, KeyRef keyRef, Optional deadline) { - AccessContext access = new AccessContext(new Principal("TEST", "owner"), new Purpose("TEST"), - Optional.empty(), Optional.empty()); - return SignatureWorkflow.SignRequest.create(id, NAMESPACE, fence, access, - keyRef, "SHA256withRSA", new EncodedObject(Encoding.BINARY, payload), - Optional.of(Encoding.BINARY), deadline); + AccessContext access = new AccessContext(new Principal("TEST", "owner"), new Purpose("TEST"), Optional.empty(), + Optional.empty()); + return SignatureWorkflow.SignRequest.create(id, NAMESPACE, fence, access, keyRef, "SHA256withRSA", + new EncodedObject(Encoding.BINARY, payload), Optional.of(Encoding.BINARY), deadline); } private static void forcePersistedStateCode(Path operations, int from, int to) { diff --git a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedEcdsaTest.java b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedEcdsaTest.java index 0dd25d8..eb580d9 100644 --- a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedEcdsaTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedEcdsaTest.java @@ -71,14 +71,14 @@ public final class ZeroEchoLibSignatureWorkflowVerifyEncodedEcdsaTest { System.out.println("verifyFromSpkiDerEcdsaSucceeds"); Path keyring = tempDir.resolve("keyring.txt"); - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore ignored = KeyringStore.create(keyring, password)) { // Empty keyring is sufficient for encoded-key verification. } try (ZeroEchoLibSignatureWorkflow wf = new ZeroEchoLibSignatureWorkflow("wf", keyring, - tempDir.resolve("operations"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider())) { + tempDir.resolve("operations"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, + TestKeyringUnlocks.provider())) { KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); kpg.initialize(new ECGenParameterSpec("secp256r1")); KeyPair kp = kpg.generateKeyPair(); diff --git a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedTest.java b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedTest.java index f660d0e..821a454 100644 --- a/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/crypto/zeroecholib/ZeroEchoLibSignatureWorkflowVerifyEncodedTest.java @@ -66,14 +66,14 @@ public final class ZeroEchoLibSignatureWorkflowVerifyEncodedTest { System.out.println("verifyFromSpkiDerSucceeds"); Path keyring = tempDir.resolve("keyring.txt"); - try (zeroecho.core.storage.KeyringPassword password = - TestKeyringUnlocks.provider().acquire(); + try (zeroecho.core.storage.KeyringPassword password = TestKeyringUnlocks.provider().acquire(); KeyringStore ignored = KeyringStore.create(keyring, password)) { // Empty keyring is sufficient for encoded-key verification. } try (ZeroEchoLibSignatureWorkflow wf = new ZeroEchoLibSignatureWorkflow("wf", keyring, - tempDir.resolve("operations"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, TestKeyringUnlocks.provider())) { + tempDir.resolve("operations"), Clock.systemUTC(), Duration.ofDays(90), "zeroecho-lib:", true, + TestKeyringUnlocks.provider())) { KeyPair kp = KeyPairGenerator.getInstance("RSA").generateKeyPair(); byte[] payload = "pqc-ready".getBytes(java.nio.charset.StandardCharsets.UTF_8); diff --git a/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSignerCleanupTest.java b/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSignerCleanupTest.java index 0319724..d63d441 100644 --- a/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSignerCleanupTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/PkiBusContentSignerCleanupTest.java @@ -80,9 +80,8 @@ final class PkiBusContentSignerCleanupTest { PkiSigningBus bus = new PkiSigningBus(store, signer, busLog)) { PkiBusContentSigner contentSigner = new PkiBusContentSigner(bus, issuerKeyRef, "SHA256withRSA", Duration.ofSeconds(5)); - contentSigner.getOutputStream().write(intermediate - ? subjectKey.getPublic().getEncoded() - : new byte[] { 1, 2, 3 }); + contentSigner.getOutputStream() + .write(intermediate ? subjectKey.getPublic().getEncoded() : new byte[] { 1, 2, 3 }); assertThrows(PkiException.class, contentSigner::getSignature); assertEquals(1, signer.submittedSignCount()); diff --git a/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/WorkflowProofOfPossessionVerifierTest.java b/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/WorkflowProofOfPossessionVerifierTest.java index aa5808c..1f8f71b 100644 --- a/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/WorkflowProofOfPossessionVerifierTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/framework/x509/bc/WorkflowProofOfPossessionVerifierTest.java @@ -71,8 +71,7 @@ public final class WorkflowProofOfPossessionVerifierTest { System.out.println("verifyRsaCsrViaWorkflow_ok"); Path keyringPath = tempDir.resolve("keyring.zek"); - try (KeyringPassword password = - zeroecho.pki.impl.crypto.zeroecholib.TestKeyringUnlocks.provider().acquire(); + try (KeyringPassword password = zeroecho.pki.impl.crypto.zeroecholib.TestKeyringUnlocks.provider().acquire(); KeyringStore ignored = KeyringStore.create(keyringPath, password)) { // Encoded-key verification needs no persisted key entry. } @@ -92,9 +91,8 @@ public final class WorkflowProofOfPossessionVerifierTest { ParsedCertificationRequest parsed = new BcX509CertificationRequestParser().parse(req); - ZeroEchoLibSignatureWorkflowProvider provider = - new ZeroEchoLibSignatureWorkflowProvider( - zeroecho.pki.impl.crypto.zeroecholib.TestKeyringUnlocks.provider()); + ZeroEchoLibSignatureWorkflowProvider provider = new ZeroEchoLibSignatureWorkflowProvider( + zeroecho.pki.impl.crypto.zeroecholib.TestKeyringUnlocks.provider()); ProviderConfig cfg = new ProviderConfig(provider.id(), Map.of("keyringPath", keyringPath.toString(), "operationRoot", tempDir.resolve("signing-operations").toString())); SignatureWorkflow wf = provider.allocate(cfg); diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreOwnershipTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreOwnershipTest.java index e5cb52a..56149b9 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreOwnershipTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreOwnershipTest.java @@ -79,8 +79,7 @@ final class FilesystemPkiStoreOwnershipTest { FilesystemPkiStore first = new FilesystemPkiStore(root, options); try { FilesystemPkiStoreTest.importProfile(first, - FilesystemPkiStoreTest.TestObjects.minimalProfile("ownership-profile"), - java.time.Instant.now()); + FilesystemPkiStoreTest.TestObjects.minimalProfile("ownership-profile"), java.time.Instant.now()); first.activateProfile("ownership-profile", 1); assertOwnershipRejected(root); assertTrue(first.getActiveProfileRef("ownership-profile").isPresent()); @@ -98,17 +97,14 @@ final class FilesystemPkiStoreOwnershipTest { @Test void separateStoreRootsRemainIndependent(@TempDir Path tempDir) throws Exception { System.out.println("separateStoreRootsRemainIndependent"); - try (FilesystemPkiStore first = new FilesystemPkiStore(tempDir.resolve("first"), - FsPkiStoreOptions.defaults()); + try (FilesystemPkiStore first = new FilesystemPkiStore(tempDir.resolve("first"), FsPkiStoreOptions.defaults()); FilesystemPkiStore second = new FilesystemPkiStore(tempDir.resolve("second"), FsPkiStoreOptions.defaults())) { FilesystemPkiStoreTest.importProfile(first, - FilesystemPkiStoreTest.TestObjects.minimalProfile("first-profile"), - java.time.Instant.now()); + FilesystemPkiStoreTest.TestObjects.minimalProfile("first-profile"), java.time.Instant.now()); first.activateProfile("first-profile", 1); FilesystemPkiStoreTest.importProfile(second, - FilesystemPkiStoreTest.TestObjects.minimalProfile("second-profile"), - java.time.Instant.now()); + FilesystemPkiStoreTest.TestObjects.minimalProfile("second-profile"), java.time.Instant.now()); second.activateProfile("second-profile", 1); assertTrue(first.getActiveProfileRef("first-profile").isPresent()); assertFalse(first.getActiveProfileRef("second-profile").isPresent()); @@ -125,8 +121,7 @@ final class FilesystemPkiStoreOwnershipTest { Files.createDirectories(root); Files.writeString(root.resolve(FsPaths.VERSION_FILE), "unsupported", StandardCharsets.US_ASCII); - assertThrows(IllegalStateException.class, - () -> new FilesystemPkiStore(root, FsPkiStoreOptions.defaults())); + assertThrows(IllegalStateException.class, () -> new FilesystemPkiStore(root, FsPkiStoreOptions.defaults())); Files.writeString(root.resolve(FsPaths.VERSION_FILE), FilesystemPkiStore.CURRENT_STORE_VERSION, StandardCharsets.US_ASCII); @@ -221,8 +216,9 @@ final class FilesystemPkiStoreOwnershipTest { } private static ChildOwner start(Path root) throws Exception { - String executable = System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT) - .contains("win") ? "java.exe" : "java"; + String executable = System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT).contains("win") + ? "java.exe" + : "java"; Path java = Path.of(System.getProperty("java.home"), "bin", executable); Process process = new ProcessBuilder(java.toString(), "-cp", childClasspath(), FilesystemPkiStoreLockProcess.class.getName(), root.toString()).redirectErrorStream(true).start(); @@ -290,10 +286,8 @@ final class FilesystemPkiStoreLockProcess { System.exit(2); } LogManager.getLogManager().reset(); - try (FilesystemPkiStore store = new FilesystemPkiStore(Path.of(args[0]), - FsPkiStoreOptions.defaults()); - BufferedReader control = new BufferedReader(new InputStreamReader(System.in, - StandardCharsets.UTF_8))) { + try (FilesystemPkiStore store = new FilesystemPkiStore(Path.of(args[0]), FsPkiStoreOptions.defaults()); + BufferedReader control = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) { System.out.println("LOCK_ACQUIRED"); System.out.flush(); String command; diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreTest.java index 86173c0..0483179 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemPkiStoreTest.java @@ -134,8 +134,8 @@ public final class FilesystemPkiStoreTest { Credential credential = TestObjects.minimalCredential("SERIAL-ALL", "profile-all"); ParsedCertificationRequest request = new ParsedCertificationRequest(new PkiId("request-all"), new FormatId("fmt-x509"), new SubjectRef("CN=request-all"), - new EncodedObject(Encoding.DER, new byte[] { 4, 5, 6 }), Optional.empty(), - Optional.of("profile-all"), attributes); + new EncodedObject(Encoding.DER, new byte[] { 4, 5, 6 }), Optional.empty(), Optional.of("profile-all"), + attributes); StatusObject status = new StatusObject(new PkiId("status-all"), new FormatId("fmt-x509"), ca.caId(), StatusObjectType.CRL, now, Optional.of(now.plusSeconds(60L)), new EncodedObject(Encoding.DER, new byte[] { 7, 8 }), attributes); @@ -238,10 +238,12 @@ public final class FilesystemPkiStoreTest { try (FilesystemPkiStore store = new FilesystemPkiStore(root, options)) { Credential credential = TestObjects.minimalCredential("SERIAL-REV", "profile-rev"); store.putCredential(credential); - store.transitionRevocation(new RevocationCommand.Hold(credential.credentialId(), - TestObjects.emptyAttributes()), Instant.EPOCH.plusSeconds(10L)); - store.transitionRevocation(new RevocationCommand.Unhold(credential.credentialId(), - TestObjects.emptyAttributes()), Instant.EPOCH.plusSeconds(11L)); + store.transitionRevocation( + new RevocationCommand.Hold(credential.credentialId(), TestObjects.emptyAttributes()), + Instant.EPOCH.plusSeconds(10L)); + store.transitionRevocation( + new RevocationCommand.Unhold(credential.credentialId(), TestObjects.emptyAttributes()), + Instant.EPOCH.plusSeconds(11L)); Optional loaded = store.getRevocationJournal(credential.credentialId()); assertTrue(loaded.isPresent()); @@ -337,8 +339,7 @@ public final class FilesystemPkiStoreTest { List profiles = s2.listProfileVersions("profile-a"); Set caIds = cas.stream().map(r -> r.caId().toString()).collect(Collectors.toSet()); - Set profileIds = profiles.stream().map(v -> v.reference().profileId()) - .collect(Collectors.toSet()); + Set profileIds = profiles.stream().map(v -> v.reference().profileId()).collect(Collectors.toSet()); System.out.println("...snapshot2 caIds: " + caIds); System.out.println("...snapshot2 profileIds: " + profileIds); @@ -528,16 +529,14 @@ public final class FilesystemPkiStoreTest { */ static void importProfile(FilesystemPkiStore store, CertificateProfile profile, Instant importedAt) throws Exception { - CertificateProfileDefinition definition = new CertificateProfileDefinition( - CertificateProfileKind.END_ENTITY, profile.profileId(), 1, profile.formatId(), - profile.displayName(), profile.maximumValidity(), profile.subjectPolicy(), - profile.leafPolicy()); + CertificateProfileDefinition definition = new CertificateProfileDefinition(CertificateProfileKind.END_ENTITY, + profile.profileId(), 1, profile.formatId(), profile.displayName(), profile.maximumValidity(), + profile.subjectPolicy(), profile.leafPolicy()); byte[] canonical = CertificateProfileDocumentCodec.writeCanonical(definition); byte[] hash = MessageDigest.getInstance("SHA-256").digest(canonical); CertificateProfileRef reference = new CertificateProfileRef(profile.profileId(), 1, hash); store.importProfileVersion(new ImportedCertificateProfileVersion(reference, - CertificateProfileDefinition.SCHEMA_VERSION, definition, canonical, - importedAt)); + CertificateProfileDefinition.SCHEMA_VERSION, definition, canonical, importedAt)); } static final class TestObjects { @@ -558,16 +557,15 @@ public final class FilesystemPkiStoreTest { static CertificateProfile minimalProfile(String profileId) { FormatId formatId = new FormatId("fmt-x509"); - SubjectPolicy subject = new SubjectPolicy(false, List.of(new SubjectRdnRule( - SubjectRdnType.COMMON_NAME, 1, 1, 256, Optional.empty(), true))); - SubjectAlternativeNamePolicy sans = new SubjectAlternativeNamePolicy(0, 0, - List.of(), false, Set.of(), false, false, false); + SubjectPolicy subject = new SubjectPolicy(false, + List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 256, Optional.empty(), true))); + SubjectAlternativeNamePolicy sans = new SubjectAlternativeNamePolicy(0, 0, List.of(), false, Set.of(), + false, false, false); LeafCertificatePolicy leaf = new LeafCertificatePolicy(sans, Set.of(LeafKeyUsage.DIGITAL_SIGNATURE, LeafKeyUsage.KEY_ENCIPHERMENT), - Set.of(new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1")), true, false, true, - Set.of("RSA")); - return new CertificateProfile(profileId, formatId, "Test leaf profile", - Duration.ofDays(365), subject, leaf); + Set.of(new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1")), true, false, true, Set.of("RSA")); + return new CertificateProfile(profileId, formatId, "Test leaf profile", Duration.ofDays(365), subject, + leaf); } static Credential minimalCredential(String serial, String profileId) { @@ -589,7 +587,9 @@ public final class FilesystemPkiStoreTest { AttributeSet attrs = emptyAttributes(); return new Credential(credentialId, formatId, issuerRef, subjectRef, validity, serial, publicKeyId, - new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef(profileId, 1, new byte[32])), status, encoded, attrs); + new CaProfileBinding( + new zeroecho.pki.api.profile.CertificateProfileRef(profileId, 1, new byte[32])), + status, encoded, attrs); } static AttributeSet emptyAttributes() { diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemProfileLifecycleTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemProfileLifecycleTest.java index 7a2e4fe..c1a0dba 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemProfileLifecycleTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemProfileLifecycleTest.java @@ -38,14 +38,14 @@ import zeroecho.pki.impl.audit.InMemoryAuditSink; import zeroecho.pki.impl.core.DefaultProfileService; /** - * Atomic persistence and fail-closed lookup evidence for profile lifecycle state. + * Atomic persistence and fail-closed lookup evidence for profile lifecycle + * state. */ final class FilesystemProfileLifecycleTest { private static final Clock CLOCK = Clock.fixed(Instant.parse("2026-07-30T10:00:00Z"), ZoneOffset.UTC); @Test - void precommitFailuresPreserveAbsentVersionAndPreviousActivePointer(@TempDir Path directory) - throws Exception { + void precommitFailuresPreserveAbsentVersionAndPreviousActivePointer(@TempDir Path directory) throws Exception { Path root = directory.resolve("store"); BuiltInCertificateProfileTemplate template = builtIn("server-tls"); byte[] versionTwo = version(template, 2, "Server TLS v2"); @@ -54,8 +54,7 @@ final class FilesystemProfileLifecycleTest { CertificateProfileRef one = service.importBuiltIn(template); service.activateProfile("server-tls", 1); - Path versionTemporary = new FsPaths(root).profileVersion("server-tls", 2) - .resolveSibling("2.bin.tmp"); + Path versionTemporary = new FsPaths(root).profileVersion("server-tls", 2).resolveSibling("2.bin.tmp"); Files.createDirectories(versionTemporary); Files.writeString(versionTemporary.resolve("block"), "blocked"); assertThrows(PkiException.class, () -> service.importProfile(versionTwo)); @@ -66,8 +65,7 @@ final class FilesystemProfileLifecycleTest { Files.delete(versionTemporary); CertificateProfileRef two = service.importProfile(versionTwo); - Path pointerTemporary = new FsPaths(root).profileActive("server-tls") - .resolveSibling("active.bin.tmp"); + Path pointerTemporary = new FsPaths(root).profileActive("server-tls").resolveSibling("active.bin.tmp"); Files.createDirectories(pointerTemporary); Files.writeString(pointerTemporary.resolve("block"), "blocked"); assertThrows(PkiException.class, () -> service.activateProfile("server-tls", 2)); @@ -78,8 +76,7 @@ final class FilesystemProfileLifecycleTest { } @Test - void corruptAndNoncanonicalVersionsFailClosedAndCannotBeOverwritten(@TempDir Path directory) - throws Exception { + void corruptAndNoncanonicalVersionsFailClosedAndCannotBeOverwritten(@TempDir Path directory) throws Exception { BuiltInCertificateProfileTemplate template = builtIn("server-tls"); Path corruptRoot = directory.resolve("corrupt"); try (FilesystemPkiStore store = store(corruptRoot)) { @@ -100,10 +97,9 @@ final class FilesystemProfileLifecycleTest { byte[] noncanonical = (new String(template.canonicalJson(), StandardCharsets.UTF_8) + "\n") .getBytes(StandardCharsets.UTF_8); ImportedCertificateProfileVersion invalid = new ImportedCertificateProfileVersion( - new CertificateProfileRef(template.definition().profileId(), - template.definition().profileVersion(), template.canonicalSha256()), - CertificateProfileDefinition.SCHEMA_VERSION, template.definition(), - noncanonical, CLOCK.instant()); + new CertificateProfileRef(template.definition().profileId(), template.definition().profileVersion(), + template.canonicalSha256()), + CertificateProfileDefinition.SCHEMA_VERSION, template.definition(), noncanonical, CLOCK.instant()); Path path = new FsPaths(noncanonicalRoot).profileVersion("server-tls", 1); FsOperations.ensureDir(path.getParent()); Files.write(path, FsCodec.encode(FsCodec.PROFILE_VERSION, invalid)); @@ -115,11 +111,10 @@ final class FilesystemProfileLifecycleTest { Path hashMismatchRoot = directory.resolve("hash-mismatch"); try (FilesystemPkiStore store = store(hashMismatchRoot)) { ImportedCertificateProfileVersion invalid = new ImportedCertificateProfileVersion( - new CertificateProfileRef(template.definition().profileId(), - template.definition().profileVersion(), + new CertificateProfileRef(template.definition().profileId(), template.definition().profileVersion(), new byte[CertificateProfileRef.HASH_BYTES]), - CertificateProfileDefinition.SCHEMA_VERSION, template.definition(), - template.canonicalJson(), CLOCK.instant()); + CertificateProfileDefinition.SCHEMA_VERSION, template.definition(), template.canonicalJson(), + CLOCK.instant()); Path path = new FsPaths(hashMismatchRoot).profileVersion("server-tls", 1); FsOperations.ensureDir(path.getParent()); Files.write(path, FsCodec.encode(FsCodec.PROFILE_VERSION, invalid)); @@ -130,14 +125,12 @@ final class FilesystemProfileLifecycleTest { } @Test - void unrelatedProfileIdsProceedWhileAnotherProfileCoordinatorIsHeld(@TempDir Path directory) - throws Exception { + void unrelatedProfileIdsProceedWhileAnotherProfileCoordinatorIsHeld(@TempDir Path directory) throws Exception { try (FilesystemPkiStore store = store(directory.resolve("store"))) { DefaultProfileService service = service(store); Method acquire = FilesystemPkiStore.class.getDeclaredMethod("acquireProfileLock", String.class); - Method release = FilesystemPkiStore.class.getDeclaredMethod("releaseProfileLock", - String.class, Class.forName( - "zeroecho.pki.impl.fs.FilesystemPkiStore$ProfileLockEntry")); + Method release = FilesystemPkiStore.class.getDeclaredMethod("releaseProfileLock", String.class, + Class.forName("zeroecho.pki.impl.fs.FilesystemPkiStore$ProfileLockEntry")); acquire.setAccessible(true); release.setAccessible(true); CountDownLatch held = new CountDownLatch(1); @@ -156,8 +149,8 @@ final class FilesystemProfileLifecycleTest { }); assertTrue(held.await(5, TimeUnit.SECONDS)); - Future unrelated = - executor.submit(() -> service.importBuiltIn(builtIn("email-signing"))); + Future unrelated = executor + .submit(() -> service.importBuiltIn(builtIn("email-signing"))); CertificateProfileRef imported = unrelated.get(5, TimeUnit.SECONDS); assertEquals("email-signing", imported.profileId()); assertTrue(service.getImportedVersion("email-signing", 1).isPresent()); @@ -173,8 +166,7 @@ final class FilesystemProfileLifecycleTest { } @Test - void caKindsUseLifecycleAndOneLogicalIdCannotChangeKindAcrossVersions( - @TempDir Path directory) throws Exception { + void caKindsUseLifecycleAndOneLogicalIdCannotChangeKindAcrossVersions(@TempDir Path directory) throws Exception { Path root = directory.resolve("store"); CertificateProfileRef rootRef; CertificateProfileRef intermediateRef; @@ -195,11 +187,9 @@ final class FilesystemProfileLifecycleTest { CertificateProfileRef versionTwo = service.importProfile(sameKindVersion); assertEquals(2, versionTwo.profileVersion()); - byte[] conflictingKind = new String(builtIn("intermediate-ca").canonicalJson(), - StandardCharsets.UTF_8) + byte[] conflictingKind = new String(builtIn("intermediate-ca").canonicalJson(), StandardCharsets.UTF_8) .replace("\"profileId\":\"intermediate-ca\"", "\"profileId\":\"root-ca\"") - .replace("\"profileVersion\":1", "\"profileVersion\":3") - .getBytes(StandardCharsets.UTF_8); + .replace("\"profileVersion\":1", "\"profileVersion\":3").getBytes(StandardCharsets.UTF_8); assertCode(() -> service.importProfile(conflictingKind), "PROFILE_KIND_CONFLICT"); assertTrue(service.getImportedVersion("root-ca", 3).isEmpty()); assertEquals(rootRef, service.getActiveReference("root-ca").orElseThrow()); @@ -208,8 +198,7 @@ final class FilesystemProfileLifecycleTest { try (FilesystemPkiStore reopened = store(root)) { DefaultProfileService service = service(reopened); assertEquals(rootRef, service.getActiveReference("root-ca").orElseThrow()); - assertEquals(intermediateRef, - service.getActiveReference("intermediate-ca").orElseThrow()); + assertEquals(intermediateRef, service.getActiveReference("intermediate-ca").orElseThrow()); assertEquals(CertificateProfileKind.ROOT_CA, service.requireActiveProfile("root-ca").definition().certificateType()); assertEquals(CertificateProfileKind.INTERMEDIATE_CA, @@ -260,8 +249,7 @@ final class FilesystemProfileLifecycleTest { IllegalStateException failure = assertThrows(IllegalStateException.class, () -> store.exportSnapshot(corruptTarget, CLOCK.instant())); - assertEquals("Historical snapshot failed: code=PROFILE_VERSION_CORRUPT", - failure.getMessage()); + assertEquals("Historical snapshot failed: code=PROFILE_VERSION_CORRUPT", failure.getMessage()); assertEquals(null, failure.getCause()); assertEquals(0, failure.getSuppressed().length); assertFalse(failure.getMessage().contains(corruptRoot.toString())); @@ -274,15 +262,14 @@ final class FilesystemProfileLifecycleTest { try (FilesystemPkiStore store = store(pathRoot)) { service(store).importBuiltIn(template); Path original = new FsPaths(pathRoot).profileVersion("server-tls", 1); - Path misplaced = pathRoot.resolve("profiles").resolve("by-id").resolve("id-wrong") - .resolve("versions").resolve("1.bin"); + Path misplaced = pathRoot.resolve("profiles").resolve("by-id").resolve("id-wrong").resolve("versions") + .resolve("1.bin"); FsOperations.ensureDir(misplaced.getParent()); Files.write(misplaced, Files.readAllBytes(original)); IllegalStateException failure = assertThrows(IllegalStateException.class, () -> store.exportSnapshot(pathTarget, CLOCK.instant())); - assertEquals("Historical snapshot failed: code=PROFILE_VERSION_CORRUPT", - failure.getMessage()); + assertEquals("Historical snapshot failed: code=PROFILE_VERSION_CORRUPT", failure.getMessage()); assertFalse(Files.exists(pathTarget)); } @@ -292,8 +279,7 @@ final class FilesystemProfileLifecycleTest { Files.writeString(invalidRoot.resolve("profiles"), "not-a-directory"); IllegalStateException failure = assertThrows(IllegalStateException.class, () -> store.exportSnapshot(invalidTarget, CLOCK.instant())); - assertEquals("Historical snapshot failed: code=PROFILE_STORE_FAILURE", - failure.getMessage()); + assertEquals("Historical snapshot failed: code=PROFILE_STORE_FAILURE", failure.getMessage()); assertEquals(null, failure.getCause()); assertEquals(0, failure.getSuppressed().length); assertFalse(Files.exists(invalidTarget)); @@ -318,17 +304,13 @@ final class FilesystemProfileLifecycleTest { private static BuiltInCertificateProfileTemplate builtIn(String profileId) { return BuiltInCertificateProfileCatalog.load(FilesystemProfileLifecycleTest.class.getClassLoader()).stream() - .filter(template -> profileId.equals(template.definition().profileId())) - .findFirst().orElseThrow(); + .filter(template -> profileId.equals(template.definition().profileId())).findFirst().orElseThrow(); } private static void assertActiveChainFailure(DefaultProfileService service, String profileId, String code) { - PkiException referenceFailure = assertThrows(PkiException.class, - () -> service.getActiveReference(profileId)); - PkiException profileFailure = assertThrows(PkiException.class, - () -> service.requireActiveProfile(profileId)); - assertEquals("Profile lifecycle operation failed: code=" + code, - referenceFailure.getMessage()); + PkiException referenceFailure = assertThrows(PkiException.class, () -> service.getActiveReference(profileId)); + PkiException profileFailure = assertThrows(PkiException.class, () -> service.requireActiveProfile(profileId)); + assertEquals("Profile lifecycle operation failed: code=" + code, referenceFailure.getMessage()); assertEquals(referenceFailure.getMessage(), profileFailure.getMessage()); assertEquals(null, referenceFailure.getCause()); assertEquals(null, profileFailure.getCause()); diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemRevocationJournalTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemRevocationJournalTest.java index c2861e3..7d4dbfb 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemRevocationJournalTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemRevocationJournalTest.java @@ -111,25 +111,20 @@ final class FilesystemRevocationJournalTest { store.putCredential(fromClear); store.putCredential(fromHeld); - RevocationJournal directJournal = transition(store, - revoke(direct, RevocationReason.KEY_COMPROMISE)); - assertEquals(List.of(RevocationState.PERMANENTLY_REVOKED), - states(directJournal)); + RevocationJournal directJournal = transition(store, revoke(direct, RevocationReason.KEY_COMPROMISE)); + assertEquals(List.of(RevocationState.PERMANENTLY_REVOKED), states(directJournal)); assertEquals(1L, directJournal.latest().revision()); transition(store, hold(fromClear)); transition(store, unhold(fromClear)); - RevocationJournal clearJournal = transition(store, - revoke(fromClear, RevocationReason.CA_COMPROMISE)); - assertEquals(List.of(RevocationState.HELD, RevocationState.CLEAR, - RevocationState.PERMANENTLY_REVOKED), states(clearJournal)); + RevocationJournal clearJournal = transition(store, revoke(fromClear, RevocationReason.CA_COMPROMISE)); + assertEquals(List.of(RevocationState.HELD, RevocationState.CLEAR, RevocationState.PERMANENTLY_REVOKED), + states(clearJournal)); assertEquals(3L, clearJournal.latest().revision()); transition(store, hold(fromHeld)); - RevocationJournal heldJournal = transition(store, - revoke(fromHeld, RevocationReason.SUPERSEDED)); - assertEquals(List.of(RevocationState.HELD, RevocationState.PERMANENTLY_REVOKED), - states(heldJournal)); + RevocationJournal heldJournal = transition(store, revoke(fromHeld, RevocationReason.SUPERSEDED)); + assertEquals(List.of(RevocationState.HELD, RevocationState.PERMANENTLY_REVOKED), states(heldJournal)); assertEquals(2L, heldJournal.latest().revision()); } } @@ -138,28 +133,23 @@ final class FilesystemRevocationJournalTest { void illegalAndInvalidCommandsFailBeforeJournalMutation() throws Exception { try (FilesystemPkiStore store = store(temporaryDirectory.resolve("illegal"))) { Credential unknown = credential("unknown"); - assertCode("REVOCATION_CREDENTIAL_NOT_FOUND", - () -> transition(store, hold(unknown))); + assertCode("REVOCATION_CREDENTIAL_NOT_FOUND", () -> transition(store, hold(unknown))); Credential credential = credential("known"); store.putCredential(credential); - assertCode("REVOCATION_TRANSITION_ILLEGAL", - () -> transition(store, unhold(credential))); - assertThrows(IllegalArgumentException.class, - () -> revoke(credential, RevocationReason.CERTIFICATE_HOLD)); + assertCode("REVOCATION_TRANSITION_ILLEGAL", () -> transition(store, unhold(credential))); + assertThrows(IllegalArgumentException.class, () -> revoke(credential, RevocationReason.CERTIFICATE_HOLD)); assertTrue(store.getRevocationJournal(credential.credentialId()).isEmpty()); transition(store, hold(credential)); - assertCode("REVOCATION_TRANSITION_ILLEGAL", - () -> transition(store, hold(credential))); + assertCode("REVOCATION_TRANSITION_ILLEGAL", () -> transition(store, hold(credential))); assertEquals(1, store.getRevocationJournal(credential.credentialId()).orElseThrow().transitions().size()); transition(store, unhold(credential)); Path journalPath = new FsPaths(temporaryDirectory.resolve("illegal")) .revocationJournal(credential.credentialId()); byte[] clearBytes = FsOperations.readAll(journalPath); - assertCode("REVOCATION_TRANSITION_ILLEGAL", - () -> transition(store, unhold(credential))); + assertCode("REVOCATION_TRANSITION_ILLEGAL", () -> transition(store, unhold(credential))); assertArrayEquals(clearBytes, FsOperations.readAll(journalPath)); transition(store, revoke(credential, RevocationReason.KEY_COMPROMISE)); @@ -173,8 +163,7 @@ final class FilesystemRevocationJournalTest { Credential invalidRemove = credential("invalid-remove"); store.putCredential(invalidRemove); - assertThrows(IllegalArgumentException.class, - () -> revoke(invalidRemove, RevocationReason.REMOVE_FROM_CRL)); + assertThrows(IllegalArgumentException.class, () -> revoke(invalidRemove, RevocationReason.REMOVE_FROM_CRL)); assertTrue(store.getRevocationJournal(invalidRemove.credentialId()).isEmpty()); } } @@ -206,37 +195,28 @@ final class FilesystemRevocationJournalTest { try { Credential fromNone = credential("hold-vs-permanent"); store.putCredential(fromNone); - runConcurrent(store, hold(fromNone), - revoke(fromNone, RevocationReason.KEY_COMPROMISE), executor); - RevocationJournal fromNoneJournal = - store.getRevocationJournal(fromNone.credentialId()).orElseThrow(); + runConcurrent(store, hold(fromNone), revoke(fromNone, RevocationReason.KEY_COMPROMISE), executor); + RevocationJournal fromNoneJournal = store.getRevocationJournal(fromNone.credentialId()).orElseThrow(); assertEquals(RevocationState.PERMANENTLY_REVOKED, fromNoneJournal.latest().state()); - assertTrue(fromNoneJournal.transitions().size() == 1 - || fromNoneJournal.transitions().size() == 2); + assertTrue(fromNoneJournal.transitions().size() == 1 || fromNoneJournal.transitions().size() == 2); Credential fromHeld = credential("unhold-vs-permanent"); store.putCredential(fromHeld); transition(store, hold(fromHeld)); - runConcurrent(store, unhold(fromHeld), - revoke(fromHeld, RevocationReason.CA_COMPROMISE), executor); - RevocationJournal fromHeldJournal = - store.getRevocationJournal(fromHeld.credentialId()).orElseThrow(); + runConcurrent(store, unhold(fromHeld), revoke(fromHeld, RevocationReason.CA_COMPROMISE), executor); + RevocationJournal fromHeldJournal = store.getRevocationJournal(fromHeld.credentialId()).orElseThrow(); assertEquals(RevocationState.PERMANENTLY_REVOKED, fromHeldJournal.latest().state()); - assertTrue(fromHeldJournal.transitions().size() == 2 - || fromHeldJournal.transitions().size() == 3); + assertTrue(fromHeldJournal.transitions().size() == 2 || fromHeldJournal.transitions().size() == 3); Credential reasons = credential("competing-permanent-reasons"); store.putCredential(reasons); - List results = runConcurrent(store, - revoke(reasons, RevocationReason.KEY_COMPROMISE), + List results = runConcurrent(store, revoke(reasons, RevocationReason.KEY_COMPROMISE), revoke(reasons, RevocationReason.CA_COMPROMISE), executor); assertEquals(1L, results.stream().filter(Boolean::booleanValue).count()); - RevocationJournal reasonsJournal = - store.getRevocationJournal(reasons.credentialId()).orElseThrow(); + RevocationJournal reasonsJournal = store.getRevocationJournal(reasons.credentialId()).orElseThrow(); assertEquals(1, reasonsJournal.transitions().size()); - assertTrue(reasonsJournal.latest().permanentReason() - .filter(reason -> reason == RevocationReason.KEY_COMPROMISE - || reason == RevocationReason.CA_COMPROMISE) + assertTrue(reasonsJournal.latest().permanentReason().filter( + reason -> reason == RevocationReason.KEY_COMPROMISE || reason == RevocationReason.CA_COMPROMISE) .isPresent()); } finally { executor.shutdownNow(); @@ -254,8 +234,7 @@ final class FilesystemRevocationJournalTest { Method acquire = FilesystemPkiStore.class.getDeclaredMethod("acquireRevocationLock", PkiId.class); Method release = Arrays.stream(FilesystemPkiStore.class.getDeclaredMethods()) - .filter(method -> method.getName().equals("releaseRevocationLock")) - .findFirst().orElseThrow(); + .filter(method -> method.getName().equals("releaseRevocationLock")).findFirst().orElseThrow(); acquire.setAccessible(true); release.setAccessible(true); Object heldLock = acquire.invoke(store, blocked.credentialId()); @@ -267,15 +246,13 @@ final class FilesystemRevocationJournalTest { return transition(store, hold(blocked)); }, executor); assertTrue(blockedStarted.await(5, TimeUnit.SECONDS)); - CompletableFuture independentTransition = CompletableFuture.supplyAsync( - () -> transition(store, hold(independent)), executor); - assertEquals(RevocationState.HELD, - independentTransition.get(5, TimeUnit.SECONDS).latest().state()); + CompletableFuture independentTransition = CompletableFuture + .supplyAsync(() -> transition(store, hold(independent)), executor); + assertEquals(RevocationState.HELD, independentTransition.get(5, TimeUnit.SECONDS).latest().state()); assertFalse(blockedTransition.isDone()); release.invoke(store, blocked.credentialId(), heldLock); heldLock = null; - assertEquals(RevocationState.HELD, - blockedTransition.get(5, TimeUnit.SECONDS).latest().state()); + assertEquals(RevocationState.HELD, blockedTransition.get(5, TimeUnit.SECONDS).latest().state()); } finally { if (heldLock != null) { release.invoke(store, blocked.credentialId(), heldLock); @@ -307,10 +284,9 @@ final class FilesystemRevocationJournalTest { @Test void transitionMetadataDefensivelySnapshotsByteValues() { byte[] source = { 1, 2, 3 }; - AttributeSet attributes = SimpleAttributeSet.builder() - .put(NOTE, new AttributeValue.BytesValue(source)).build(); - RevocationTransition transition = new RevocationTransition(1L, RevocationState.HELD, TIME, - Optional.empty(), attributes); + AttributeSet attributes = SimpleAttributeSet.builder().put(NOTE, new AttributeValue.BytesValue(source)).build(); + RevocationTransition transition = new RevocationTransition(1L, RevocationState.HELD, TIME, Optional.empty(), + attributes); source[0] = 9; byte[] first = ((AttributeValue.BytesValue) transition.attributes().get(NOTE).orElseThrow()).value(); assertArrayEquals(new byte[] { 1, 2, 3 }, first); @@ -323,30 +299,30 @@ final class FilesystemRevocationJournalTest { void journalConstructorRejectsEveryHostileSequenceShape() { PkiId id = new PkiId("credential:hostile"); SimpleAttributeSet attributes = new SimpleAttributeSet(); + assertThrows(IllegalArgumentException.class, () -> new RevocationJournal(id, + List.of(new RevocationTransition(1L, RevocationState.CLEAR, TIME, Optional.empty(), attributes)))); + assertThrows(IllegalArgumentException.class, () -> new RevocationJournal(id, + List.of(new RevocationTransition(2L, RevocationState.HELD, TIME, Optional.empty(), attributes)))); assertThrows(IllegalArgumentException.class, - () -> new RevocationJournal(id, List.of(new RevocationTransition(1L, RevocationState.CLEAR, TIME, - Optional.empty(), attributes)))); + () -> new RevocationJournal(id, + List.of(new RevocationTransition(1L, RevocationState.HELD, TIME, Optional.empty(), attributes), + new RevocationTransition(2L, RevocationState.CLEAR, TIME.minusNanos(1), + Optional.empty(), attributes)))); assertThrows(IllegalArgumentException.class, - () -> new RevocationJournal(id, List.of(new RevocationTransition(2L, RevocationState.HELD, TIME, - Optional.empty(), attributes)))); - assertThrows(IllegalArgumentException.class, () -> new RevocationJournal(id, List.of( - new RevocationTransition(1L, RevocationState.HELD, TIME, Optional.empty(), attributes), - new RevocationTransition(2L, RevocationState.CLEAR, TIME.minusNanos(1), Optional.empty(), - attributes)))); + () -> new RevocationJournal(id, + List.of(new RevocationTransition(1L, RevocationState.PERMANENTLY_REVOKED, TIME, + Optional.of(RevocationReason.CERTIFICATE_HOLD), attributes)))); assertThrows(IllegalArgumentException.class, - () -> new RevocationJournal(id, List.of(new RevocationTransition(1L, - RevocationState.PERMANENTLY_REVOKED, TIME, - Optional.of(RevocationReason.CERTIFICATE_HOLD), attributes)))); - assertThrows(IllegalArgumentException.class, () -> new RevocationJournal(id, List.of( - new RevocationTransition(1L, RevocationState.PERMANENTLY_REVOKED, TIME, - Optional.of(RevocationReason.KEY_COMPROMISE), attributes), - new RevocationTransition(2L, RevocationState.HELD, TIME, Optional.empty(), attributes)))); + () -> new RevocationJournal(id, List.of( + new RevocationTransition(1L, RevocationState.PERMANENTLY_REVOKED, TIME, + Optional.of(RevocationReason.KEY_COMPROMISE), attributes), + new RevocationTransition(2L, RevocationState.HELD, TIME, Optional.empty(), attributes)))); } @TestFactory Stream strictCodecCorruptionFailsReadAndTransitionWithoutOverwrite() { - return Arrays.stream(CorruptionCase.values()).map(corruption -> DynamicTest.dynamicTest( - corruption.description, () -> assertPersistedCorruptionRejected(corruption))); + return Arrays.stream(CorruptionCase.values()).map(corruption -> DynamicTest.dynamicTest(corruption.description, + () -> assertPersistedCorruptionRejected(corruption))); } @Test @@ -365,12 +341,10 @@ final class FilesystemRevocationJournalTest { try (FilesystemPkiStore reopened = store(root)) { assertTrue(reopened.getRevocationJournal(credential.credentialId()).isEmpty()); - StoreBackedEffectiveCredentialStatusResolver resolver = - new StoreBackedEffectiveCredentialStatusResolver(reopened, Clock.fixed(TIME, - java.time.ZoneOffset.UTC)); - assertEquals(EffectiveCredentialStatus.USABLE, - resolver.beginEvaluation().resolve( - reopened.getCredential(credential.credentialId()).orElseThrow())); + StoreBackedEffectiveCredentialStatusResolver resolver = new StoreBackedEffectiveCredentialStatusResolver( + reopened, Clock.fixed(TIME, java.time.ZoneOffset.UTC)); + assertEquals(EffectiveCredentialStatus.USABLE, resolver.beginEvaluation() + .resolve(reopened.getCredential(credential.credentialId()).orElseThrow())); transition(reopened, hold(credential)); assertEquals(RevocationState.HELD, reopened.getRevocationJournal(credential.credentialId()).orElseThrow().latest().state()); @@ -386,8 +360,7 @@ final class FilesystemRevocationJournalTest { field.setAccessible(true); ((AtomicBoolean) field.get(store)).set(true); - assertCode("STORE_DURABILITY_UNCONFIRMED", - () -> store.getCredential(credential.credentialId())); + assertCode("STORE_DURABILITY_UNCONFIRMED", () -> store.getCredential(credential.credentialId())); assertCode("STORE_DURABILITY_UNCONFIRMED", store::listCas); assertCode("STORE_DURABILITY_UNCONFIRMED", store::signingNow); } @@ -417,8 +390,8 @@ final class FilesystemRevocationJournalTest { return List.of(first.join(), second.join()); } - private static CompletableFuture attemptTransition(FilesystemPkiStore store, - RevocationCommand command, CyclicBarrier barrier, ExecutorService executor) { + private static CompletableFuture attemptTransition(FilesystemPkiStore store, RevocationCommand command, + CyclicBarrier barrier, ExecutorService executor) { return CompletableFuture.supplyAsync(() -> { try { barrier.await(); @@ -449,10 +422,8 @@ final class FilesystemRevocationJournalTest { try (FilesystemPkiStore reopened = store(root)) { byte[] before = FsOperations.readAll(journalPath); - assertCode("REVOCATION_STATE_CORRUPT", - () -> reopened.getRevocationJournal(credential.credentialId())); - assertCode("REVOCATION_STATE_CORRUPT", - () -> transition(reopened, hold(credential))); + assertCode("REVOCATION_STATE_CORRUPT", () -> reopened.getRevocationJournal(credential.credentialId())); + assertCode("REVOCATION_STATE_CORRUPT", () -> transition(reopened, hold(credential))); assertArrayEquals(before, FsOperations.readAll(journalPath)); } } @@ -481,8 +452,7 @@ final class FilesystemRevocationJournalTest { } private static RevocationCommand.RevokePermanently revoke(Credential credential, RevocationReason reason) { - return new RevocationCommand.RevokePermanently(credential.credentialId(), reason, - new SimpleAttributeSet()); + return new RevocationCommand.RevokePermanently(credential.credentialId(), reason, new SimpleAttributeSet()); } private static void assertCode(String code, Runnable operation) { @@ -497,9 +467,10 @@ final class FilesystemRevocationJournalTest { private static Credential credential(String suffix) { return new Credential(new PkiId("credential:" + suffix), new FormatId("x509"), new IssuerRef(new PkiId("ca:issuer")), new SubjectRef("CN=" + suffix), - new Validity(TIME.minusSeconds(60), TIME.plusSeconds(60)), suffix, - new PkiId("key:" + suffix), new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), CredentialStatus.ISSUED, - new EncodedObject(Encoding.DER, new byte[] { 1, 2, 3 }), new SimpleAttributeSet()); + new Validity(TIME.minusSeconds(60), TIME.plusSeconds(60)), suffix, new PkiId("key:" + suffix), + new CaProfileBinding(new zeroecho.pki.api.profile.CertificateProfileRef("default", 1, new byte[32])), + CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, new byte[] { 1, 2, 3 }), + new SimpleAttributeSet()); } private static byte[] journalPayload(PkiId credentialId, long journalVersion, RawTransition... transitions) @@ -596,25 +567,18 @@ final class FilesystemRevocationJournalTest { } private enum CorruptionCase { - ZERO_TRANSITIONS("zero transitions"), - ZERO_REVISION("zero transition revision"), - FIRST_REVISION_NOT_ONE("first revision is not one"), - REVISION_GAP("revision gap"), - DUPLICATE_REVISION("duplicate revision"), - BACKWARD_TIME("backward transition time"), - CLEAR_FIRST("CLEAR as first state"), - CLEAR_NOT_AFTER_HELD("CLEAR not immediately after HELD"), - REPEATED_HELD("repeated HELD"), - PERMANENT_REASON_MISSING("permanent revocation without reason"), + ZERO_TRANSITIONS("zero transitions"), ZERO_REVISION("zero transition revision"), + FIRST_REVISION_NOT_ONE("first revision is not one"), REVISION_GAP("revision gap"), + DUPLICATE_REVISION("duplicate revision"), BACKWARD_TIME("backward transition time"), + CLEAR_FIRST("CLEAR as first state"), CLEAR_NOT_AFTER_HELD("CLEAR not immediately after HELD"), + REPEATED_HELD("repeated HELD"), PERMANENT_REASON_MISSING("permanent revocation without reason"), PERMANENT_REASON_CERTIFICATE_HOLD("permanent revocation with hold reason"), PERMANENT_REASON_REMOVE_FROM_CRL("permanent revocation with remove-from-CRL reason"), - HELD_WITH_REASON("HELD with permanent reason"), - CLEAR_WITH_REASON("CLEAR with permanent reason"), + HELD_WITH_REASON("HELD with permanent reason"), CLEAR_WITH_REASON("CLEAR with permanent reason"), TRANSITION_AFTER_PERMANENT("transition after permanent revocation"), MISMATCHED_CREDENTIAL_ID("mismatched credential namespace"), UNSUPPORTED_JOURNAL_VERSION("unsupported embedded journal version"), - OLD_REVOKED_RECORD_PAYLOAD("old RevokedRecord payload"), - TRUNCATED_PAYLOAD("truncated current journal"), + OLD_REVOKED_RECORD_PAYLOAD("old RevokedRecord payload"), TRUNCATED_PAYLOAD("truncated current journal"), TRAILING_PAYLOAD("trailing journal data"); private final String description; @@ -627,42 +591,34 @@ final class FilesystemRevocationJournalTest { RawTransition held = raw(1L, RevocationState.HELD, TIME); return switch (this) { case ZERO_TRANSITIONS -> journalPayload(credentialId, 1L); - case ZERO_REVISION -> journalPayload(credentialId, 1L, - raw(0L, RevocationState.HELD, TIME)); - case FIRST_REVISION_NOT_ONE -> journalPayload(credentialId, 1L, - raw(2L, RevocationState.HELD, TIME)); - case REVISION_GAP -> journalPayload(credentialId, 1L, held, - raw(3L, RevocationState.CLEAR, TIME.plusSeconds(1))); - case DUPLICATE_REVISION -> journalPayload(credentialId, 1L, held, - raw(1L, RevocationState.CLEAR, TIME.plusSeconds(1))); - case BACKWARD_TIME -> journalPayload(credentialId, 1L, held, - raw(2L, RevocationState.CLEAR, TIME.minusSeconds(1))); - case CLEAR_FIRST -> journalPayload(credentialId, 1L, - raw(1L, RevocationState.CLEAR, TIME)); - case CLEAR_NOT_AFTER_HELD -> journalPayload(credentialId, 1L, held, - raw(2L, RevocationState.CLEAR, TIME.plusSeconds(1)), - raw(3L, RevocationState.CLEAR, TIME.plusSeconds(2))); - case REPEATED_HELD -> journalPayload(credentialId, 1L, held, - raw(2L, RevocationState.HELD, TIME.plusSeconds(1))); - case PERMANENT_REASON_MISSING -> journalPayload(credentialId, 1L, - raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME)); + case ZERO_REVISION -> journalPayload(credentialId, 1L, raw(0L, RevocationState.HELD, TIME)); + case FIRST_REVISION_NOT_ONE -> journalPayload(credentialId, 1L, raw(2L, RevocationState.HELD, TIME)); + case REVISION_GAP -> + journalPayload(credentialId, 1L, held, raw(3L, RevocationState.CLEAR, TIME.plusSeconds(1))); + case DUPLICATE_REVISION -> + journalPayload(credentialId, 1L, held, raw(1L, RevocationState.CLEAR, TIME.plusSeconds(1))); + case BACKWARD_TIME -> + journalPayload(credentialId, 1L, held, raw(2L, RevocationState.CLEAR, TIME.minusSeconds(1))); + case CLEAR_FIRST -> journalPayload(credentialId, 1L, raw(1L, RevocationState.CLEAR, TIME)); + case CLEAR_NOT_AFTER_HELD -> + journalPayload(credentialId, 1L, held, raw(2L, RevocationState.CLEAR, TIME.plusSeconds(1)), + raw(3L, RevocationState.CLEAR, TIME.plusSeconds(2))); + case REPEATED_HELD -> + journalPayload(credentialId, 1L, held, raw(2L, RevocationState.HELD, TIME.plusSeconds(1))); + case PERMANENT_REASON_MISSING -> + journalPayload(credentialId, 1L, raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME)); case PERMANENT_REASON_CERTIFICATE_HOLD -> journalPayload(credentialId, 1L, - raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, - RevocationReason.CERTIFICATE_HOLD)); + raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, RevocationReason.CERTIFICATE_HOLD)); case PERMANENT_REASON_REMOVE_FROM_CRL -> journalPayload(credentialId, 1L, - raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, - RevocationReason.REMOVE_FROM_CRL)); + raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, RevocationReason.REMOVE_FROM_CRL)); case HELD_WITH_REASON -> journalPayload(credentialId, 1L, raw(1L, RevocationState.HELD, TIME, RevocationReason.KEY_COMPROMISE)); case CLEAR_WITH_REASON -> journalPayload(credentialId, 1L, held, - raw(2L, RevocationState.CLEAR, TIME.plusSeconds(1), - RevocationReason.KEY_COMPROMISE)); + raw(2L, RevocationState.CLEAR, TIME.plusSeconds(1), RevocationReason.KEY_COMPROMISE)); case TRANSITION_AFTER_PERMANENT -> journalPayload(credentialId, 1L, - raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, - RevocationReason.KEY_COMPROMISE), + raw(1L, RevocationState.PERMANENTLY_REVOKED, TIME, RevocationReason.KEY_COMPROMISE), raw(2L, RevocationState.HELD, TIME.plusSeconds(1))); - case MISMATCHED_CREDENTIAL_ID -> journalPayload( - new PkiId("credential:other-namespace"), 1L, held); + case MISMATCHED_CREDENTIAL_ID -> journalPayload(new PkiId("credential:other-namespace"), 1L, held); case UNSUPPORTED_JOURNAL_VERSION -> journalPayload(credentialId, 2L, held); case OLD_REVOKED_RECORD_PAYLOAD -> oldRevokedRecordPayload(credentialId); case TRUNCATED_PAYLOAD -> { @@ -680,8 +636,7 @@ final class FilesystemRevocationJournalTest { return new RawTransition(revision, state, time, Optional.empty()); } - private static RawTransition raw(long revision, RevocationState state, Instant time, - RevocationReason reason) { + private static RawTransition raw(long revision, RevocationState state, Instant time, RevocationReason reason) { return new RawTransition(revision, state, time, Optional.of(reason)); } } diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemSignWorkflowStoreTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemSignWorkflowStoreTest.java index a1ba6fa..a392a53 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemSignWorkflowStoreTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FilesystemSignWorkflowStoreTest.java @@ -85,10 +85,10 @@ final class FilesystemSignWorkflowStoreTest { void currentRecordCodecRequiresExactShapeAndRedactsStructuralFailures() throws Exception { System.out.println("currentRecordCodecRequiresExactShapeAndRedactsStructuralFailures"); Instant createdAt = Instant.parse("2026-01-02T03:04:05.123Z"); - PkiId id = SigningSubmissionId.create("0123456789abcdef0123456789abcdef.test-signer", createdAt, - new SecureRandom()).id(); - SignWorkflowStore.Record current = intent(id, createdAt, - new EncodedObject(Encoding.BINARY, new byte[] { 7 }), TEST_ALGORITHM); + PkiId id = SigningSubmissionId + .create("0123456789abcdef0123456789abcdef.test-signer", createdAt, new SecureRandom()).id(); + SignWorkflowStore.Record current = intent(id, createdAt, new EncodedObject(Encoding.BINARY, new byte[] { 7 }), + TEST_ALGORITHM); String fingerprint = current.fingerprint(); byte[] encoded = FsCodec.encode(FsCodec.SIGN_WORKFLOW_RECORD, current); @@ -177,9 +177,9 @@ final class FilesystemSignWorkflowStoreTest { Instant createdAt = Instant.parse("2026-01-02T03:04:05.123Z"); MutableClock clock = new MutableClock(createdAt); FsPkiStoreOptions defaults = FsPkiStoreOptions.defaults(); - FsPkiStoreOptions options = new FsPkiStoreOptions(defaults.caHistoryPolicy(), - defaults.profileHistoryPolicy(), defaults.revocationHistoryPolicy(), defaults.workflowHistoryPolicy(), - true, Duration.ofDays(90), Duration.ZERO); + FsPkiStoreOptions options = new FsPkiStoreOptions(defaults.caHistoryPolicy(), defaults.profileHistoryPolicy(), + defaults.revocationHistoryPolicy(), defaults.workflowHistoryPolicy(), true, Duration.ofDays(90), + Duration.ZERO); EncodedObject request = new EncodedObject(Encoding.BINARY, new byte[] { 1, 2, 3 }); PkiId id; SignWorkflowStore.Record intent; @@ -196,9 +196,8 @@ final class FilesystemSignWorkflowStoreTest { assertEquals(1L, claimed.revision()); assertEquals(1L, claimed.fence()); assertTrue(store.tryClaimSign(id, 0L, Duration.ofSeconds(30)).isEmpty()); - assertTrue(store.transitionSign(id, claimed.revision(), claimed.fence(), - SignWorkflowStore.State.DISPATCHED, Optional.of("DISPATCHED"), Optional.empty(), - Optional.empty()).isPresent()); + assertTrue(store.transitionSign(id, claimed.revision(), claimed.fence(), SignWorkflowStore.State.DISPATCHED, + Optional.of("DISPATCHED"), Optional.empty(), Optional.empty()).isPresent()); } try (FilesystemPkiStore reopened = new FilesystemPkiStore(root, options, clock)) { @@ -206,9 +205,10 @@ final class FilesystemSignWorkflowStoreTest { clock.set(createdAt.plus(Duration.ofDays(90))); assertEquals(0, reopened.purgeExpiredSignRecords()); SignWorkflowStore.Record active = reopened.getSignRecord(id).orElseThrow(); - SignWorkflowStore.Record failed = reopened.transitionSign(id, active.revision(), active.fence(), - SignWorkflowStore.State.FAILED, Optional.of("PROVIDER_FAILED"), Optional.empty(), - Optional.of(clock.instant())).orElseThrow(); + SignWorkflowStore.Record failed = reopened + .transitionSign(id, active.revision(), active.fence(), SignWorkflowStore.State.FAILED, + Optional.of("PROVIDER_FAILED"), Optional.empty(), Optional.of(clock.instant())) + .orElseThrow(); reopened.retireSign(id, failed.revision(), failed.fence()).orElseThrow(); assertEquals(1, reopened.purgeExpiredSignRecords()); assertFalse(reopened.getSignRecord(id).isPresent()); @@ -235,13 +235,12 @@ final class FilesystemSignWorkflowStoreTest { store.createSignIntent(created); SignWorkflowStore.Record claimed = store.tryClaimSign(id, 0L, Duration.ofSeconds(10)).orElseThrow(); SignWorkflowStore.Record dispatched = store.transitionSign(id, claimed.revision(), claimed.fence(), - SignWorkflowStore.State.DISPATCHED, Optional.of("DISPATCHED"), Optional.empty(), - Optional.empty()).orElseThrow(); + SignWorkflowStore.State.DISPATCHED, Optional.of("DISPATCHED"), Optional.empty(), Optional.empty()) + .orElseThrow(); clock.set(createdAt.plusSeconds(1)); store.transitionSign(id, dispatched.revision(), dispatched.fence(), SignWorkflowStore.State.SUCCEEDED, Optional.of("SIGNED"), Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 9 })), - Optional.of(createdAt.plusSeconds(1))) - .orElseThrow(); + Optional.of(createdAt.plusSeconds(1))).orElseThrow(); clock.set(createdAt.plusSeconds(30)); assertEquals(createdAt.plusSeconds(30), store.signingNow()); clock.set(createdAt.minusSeconds(30)); @@ -317,19 +316,16 @@ final class FilesystemSignWorkflowStoreTest { try (FilesystemPkiStore store = new FilesystemPkiStore(root, FsPkiStoreOptions.defaults(), clock)) { String namespace = store.signingNamespace() + ".test-signer"; id = SigningSubmissionId.create(namespace, createdAt, new SecureRandom()).id(); - store.createSignIntent(intent(id, createdAt, - new EncodedObject(Encoding.BINARY, new byte[] { 8 }), TEST_ALGORITHM)); + store.createSignIntent( + intent(id, createdAt, new EncodedObject(Encoding.BINARY, new byte[] { 8 }), TEST_ALGORITHM)); SignWorkflowStore.Record claimed = store.tryClaimSign(id, 0L, Duration.ofSeconds(30)).orElseThrow(); SignWorkflowStore.Record dispatched = store.transitionSign(id, claimed.revision(), claimed.fence(), - SignWorkflowStore.State.DISPATCHED, Optional.of("DISPATCHED"), Optional.empty(), - Optional.empty()).orElseThrow(); - store.transitionSign(id, dispatched.revision(), dispatched.fence(), - SignWorkflowStore.State.CANCELLING, Optional.of("CANCEL_REQUESTED"), Optional.empty(), - Optional.empty()) + SignWorkflowStore.State.DISPATCHED, Optional.of("DISPATCHED"), Optional.empty(), Optional.empty()) .orElseThrow(); + store.transitionSign(id, dispatched.revision(), dispatched.fence(), SignWorkflowStore.State.CANCELLING, + Optional.of("CANCEL_REQUESTED"), Optional.empty(), Optional.empty()).orElseThrow(); assertEquals(30, SignWorkflowStore.State.CANCELLING.persistentCode()); - assertThrows(IllegalArgumentException.class, - () -> SignWorkflowStore.State.fromPersistentCode(999)); + assertThrows(IllegalArgumentException.class, () -> SignWorkflowStore.State.fromPersistentCode(999)); } try (FilesystemPkiStore restarted = new FilesystemPkiStore(root, FsPkiStoreOptions.defaults(), clock)) { assertEquals(SignWorkflowStore.State.CANCELLING, restarted.getSignRecord(id).orElseThrow().state()); @@ -393,9 +389,9 @@ final class FilesystemSignWorkflowStoreTest { PkiId retiredId = persistDispatched(store, createdAt, 9); SignWorkflowStore.Record retiredSuccessSource = store.getSignRecord(retiredId).orElseThrow(); - SignWorkflowStore.Record retiredSuccess = store.transitionSign(retiredId, - retiredSuccessSource.revision(), retiredSuccessSource.fence(), SignWorkflowStore.State.SUCCEEDED, - Optional.of("SIGNED"), Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 9 })), + SignWorkflowStore.Record retiredSuccess = store.transitionSign(retiredId, retiredSuccessSource.revision(), + retiredSuccessSource.fence(), SignWorkflowStore.State.SUCCEEDED, Optional.of("SIGNED"), + Optional.of(new EncodedObject(Encoding.BINARY, new byte[] { 9 })), Optional.of(createdAt.plusSeconds(1))).orElseThrow(); store.retireSign(retiredId, retiredSuccess.revision(), retiredSuccess.fence()).orElseThrow(); expected.put(retiredId, SignWorkflowStore.State.RETIRED); @@ -419,68 +415,86 @@ final class FilesystemSignWorkflowStoreTest { void semanticCorruptionCannotActivateRecoverOrExposeInjectedResult(@TempDir Path root) throws Exception { System.out.println("semanticCorruptionCannotActivateRecoverOrExposeInjectedResult"); List cases = List.of( - new CorruptionCase("request-changed", record -> copy(record, - request(record.submissionId(), new byte[] { 99 }, TEST_ALGORITHM), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - record.detailCode(), record.result(), record.providerUpdatedAt()), "FINGERPRINT_MISMATCH"), - new CorruptionCase("fingerprint-changed", record -> copy(record, record.request(), - flipFingerprint(record.fingerprint()), record.state(), record.revision(), record.fence(), - record.leaseUntil(), record.detailCode(), record.result(), record.providerUpdatedAt()), + new CorruptionCase("request-changed", + record -> copy(record, request(record.submissionId(), new byte[] { 99 }, TEST_ALGORITHM), + record.fingerprint(), record.state(), record.revision(), record.fence(), + record.leaseUntil(), record.detailCode(), record.result(), record.providerUpdatedAt()), "FINGERPRINT_MISMATCH"), - new CorruptionCase("fingerprint-length", record -> copy(record, record.request(), "signfp:v1:00", - record.state(), record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), - record.result(), record.providerUpdatedAt()), "FINGERPRINT_FORMAT_INVALID"), - new CorruptionCase("success-no-result", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - record.detailCode(), Optional.empty(), record.providerUpdatedAt()), + new CorruptionCase("fingerprint-changed", + record -> copy(record, record.request(), flipFingerprint(record.fingerprint()), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + record.result(), record.providerUpdatedAt()), + "FINGERPRINT_MISMATCH"), + new CorruptionCase("fingerprint-length", + record -> copy(record, record.request(), "signfp:v1:00", record.state(), record.revision(), + record.fence(), record.leaseUntil(), record.detailCode(), record.result(), + record.providerUpdatedAt()), + "FINGERPRINT_FORMAT_INVALID"), + new CorruptionCase("success-no-result", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + Optional.empty(), record.providerUpdatedAt()), "SUCCESS_EVIDENCE_MISSING"), - new CorruptionCase("success-no-provider-time", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - record.detailCode(), record.result(), Optional.empty()), "SUCCESS_EVIDENCE_MISSING"), - new CorruptionCase("success-at-deadline", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - record.detailCode(), record.result(), Optional.of(record.deadline())), + new CorruptionCase("success-no-provider-time", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + record.result(), Optional.empty()), + "SUCCESS_EVIDENCE_MISSING"), + new CorruptionCase("success-at-deadline", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + record.result(), Optional.of(record.deadline())), "PROVIDER_TIME_INVALID"), - new CorruptionCase("success-after-deadline", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - record.detailCode(), record.result(), Optional.of(record.deadline().plusNanos(1))), + new CorruptionCase("success-after-deadline", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + record.result(), Optional.of(record.deadline().plusNanos(1))), "PROVIDER_TIME_INVALID"), - new CorruptionCase("success-active-lease", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), - Optional.of(record.createdAt().plusSeconds(30)), record.detailCode(), record.result(), - record.providerUpdatedAt()), "SUCCESS_EVIDENCE_MISSING"), - new CorruptionCase("success-expiry-code", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), record.leaseUntil(), - Optional.of("EXPIRED"), record.result(), record.providerUpdatedAt()), + new CorruptionCase("success-active-lease", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), Optional.of(record.createdAt().plusSeconds(30)), + record.detailCode(), record.result(), record.providerUpdatedAt()), + "SUCCESS_EVIDENCE_MISSING"), + new CorruptionCase("success-expiry-code", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), record.leaseUntil(), Optional.of("EXPIRED"), + record.result(), record.providerUpdatedAt()), "SUCCESS_DETAIL_CONTRADICTORY"), - new CorruptionCase("success-stale-fence", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.revision(), - record.leaseUntil(), record.detailCode(), record.result(), record.providerUpdatedAt()), + new CorruptionCase("success-stale-fence", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.revision(), record.leaseUntil(), record.detailCode(), + record.result(), record.providerUpdatedAt()), "SUCCESS_REVISION_INVALID"), - new CorruptionCase("injected-result-fingerprint-mismatch", record -> copy(record, record.request(), - flipFingerprint(record.fingerprint()), record.state(), record.revision(), record.fence(), - record.leaseUntil(), record.detailCode(), - Optional.of(new EncodedObject(Encoding.BINARY, "injected-signature".getBytes( - java.nio.charset.StandardCharsets.UTF_8))), - record.providerUpdatedAt()), "FINGERPRINT_MISMATCH"), - new CorruptionCase("failed-with-result", record -> copy(record, record.request(), - record.fingerprint(), SignWorkflowStore.State.FAILED, record.revision(), record.fence(), - record.leaseUntil(), Optional.of("FAILED"), record.result(), record.providerUpdatedAt()), + new CorruptionCase("injected-result-fingerprint-mismatch", + record -> copy(record, record.request(), flipFingerprint(record.fingerprint()), record.state(), + record.revision(), record.fence(), record.leaseUntil(), record.detailCode(), + Optional.of(new EncodedObject(Encoding.BINARY, + "injected-signature".getBytes(java.nio.charset.StandardCharsets.UTF_8))), + record.providerUpdatedAt()), + "FINGERPRINT_MISMATCH"), + new CorruptionCase("failed-with-result", + record -> copy(record, record.request(), record.fingerprint(), SignWorkflowStore.State.FAILED, + record.revision(), record.fence(), record.leaseUntil(), Optional.of("FAILED"), + record.result(), record.providerUpdatedAt()), "FAILURE_RESULT_OR_LEASE_PRESENT"), - new CorruptionCase("claim-without-lease", record -> copy(record, record.request(), - record.fingerprint(), SignWorkflowStore.State.INTENT, 1L, 1L, Optional.empty(), - Optional.of("INTENT"), Optional.empty(), Optional.empty()), "INTENT_CLAIM_INVALID"), - new CorruptionCase("terminal-with-lease", record -> copy(record, record.request(), - record.fingerprint(), record.state(), record.revision(), record.fence(), - Optional.of(record.createdAt().plusSeconds(30)), record.detailCode(), record.result(), - record.providerUpdatedAt()), "SUCCESS_EVIDENCE_MISSING"), - new CorruptionCase("lease-before-creation", record -> copy(record, record.request(), - record.fingerprint(), SignWorkflowStore.State.INTENT, 1L, 1L, - Optional.of(record.createdAt().minusNanos(1)), Optional.of("INTENT"), Optional.empty(), - Optional.empty()), "LEASE_TIME_INVALID"), - new CorruptionCase("dispatched-zero-fence", record -> copy(record, record.request(), - record.fingerprint(), SignWorkflowStore.State.DISPATCHED, 1L, 0L, Optional.empty(), - Optional.of("DISPATCHED"), Optional.empty(), Optional.empty()), + new CorruptionCase("claim-without-lease", + record -> copy(record, record.request(), record.fingerprint(), SignWorkflowStore.State.INTENT, + 1L, 1L, Optional.empty(), Optional.of("INTENT"), Optional.empty(), Optional.empty()), + "INTENT_CLAIM_INVALID"), + new CorruptionCase("terminal-with-lease", + record -> copy(record, record.request(), record.fingerprint(), record.state(), + record.revision(), record.fence(), Optional.of(record.createdAt().plusSeconds(30)), + record.detailCode(), record.result(), record.providerUpdatedAt()), + "SUCCESS_EVIDENCE_MISSING"), + new CorruptionCase("lease-before-creation", + record -> copy(record, record.request(), record.fingerprint(), SignWorkflowStore.State.INTENT, + 1L, 1L, Optional.of(record.createdAt().minusNanos(1)), Optional.of("INTENT"), + Optional.empty(), Optional.empty()), + "LEASE_TIME_INVALID"), + new CorruptionCase("dispatched-zero-fence", + record -> copy(record, record.request(), record.fingerprint(), + SignWorkflowStore.State.DISPATCHED, 1L, 0L, Optional.empty(), Optional.of("DISPATCHED"), + Optional.empty(), Optional.empty()), "DISPATCH_REVISION_INVALID")); int index = 0; @@ -498,16 +512,16 @@ final class FilesystemSignWorkflowStoreTest { PkiSigningBus.SignContinuation continuation = continuation(id, request, algorithmId); String namespace = SigningSubmissionId.parse(id).namespace(); String fingerprint = continuation.semanticFingerprint(namespace, deadline); - return new SignWorkflowStore.Record(id, namespace, fingerprint, TEST_OWNER, - createdAt, deadline, continuation.encode(), SignWorkflowStore.State.INTENT, 0L, 0L, - Optional.empty(), Optional.of("INTENT"), Optional.empty(), Optional.empty()); + return new SignWorkflowStore.Record(id, namespace, fingerprint, TEST_OWNER, createdAt, deadline, + continuation.encode(), SignWorkflowStore.State.INTENT, 0L, 0L, Optional.empty(), Optional.of("INTENT"), + Optional.empty(), Optional.empty()); } private static PkiId persistIntent(FilesystemPkiStore store, Instant createdAt, int marker) { String namespace = store.signingNamespace() + ".test-signer"; PkiId id = SigningSubmissionId.create(namespace, createdAt, new SecureRandom()).id(); - store.createSignIntent(intent(id, createdAt, - new EncodedObject(Encoding.BINARY, new byte[] { (byte) marker }), TEST_ALGORITHM)); + store.createSignIntent(intent(id, createdAt, new EncodedObject(Encoding.BINARY, new byte[] { (byte) marker }), + TEST_ALGORITHM)); return id; } @@ -525,8 +539,7 @@ final class FilesystemSignWorkflowStoreTest { return id; } - private static PkiSigningBus.SignContinuation continuation(PkiId id, EncodedObject payload, - String algorithmId) { + private static PkiSigningBus.SignContinuation continuation(PkiId id, EncodedObject payload, String algorithmId) { return new PkiSigningBus.SignContinuation(TEST_ACCESS, algorithmId, payload, TEST_KEY, Encoding.BINARY, Optional.of(id)); } @@ -536,9 +549,8 @@ final class FilesystemSignWorkflowStoreTest { } private static SignWorkflowStore.Record copy(SignWorkflowStore.Record source, EncodedObject request, - String fingerprint, SignWorkflowStore.State state, long revision, long fence, - Optional leaseUntil, Optional detailCode, Optional result, - Optional providerUpdatedAt) { + String fingerprint, SignWorkflowStore.State state, long revision, long fence, Optional leaseUntil, + Optional detailCode, Optional result, Optional providerUpdatedAt) { return new SignWorkflowStore.Record(source.submissionId(), source.namespace(), fingerprint, source.owner(), source.createdAt(), source.deadline(), request, state, revision, fence, leaseUntil, detailCode, result, providerUpdatedAt); @@ -561,8 +573,8 @@ final class FilesystemSignWorkflowStoreTest { clock.set(createdAt.plusSeconds(1)); succeeded = store.transitionSign(id, dispatched.revision(), dispatched.fence(), SignWorkflowStore.State.SUCCEEDED, Optional.of("SIGNED"), - Optional.of(new EncodedObject(Encoding.BINARY, "valid-signature".getBytes( - java.nio.charset.StandardCharsets.UTF_8))), + Optional.of(new EncodedObject(Encoding.BINARY, + "valid-signature".getBytes(java.nio.charset.StandardCharsets.UTF_8))), Optional.of(createdAt.plusSeconds(1))).orElseThrow(); } @@ -588,8 +600,7 @@ final class FilesystemSignWorkflowStoreTest { assertFalse(Files.exists(busLog) && Files.size(busLog) > 0L); } - private static void writeRawCurrentRecord(Path root, PkiId id, SignWorkflowStore.Record record) - throws Exception { + private static void writeRawCurrentRecord(Path root, PkiId id, SignWorkflowStore.Record record) throws Exception { Path path = new FsPaths(root).signWorkflowPath(id); byte[] existing = Files.readAllBytes(path); byte[] payload = FsCodec.encode(FsCodec.SIGN_WORKFLOW_RECORD, record); @@ -605,8 +616,8 @@ final class FilesystemSignWorkflowStoreTest { assertFalse(failure.toString().contains(sensitiveFingerprint)); } - private record CorruptionCase(String name, - java.util.function.UnaryOperator mutation, String expectedCode) { + private record CorruptionCase(String name, java.util.function.UnaryOperator mutation, + String expectedCode) { } private static final class MutableClock extends Clock { diff --git a/pki/src/test/java/zeroecho/pki/impl/fs/FsCodecTest.java b/pki/src/test/java/zeroecho/pki/impl/fs/FsCodecTest.java index 6949056..d86b6f2 100644 --- a/pki/src/test/java/zeroecho/pki/impl/fs/FsCodecTest.java +++ b/pki/src/test/java/zeroecho/pki/impl/fs/FsCodecTest.java @@ -138,8 +138,7 @@ final class FsCodecTest { void attackerClassNamesCannotSelectOrInitializeRuntimeTypes() { System.out.println("attackerClassNamesCannotSelectOrInitializeRuntimeTypes"); List classNames = List.of("java.io.File", "org.bouncycastle.asn1.ASN1ObjectIdentifier", - "zeroecho.pki.impl.fs.FsCodecTest$ArbitraryRecord", - "zeroecho.pki.impl.fs.FsCodecTest$ArbitraryEnum", + "zeroecho.pki.impl.fs.FsCodecTest$ArbitraryRecord", "zeroecho.pki.impl.fs.FsCodecTest$ArbitraryEnum", "zeroecho.pki.impl.fs.FsCodecTest$StaticInitializerSentinel"); for (String className : classNames) { @@ -189,8 +188,7 @@ final class FsCodecTest { Arrays.fill(source, (byte) 0); ParsedCertificationRequest decoded = FsCodec.decode(FsCodec.PARSED_REQUEST, encoded); - AttributeValue.BytesValue decodedValue = - (AttributeValue.BytesValue) decoded.attributes().getAll(id).get(0); + AttributeValue.BytesValue decodedValue = (AttributeValue.BytesValue) decoded.attributes().getAll(id).get(0); byte[] second = decodedValue.value(); System.out.println("...decoded byte count=" + second.length); @@ -228,9 +226,8 @@ final class FsCodecTest { @Test void obsoleteBareStringCaBindingFailsStrictDecode() { - byte[] encoded = FsCodec.encode(FsCodec.CREDENTIAL, - credential(new CaProfileBinding(new CertificateProfileRef("profile-a", 1, - new byte[CertificateProfileRef.HASH_BYTES])))); + byte[] encoded = FsCodec.encode(FsCodec.CREDENTIAL, credential(new CaProfileBinding( + new CertificateProfileRef("profile-a", 1, new byte[CertificateProfileRef.HASH_BYTES])))); int binding = indexOf(encoded, new byte[] { 73, 2, 72 }); assertTrue(binding >= 0); encoded[binding + 2] = 1; @@ -259,30 +256,28 @@ final class FsCodecTest { } private static ParsedCertificationRequest request(AttributeSet attributes) { - return new ParsedCertificationRequest(new PkiId("request-1"), new FormatId("x509"), - new SubjectRef("subject-1"), new EncodedObject(Encoding.DER, new byte[] { 4, 5, 6 }), - Optional.of(new Validity(Instant.parse("2026-01-02T03:04:05Z"), - Instant.parse("2027-01-02T03:04:05Z"))), + return new ParsedCertificationRequest(new PkiId("request-1"), new FormatId("x509"), new SubjectRef("subject-1"), + new EncodedObject(Encoding.DER, new byte[] { 4, 5, 6 }), + Optional.of(new Validity(Instant.parse("2026-01-02T03:04:05Z"), Instant.parse("2027-01-02T03:04:05Z"))), Optional.of("profile-1"), attributes); } private static CertificateProfile profile() { - SubjectPolicy subject = new SubjectPolicy(false, List.of(new SubjectRdnRule( - SubjectRdnType.COMMON_NAME, 1, 1, 256, Optional.empty(), true))); - SubjectAlternativeNamePolicy sans = new SubjectAlternativeNamePolicy(0, 0, - List.of(), false, java.util.Set.of(), false, false, false); + SubjectPolicy subject = new SubjectPolicy(false, + List.of(new SubjectRdnRule(SubjectRdnType.COMMON_NAME, 1, 1, 256, Optional.empty(), true))); + SubjectAlternativeNamePolicy sans = new SubjectAlternativeNamePolicy(0, 0, List.of(), false, java.util.Set.of(), + false, false, false); LeafCertificatePolicy leaf = new LeafCertificatePolicy(sans, java.util.Set.of(LeafKeyUsage.DIGITAL_SIGNATURE, LeafKeyUsage.KEY_ENCIPHERMENT), java.util.Set.of(new ExtendedKeyUsageId("1.3.6.1.5.5.7.3.1")), true, false, true, java.util.Set.of("RSA")); - return new CertificateProfile("profile-a", new FormatId("x509"), "Test leaf profile", - Duration.ofDays(365), subject, leaf); + return new CertificateProfile("profile-a", new FormatId("x509"), "Test leaf profile", Duration.ofDays(365), + subject, leaf); } private static Credential credential(CredentialProfileBinding binding) { - return new Credential(new PkiId("credential-1"), new FormatId("x509"), - new IssuerRef(new PkiId("ca-1")), new SubjectRef("subject-1"), - new Validity(Instant.EPOCH, Instant.EPOCH.plusSeconds(1)), "1", + return new Credential(new PkiId("credential-1"), new FormatId("x509"), new IssuerRef(new PkiId("ca-1")), + new SubjectRef("subject-1"), new Validity(Instant.EPOCH, Instant.EPOCH.plusSeconds(1)), "1", new PkiId("spki-1"), binding, CredentialStatus.ISSUED, new EncodedObject(Encoding.DER, new byte[] { 1 }), new SimpleAttributeSet()); } @@ -292,13 +287,12 @@ final class FsCodecTest { CertificateProfile profile = profile(); CertificateProfileDefinition definition = new CertificateProfileDefinition( CertificateProfileKind.END_ENTITY, profile.profileId(), 1, profile.formatId(), - profile.displayName(), profile.maximumValidity(), profile.subjectPolicy(), - profile.leafPolicy()); + profile.displayName(), profile.maximumValidity(), profile.subjectPolicy(), profile.leafPolicy()); byte[] canonical = CertificateProfileDocumentCodec.writeCanonical(definition); CertificateProfileRef reference = new CertificateProfileRef(profile.profileId(), 1, MessageDigest.getInstance("SHA-256").digest(canonical)); - return new ImportedCertificateProfileVersion(reference, - CertificateProfileDefinition.SCHEMA_VERSION, definition, canonical, Instant.EPOCH); + return new ImportedCertificateProfileVersion(reference, CertificateProfileDefinition.SCHEMA_VERSION, + definition, canonical, Instant.EPOCH); } catch (java.security.NoSuchAlgorithmException impossible) { throw new IllegalStateException(impossible); } diff --git a/pki/src/test/java/zeroecho/pki/spi/bootstrap/PkiBootstrapTest.java b/pki/src/test/java/zeroecho/pki/spi/bootstrap/PkiBootstrapTest.java index 730d80e..793661f 100644 --- a/pki/src/test/java/zeroecho/pki/spi/bootstrap/PkiBootstrapTest.java +++ b/pki/src/test/java/zeroecho/pki/spi/bootstrap/PkiBootstrapTest.java @@ -99,10 +99,9 @@ import zeroecho.pki.util.async.AsyncBus; *

      */ public final class PkiBootstrapTest { - private static final char[] KEYRING_PASSWORD = - { 'b', 'o', 'o', 't', 's', 't', 'r', 'a', 'p', '-', 't', 'e', 's', 't' }; - private static final String SIGNING_NAMESPACE = - "0123456789abcdef0123456789abcdef.zeroecho-lib"; + private static final char[] KEYRING_PASSWORD = { 'b', 'o', 'o', 't', 's', 't', 'r', 'a', 'p', '-', 't', 'e', 's', + 't' }; + private static final String SIGNING_NAMESPACE = "0123456789abcdef0123456789abcdef.zeroecho-lib"; @TempDir private Path tempDir; @@ -292,15 +291,13 @@ public final class PkiBootstrapTest { } @Test - public void openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath() - throws Exception { + public void openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath() throws Exception { System.out.println("openSignatureWorkflow_zeroEchoLib_usesConfiguredKeyringPath"); Path keyringPath = this.tempDir.resolve("workflow").resolve("keyring.zek"); createSigningKeyring(keyringPath); System.setProperty("zeroecho.pki.crypto.workflow", "zeroecho-lib"); - System.setProperty("zeroecho.pki.crypto.workflow.keyringPath", - keyringPath.toString()); + System.setProperty("zeroecho.pki.crypto.workflow.keyringPath", keyringPath.toString()); System.setProperty("zeroecho.pki.crypto.workflow.operationRoot", this.tempDir.resolve("workflow").resolve("operations").toString()); System.setProperty("zeroecho.pki.crypto.workflow.keyRefPrefix", "test-prefix:"); @@ -314,8 +311,8 @@ public final class PkiBootstrapTest { suppliedPassword.set(password); return password; }; - SignatureWorkflowRuntimeDependencies dependencies = - SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider(unlockProvider); + SignatureWorkflowRuntimeDependencies dependencies = SignatureWorkflowRuntimeDependencies + .withKeyringUnlockProvider(unlockProvider); try (SignatureWorkflow workflow = PkiBootstrap.openSignatureWorkflow(dependencies)) { assertNotNull(workflow); @@ -323,30 +320,24 @@ public final class PkiBootstrapTest { System.out.println("...workflowClass=" + workflowClassName); System.out.println("...unlockAcquisitions=" + acquisitions.get()); - assertEquals("zeroecho.pki.impl.crypto.zeroecholib.ZeroEchoLibSignatureWorkflow", - workflowClassName); + assertEquals("zeroecho.pki.impl.crypto.zeroecholib.ZeroEchoLibSignatureWorkflow", workflowClassName); assertEquals(1, acquisitions.get()); assertTrue(suppliedPassword.get().isDestroyed()); byte[] payload = { 1, 2, 3, 4 }; - AccessContext access = new AccessContext(new Principal("TEST", "bootstrap"), - new Purpose("BOOTSTRAP_TEST"), Optional.empty(), Optional.empty()); - PkiId submissionId = SigningSubmissionId.create(SIGNING_NAMESPACE, - Instant.now(), new SecureRandom()).id(); - SignatureWorkflow.SignRequest request = SignatureWorkflow.SignRequest.create( - submissionId, SIGNING_NAMESPACE, 1L, access, - new KeyRef("test-prefix:bootstrap"), "SHA256withRSA", - new EncodedObject(Encoding.BINARY, payload), - Optional.of(Encoding.BINARY), Optional.empty()); + AccessContext access = new AccessContext(new Principal("TEST", "bootstrap"), new Purpose("BOOTSTRAP_TEST"), + Optional.empty(), Optional.empty()); + PkiId submissionId = SigningSubmissionId.create(SIGNING_NAMESPACE, Instant.now(), new SecureRandom()).id(); + SignatureWorkflow.SignRequest request = SignatureWorkflow.SignRequest.create(submissionId, + SIGNING_NAMESPACE, 1L, access, new KeyRef("test-prefix:bootstrap"), "SHA256withRSA", + new EncodedObject(Encoding.BINARY, payload), Optional.of(Encoding.BINARY), Optional.empty()); workflow.submitSign(request); - assertEquals(SignatureWorkflow.State.SUCCEEDED, - workflow.status(submissionId).state()); - assertTrue(workflow.status(submissionId).result().orElseThrow() - .signature().orElseThrow().bytes().length > 0); + assertEquals(SignatureWorkflow.State.SUCCEEDED, workflow.status(submissionId).state()); + assertTrue( + workflow.status(submissionId).result().orElseThrow().signature().orElseThrow().bytes().length > 0); } - try (KeyringPassword password = password(); - KeyringStore reopened = KeyringStore.open(keyringPath, password)) { + try (KeyringPassword password = password(); KeyringStore reopened = KeyringStore.open(keyringPath, password)) { assertTrue(reopened.contains("bootstrap.prv")); } @@ -360,12 +351,10 @@ public final class PkiBootstrapTest { configureWorkflow(this.tempDir.resolve("missing-provider")); PkiException exception = assertThrows(PkiException.class, - () -> PkiBootstrap.openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies.none())); + () -> PkiBootstrap.openSignatureWorkflow(SignatureWorkflowRuntimeDependencies.none())); System.out.println("...code=" + exception.getMessage()); - assertEquals(ZeroEchoLibSignatureWorkflowProvider.DC_KEYRING_UNLOCK_PROVIDER_REQUIRED, - exception.getMessage()); + assertEquals(ZeroEchoLibSignatureWorkflowProvider.DC_KEYRING_UNLOCK_PROVIDER_REQUIRED, exception.getMessage()); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); @@ -392,23 +381,19 @@ public final class PkiBootstrapTest { KeyringUnlockProvider failing = () -> { throw new IOException(sentinel); }; - PkiException exception = assertThrows(PkiException.class, - () -> PkiBootstrap.openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies - .withKeyringUnlockProvider(failing))); + PkiException exception = assertThrows(PkiException.class, () -> PkiBootstrap + .openSignatureWorkflow(SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider(failing))); System.out.println("...code=" + exception.getMessage()); - assertEquals(ZeroEchoLibSignatureWorkflowProvider - .DC_KEYRING_UNLOCK_PROVIDER_FAILED, exception.getMessage()); + assertEquals(ZeroEchoLibSignatureWorkflowProvider.DC_KEYRING_UNLOCK_PROVIDER_FAILED, + exception.getMessage()); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); assertFalse(exception.toString().contains(sentinel)); - assertTrue(records.stream().noneMatch(record -> - String.valueOf(record.getMessage()).contains(sentinel))); + assertTrue(records.stream().noneMatch(record -> String.valueOf(record.getMessage()).contains(sentinel))); try (SignatureWorkflow workflow = PkiBootstrap.openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider( - () -> password()))) { + SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider(() -> password()))) { assertNotNull(workflow); } } finally { @@ -420,8 +405,7 @@ public final class PkiBootstrapTest { } @Test - public void openSignatureWorkflow_zeroEchoLib_wrongPasswordFailsUniformly() - throws Exception { + public void openSignatureWorkflow_zeroEchoLib_wrongPasswordFailsUniformly() throws Exception { System.out.println("openSignatureWorkflow_zeroEchoLib_wrongPasswordFailsUniformly"); Path root = this.tempDir.resolve("wrong-password"); @@ -429,25 +413,20 @@ public final class PkiBootstrapTest { configureWorkflow(root); AtomicReference suppliedPassword = new AtomicReference<>(); KeyringUnlockProvider wrongProvider = () -> { - KeyringPassword password = - new KeyringPassword(new char[] { 'w', 'r', 'o', 'n', 'g' }); + KeyringPassword password = new KeyringPassword(new char[] { 'w', 'r', 'o', 'n', 'g' }); suppliedPassword.set(password); return password; }; - PkiException exception = assertThrows(PkiException.class, - () -> PkiBootstrap.openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies - .withKeyringUnlockProvider(wrongProvider))); - assertEquals(ZeroEchoLibSignatureWorkflowProvider.DC_KEYRING_OPEN_FAILED, - exception.getMessage()); + PkiException exception = assertThrows(PkiException.class, () -> PkiBootstrap + .openSignatureWorkflow(SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider(wrongProvider))); + assertEquals(ZeroEchoLibSignatureWorkflowProvider.DC_KEYRING_OPEN_FAILED, exception.getMessage()); assertNull(exception.getCause()); assertEquals(0, exception.getSuppressed().length); assertTrue(suppliedPassword.get().isDestroyed()); try (SignatureWorkflow workflow = PkiBootstrap.openSignatureWorkflow( - SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider( - () -> password()))) { + SignatureWorkflowRuntimeDependencies.withKeyringUnlockProvider(() -> password()))) { assertNotNull(workflow); } @@ -460,23 +439,18 @@ public final class PkiBootstrapTest { java.lang.reflect.Method[] methods = PkiBootstrap.class.getDeclaredMethods(); long workflowOpeners = java.util.Arrays.stream(methods) - .filter(method -> "openSignatureWorkflow".equals(method.getName())) - .count(); + .filter(method -> "openSignatureWorkflow".equals(method.getName())).count(); java.lang.reflect.Method opener = java.util.Arrays.stream(methods) - .filter(method -> "openSignatureWorkflow".equals(method.getName())) - .findFirst().orElseThrow(); + .filter(method -> "openSignatureWorkflow".equals(method.getName())).findFirst().orElseThrow(); System.out.println("...workflowOpeners=" + workflowOpeners); assertEquals(1L, workflowOpeners); - assertEquals(List.of(SignatureWorkflowRuntimeDependencies.class), - List.of(opener.getParameterTypes())); + assertEquals(List.of(SignatureWorkflowRuntimeDependencies.class), List.of(opener.getParameterTypes())); assertTrue(java.util.Arrays.stream(PkiBootstrap.class.getDeclaredFields()) .noneMatch(field -> KeyringUnlockProvider.class.equals(field.getType()))); - assertTrue(ProviderConfig.class.getRecordComponents()[1].getType() - .equals(Map.class)); + assertTrue(ProviderConfig.class.getRecordComponents()[1].getType().equals(Map.class)); assertTrue(java.util.Arrays.stream(PkiBootstrap.class.getMethods()) - .noneMatch(method -> java.util.Arrays.stream(method.getParameterTypes()) - .anyMatch(String.class::equals) + .noneMatch(method -> java.util.Arrays.stream(method.getParameterTypes()).anyMatch(String.class::equals) && "openSignatureWorkflow".equals(method.getName()))); System.out.println("...ok"); @@ -512,10 +486,8 @@ public final class PkiBootstrapTest { private void configureWorkflow(Path root) { System.setProperty("zeroecho.pki.crypto.workflow", "zeroecho-lib"); - System.setProperty("zeroecho.pki.crypto.workflow.keyringPath", - root.resolve("keyring.zek").toString()); - System.setProperty("zeroecho.pki.crypto.workflow.operationRoot", - root.resolve("operations").toString()); + System.setProperty("zeroecho.pki.crypto.workflow.keyringPath", root.resolve("keyring.zek").toString()); + System.setProperty("zeroecho.pki.crypto.workflow.operationRoot", root.resolve("operations").toString()); System.setProperty("zeroecho.pki.crypto.workflow.keyRefPrefix", "test-prefix:"); System.setProperty("zeroecho.pki.crypto.workflow.requireComponentSuffix", "false"); } @@ -529,8 +501,7 @@ public final class PkiBootstrapTest { keyring.putPublic("bootstrap.pub", "RSA", pair.getPublic()); } } catch (Exception failure) { - throw new IllegalStateException("Unable to create bootstrap test keyring", - failure); + throw new IllegalStateException("Unable to create bootstrap test keyring", failure); } } diff --git a/pki/src/test/java/zeroecho/pki/spi/framework/CrlEntryTest.java b/pki/src/test/java/zeroecho/pki/spi/framework/CrlEntryTest.java index 550859d..4092b07 100644 --- a/pki/src/test/java/zeroecho/pki/spi/framework/CrlEntryTest.java +++ b/pki/src/test/java/zeroecho/pki/spi/framework/CrlEntryTest.java @@ -21,12 +21,8 @@ final class CrlEntryTest { @Test void acceptsEveryExplicitActiveReasonAndFullPositiveSerialDomain() { BigInteger largestFeasibleX509Serial = BigInteger.ONE.shiftLeft(159).subtract(BigInteger.ONE); - BigInteger[] serials = { - BigInteger.ONE, - BigInteger.valueOf(Long.MAX_VALUE), - BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), - largestFeasibleX509Serial - }; + BigInteger[] serials = { BigInteger.ONE, BigInteger.valueOf(Long.MAX_VALUE), + BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), largestFeasibleX509Serial }; for (RevocationReason reason : activeReasons()) { for (BigInteger serial : serials) { CrlEntry entry = new CrlEntry(serial, TIME, reason); @@ -45,15 +41,13 @@ final class CrlEntryTest { () -> new CrlEntry(BigInteger.valueOf(-1L), TIME, RevocationReason.UNSPECIFIED)); assertThrows(NullPointerException.class, () -> new CrlEntry(BigInteger.ONE, null, RevocationReason.UNSPECIFIED)); - assertThrows(NullPointerException.class, - () -> new CrlEntry(BigInteger.ONE, TIME, null)); + assertThrows(NullPointerException.class, () -> new CrlEntry(BigInteger.ONE, TIME, null)); assertThrows(IllegalArgumentException.class, () -> new CrlEntry(BigInteger.ONE, TIME, RevocationReason.REMOVE_FROM_CRL)); } private static RevocationReason[] activeReasons() { - return Arrays.stream(RevocationReason.values()) - .filter(reason -> reason != RevocationReason.REMOVE_FROM_CRL) + return Arrays.stream(RevocationReason.values()).filter(reason -> reason != RevocationReason.REMOVE_FROM_CRL) .toArray(RevocationReason[]::new); } } diff --git a/pki/src/test/java/zeroecho/pki/testkit/DurableDelayedSignatureWorkflow.java b/pki/src/test/java/zeroecho/pki/testkit/DurableDelayedSignatureWorkflow.java index ec467f2..1e6cf9e 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/DurableDelayedSignatureWorkflow.java +++ b/pki/src/test/java/zeroecho/pki/testkit/DurableDelayedSignatureWorkflow.java @@ -296,8 +296,8 @@ public final class DurableDelayedSignatureWorkflow implements SignatureWorkflow, String keyRef = parts[0]; String alg = parts[1]; byte[] payload = Base64.getDecoder().decode(parts[2]); - Optional deadline = parts.length == 4 && !parts[3].isBlank() - ? Optional.of(Instant.parse(parts[3])) : Optional.empty(); + Optional deadline = parts.length == 4 && !parts[3].isBlank() ? Optional.of(Instant.parse(parts[3])) + : Optional.empty(); return new PersistedSignRequest(keyRef, alg, payload, deadline); } catch (IOException ex) { return new PersistedSignRequest("", "", new byte[0], Optional.empty()); diff --git a/pki/src/test/java/zeroecho/pki/testkit/DurableOperatorApprovalSignatureWorkflow.java b/pki/src/test/java/zeroecho/pki/testkit/DurableOperatorApprovalSignatureWorkflow.java index f54e3b1..e3cb498 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/DurableOperatorApprovalSignatureWorkflow.java +++ b/pki/src/test/java/zeroecho/pki/testkit/DurableOperatorApprovalSignatureWorkflow.java @@ -174,8 +174,8 @@ public final class DurableOperatorApprovalSignatureWorkflow implements Signature Objects.requireNonNull(operationId, "operationId"); persistDecision(operationId, DECISION_APPROVE); Instant now = Instant.now(); - OperationStatus st = deadlineReached(loadDeadline(operationId), now) - ? expired(now) : new OperationStatus(State.PENDING, now, Optional.of("APPROVED"), Optional.empty()); + OperationStatus st = deadlineReached(loadDeadline(operationId), now) ? expired(now) + : new OperationStatus(State.PENDING, now, Optional.of("APPROVED"), Optional.empty()); persistStatus(operationId, st); notifySink(operationId, st); } diff --git a/pki/src/test/java/zeroecho/pki/testkit/H7ProfileDocuments.java b/pki/src/test/java/zeroecho/pki/testkit/H7ProfileDocuments.java index 27e9e39..5241393 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/H7ProfileDocuments.java +++ b/pki/src/test/java/zeroecho/pki/testkit/H7ProfileDocuments.java @@ -10,34 +10,28 @@ import java.util.List; /** * Explicit strict JSON documents used by H7 issuance tests. * - *

      These fixtures deliberately contain document syntax and never construct a + *

      + * These fixtures deliberately contain document syntax and never construct a * runtime profile or profile definition before entering the production import - * boundary.

      + * boundary. + *

      */ public final class H7ProfileDocuments { - private static final String CN = - "{\"oid\":\"2.5.4.3\",\"source\":\"REQUESTER\",\"minimumOccurrences\":1," - + "\"maximumOccurrences\":1,\"maximumUtf8Bytes\":256}"; - private static final String SUBJECT_EMAIL = - "{\"oid\":\"1.2.840.113549.1.9.1\",\"source\":\"REQUESTER\",\"minimumOccurrences\":1," - + "\"maximumOccurrences\":1,\"maximumUtf8Bytes\":256}"; - private static final String DNS_ONE = - "{\"type\":\"DNS_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"wildcardAllowed\":false}"; - private static final String IPV4_ONE = - "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"ipv4Allowed\":true,\"ipv6Allowed\":false}"; - private static final String IPV6_ONE = - "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"ipv4Allowed\":false,\"ipv6Allowed\":true}"; - private static final String IP_BOTH_ONE = - "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"ipv4Allowed\":true,\"ipv6Allowed\":true}"; - private static final String URI_ONE = - "{\"type\":\"URI\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"allowedSchemes\":[\"https\"]}"; - private static final String RFC822_ONE = - "{\"type\":\"RFC822_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":1}"; + private static final String CN = "{\"oid\":\"2.5.4.3\",\"source\":\"REQUESTER\",\"minimumOccurrences\":1," + + "\"maximumOccurrences\":1,\"maximumUtf8Bytes\":256}"; + private static final String SUBJECT_EMAIL = "{\"oid\":\"1.2.840.113549.1.9.1\",\"source\":\"REQUESTER\",\"minimumOccurrences\":1," + + "\"maximumOccurrences\":1,\"maximumUtf8Bytes\":256}"; + private static final String DNS_ONE = "{\"type\":\"DNS_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," + + "\"wildcardAllowed\":false}"; + private static final String IPV4_ONE = "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," + + "\"ipv4Allowed\":true,\"ipv6Allowed\":false}"; + private static final String IPV6_ONE = "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," + + "\"ipv4Allowed\":false,\"ipv6Allowed\":true}"; + private static final String IP_BOTH_ONE = "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," + + "\"ipv4Allowed\":true,\"ipv6Allowed\":true}"; + private static final String URI_ONE = "{\"type\":\"URI\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," + + "\"allowedSchemes\":[\"https\"]}"; + private static final String RFC822_ONE = "{\"type\":\"RFC822_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":1}"; private H7ProfileDocuments() { } @@ -50,40 +44,39 @@ public final class H7ProfileDocuments { /** @return imported but intentionally inactive profile document */ public static byte[] inactiveProfile() { - return profile("inactive", false, List.of(CN), 0, 0, false, false, false, List.of(), false, - serverAuth()); + return profile("inactive", false, List.of(CN), 0, 0, false, false, false, List.of(), false, serverAuth()); } /** @return profile document using a deliberately mismatched format */ public static byte[] wrongFormatProfile() { - return profile("wrong-format", "wrong", false, List.of(CN), 0, 0, false, false, false, List.of(), - false, serverAuth()); + return profile("wrong-format", "wrong", false, List.of(CN), 0, 0, false, false, false, List.of(), false, + serverAuth()); } /** @return profile document used to reject an excessive validity request */ public static byte[] excessiveValidityProfile() { - return profile("too-long", false, List.of(CN), 0, 0, false, false, false, List.of(), false, - serverAuth()); + return profile("too-long", false, List.of(CN), 0, 0, false, false, false, List.of(), false, serverAuth()); } /** @return one-DNS profile document */ public static byte[] dnsProfile() { - return profile("h7-dns", false, List.of(CN), 1, 1, true, false, false, List.of(DNS_ONE), false, - serverAuth()); + return profile("h7-dns", false, List.of(CN), 1, 1, true, false, false, List.of(DNS_ONE), false, serverAuth()); } /** @return multiple-DNS profile document */ public static byte[] multipleDnsProfile() { return profile("h7-dns-multiple", false, List.of(CN), 1, 3, true, false, false, List.of("{\"type\":\"DNS_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":3," - + "\"wildcardAllowed\":false}"), false, serverAuth()); + + "\"wildcardAllowed\":false}"), + false, serverAuth()); } /** @return wildcard-DNS profile document */ public static byte[] wildcardDnsProfile() { return profile("h7-wildcard", false, List.of(CN), 1, 1, true, false, false, List.of("{\"type\":\"DNS_NAME\",\"minimumOccurrences\":1,\"maximumOccurrences\":1," - + "\"wildcardAllowed\":true}"), false, serverAuth()); + + "\"wildcardAllowed\":true}"), + false, serverAuth()); } /** @return empty-subject DNS profile document */ @@ -108,8 +101,7 @@ public final class H7ProfileDocuments { public static byte[] mixedIpProfile() { String rule = "{\"type\":\"IP_ADDRESS\",\"minimumOccurrences\":2,\"maximumOccurrences\":2," + "\"ipv4Allowed\":true,\"ipv6Allowed\":true}"; - return profile("h7-ip-mixed", false, List.of(CN), 2, 2, true, false, false, List.of(rule), false, - serverAuth()); + return profile("h7-ip-mixed", false, List.of(CN), 2, 2, true, false, false, List.of(rule), false, serverAuth()); } /** @return empty-subject IP profile document */ @@ -120,20 +112,17 @@ public final class H7ProfileDocuments { /** @return IPv4-only profile document */ public static byte[] ipv4Profile() { - return profile("h7-ipv4", false, List.of(CN), 1, 1, true, false, false, List.of(IPV4_ONE), false, - serverAuth()); + return profile("h7-ipv4", false, List.of(CN), 1, 1, true, false, false, List.of(IPV4_ONE), false, serverAuth()); } /** @return IPv6-only profile document */ public static byte[] ipv6Profile() { - return profile("h7-ipv6", false, List.of(CN), 1, 1, true, false, false, List.of(IPV6_ONE), false, - serverAuth()); + return profile("h7-ipv6", false, List.of(CN), 1, 1, true, false, false, List.of(IPV6_ONE), false, serverAuth()); } /** @return one-URI profile document */ public static byte[] uriProfile() { - return profile("h7-uri", false, List.of(CN), 1, 1, true, false, false, List.of(URI_ONE), false, - serverAuth()); + return profile("h7-uri", false, List.of(CN), 1, 1, true, false, false, List.of(URI_ONE), false, serverAuth()); } /** @return multiple-URI profile document */ @@ -192,45 +181,39 @@ public final class H7ProfileDocuments { /** @return CN-only profile forbidding SAN and EKU */ public static byte[] noSanOrEkuProfile() { - return profile("h7-no-san-eku", false, List.of(CN), 0, 0, false, false, false, List.of(), false, - List.of()); + return profile("h7-no-san-eku", false, List.of(CN), 0, 0, false, false, false, List.of(), false, List.of()); } private static List serverAuth() { return List.of("1.3.6.1.5.5.7.3.1"); } - private static byte[] profile(String id, boolean allowEmpty, List subjectRules, - int minimumTotal, int maximumTotal, boolean serviceIdentity, boolean emailIdentity, - boolean critical, List sanRules, boolean noKeyEncipherment, List eku) { - return profile(id, "x509", allowEmpty, subjectRules, minimumTotal, maximumTotal, serviceIdentity, - emailIdentity, critical, sanRules, noKeyEncipherment, eku); + private static byte[] profile(String id, boolean allowEmpty, List subjectRules, int minimumTotal, + int maximumTotal, boolean serviceIdentity, boolean emailIdentity, boolean critical, List sanRules, + boolean noKeyEncipherment, List eku) { + return profile(id, "x509", allowEmpty, subjectRules, minimumTotal, maximumTotal, serviceIdentity, emailIdentity, + critical, sanRules, noKeyEncipherment, eku); } private static byte[] profile(String id, String formatId, boolean allowEmpty, List subjectRules, - int minimumTotal, int maximumTotal, boolean serviceIdentity, boolean emailIdentity, - boolean critical, List sanRules, boolean noKeyEncipherment, List eku) { - String keyUsage = noKeyEncipherment - ? "[\"DIGITAL_SIGNATURE\"]" : "[\"DIGITAL_SIGNATURE\",\"KEY_ENCIPHERMENT\"]"; + int minimumTotal, int maximumTotal, boolean serviceIdentity, boolean emailIdentity, boolean critical, + List sanRules, boolean noKeyEncipherment, List eku) { + String keyUsage = noKeyEncipherment ? "[\"DIGITAL_SIGNATURE\"]" + : "[\"DIGITAL_SIGNATURE\",\"KEY_ENCIPHERMENT\"]"; String json = "{\"schemaVersion\":2,\"certificateType\":\"END_ENTITY\",\"profileId\":\"" + id - + "\",\"profileVersion\":1,\"formatId\":\"" + formatId - + "\",\"displayName\":\"H7 Test Profile\"," - + "\"maxValidity\":\"PT8760H\",\"subject\":{\"allowEmpty\":" + allowEmpty - + ",\"rules\":[" + String.join(",", subjectRules) + "]}," - + "\"subjectAlternativeNames\":{\"minimumTotal\":" + minimumTotal - + ",\"maximumTotal\":" + maximumTotal - + ",\"serviceIdentityRequired\":" + serviceIdentity - + ",\"emailIdentityRequired\":" + emailIdentity - + ",\"criticalWhenSubjectNonEmpty\":" + critical + + "\",\"profileVersion\":1,\"formatId\":\"" + formatId + "\",\"displayName\":\"H7 Test Profile\"," + + "\"maxValidity\":\"PT8760H\",\"subject\":{\"allowEmpty\":" + allowEmpty + ",\"rules\":[" + + String.join(",", subjectRules) + "]}," + "\"subjectAlternativeNames\":{\"minimumTotal\":" + + minimumTotal + ",\"maximumTotal\":" + maximumTotal + ",\"serviceIdentityRequired\":" + serviceIdentity + + ",\"emailIdentityRequired\":" + emailIdentity + ",\"criticalWhenSubjectNonEmpty\":" + critical + ",\"rules\":[" + String.join(",", sanRules) + "]}," - + "\"leafCertificate\":{\"basicConstraintsCritical\":true,\"keyUsageCritical\":true," - + "\"keyUsage\":" + keyUsage + ",\"extendedKeyUsageCritical\":false," - + "\"extendedKeyUsage\":[" + quoted(eku) + "],\"allowedKeyAlgorithms\":[\"RSA\"]}}"; + + "\"leafCertificate\":{\"basicConstraintsCritical\":true,\"keyUsageCritical\":true," + "\"keyUsage\":" + + keyUsage + ",\"extendedKeyUsageCritical\":false," + "\"extendedKeyUsage\":[" + quoted(eku) + + "],\"allowedKeyAlgorithms\":[\"RSA\"]}}"; return json.getBytes(StandardCharsets.UTF_8); } private static String quoted(List values) { - return values.stream().map(value -> "\"" + value + "\"") - .collect(java.util.stream.Collectors.joining(",")); + return values.stream().map(value -> "\"" + value + "\"").collect(java.util.stream.Collectors.joining(",")); } } diff --git a/pki/src/test/java/zeroecho/pki/testkit/InMemorySignatureWorkflow.java b/pki/src/test/java/zeroecho/pki/testkit/InMemorySignatureWorkflow.java index c03b3d0..7f9c8fe 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/InMemorySignatureWorkflow.java +++ b/pki/src/test/java/zeroecho/pki/testkit/InMemorySignatureWorkflow.java @@ -102,8 +102,8 @@ public final class InMemorySignatureWorkflow implements SignatureWorkflow { } fingerprints.put(opId, request.semanticFingerprint()); fences.put(opId, request.fencingToken()); - status.put(opId, new OperationStatus(State.RUNNING, Instant.now(), Optional.of("PENDING"), - Optional.empty())); + status.put(opId, + new OperationStatus(State.RUNNING, Instant.now(), Optional.of("PENDING"), Optional.empty())); submittedSignCount.incrementAndGet(); } if (!completeImmediately) { @@ -202,8 +202,8 @@ public final class InMemorySignatureWorkflow implements SignatureWorkflow { return false; } fences.put(operationId, fencingToken); - status.put(operationId, new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), - Optional.empty())); + status.put(operationId, + new OperationStatus(State.CANCELLED, Instant.now(), Optional.of("CANCELLED"), Optional.empty())); return true; } } diff --git a/pki/src/test/java/zeroecho/pki/testkit/OperatorApprovalSignatureWorkflow.java b/pki/src/test/java/zeroecho/pki/testkit/OperatorApprovalSignatureWorkflow.java index 393b037..6fe80f5 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/OperatorApprovalSignatureWorkflow.java +++ b/pki/src/test/java/zeroecho/pki/testkit/OperatorApprovalSignatureWorkflow.java @@ -285,8 +285,7 @@ public final class OperatorApprovalSignatureWorkflow implements SignatureWorkflo p.setProperty(K_UPDATED_AT, startedAt.toString()); writePropsSafe(dir.resolve(FILE_META), p); notifySink(operationId); - return new OperationStatus(State.EXPIRED, startedAt, Optional.of("EXPIRED"), - Optional.empty()); + return new OperationStatus(State.EXPIRED, startedAt, Optional.of("EXPIRED"), Optional.empty()); } try { byte[] payload = Files.readAllBytes(dir.resolve(FILE_REQUEST)); diff --git a/pki/src/test/java/zeroecho/pki/testkit/PkiSigningBusWorker.java b/pki/src/test/java/zeroecho/pki/testkit/PkiSigningBusWorker.java index acd7826..aef206a 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/PkiSigningBusWorker.java +++ b/pki/src/test/java/zeroecho/pki/testkit/PkiSigningBusWorker.java @@ -41,8 +41,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import zeroecho.pki.impl.core.async.PkiSigningBus; /** - * Simple test-only worker that drives {@link PkiSigningBus} by periodically invoking - * {@link PkiSigningBus#sweep(Instant)}. + * Simple test-only worker that drives {@link PkiSigningBus} by periodically + * invoking {@link PkiSigningBus#sweep(Instant)}. */ public final class PkiSigningBusWorker implements AutoCloseable { @@ -54,7 +54,7 @@ public final class PkiSigningBusWorker implements AutoCloseable { /** * Creates and starts a worker. * - * @param bus bus instance (never {@code null}) + * @param bus bus instance (never {@code null}) * @param period sweep period (must be positive) */ public PkiSigningBusWorker(PkiSigningBus bus, Duration period) { diff --git a/pki/src/test/java/zeroecho/pki/testkit/PkiTestRuntime.java b/pki/src/test/java/zeroecho/pki/testkit/PkiTestRuntime.java index 40131d6..abfa48e 100644 --- a/pki/src/test/java/zeroecho/pki/testkit/PkiTestRuntime.java +++ b/pki/src/test/java/zeroecho/pki/testkit/PkiTestRuntime.java @@ -196,8 +196,7 @@ public final class PkiTestRuntime implements AutoCloseable { BcX509StatusObjectGenerator statusGen = new BcX509StatusObjectGenerator(signingBus, "SHA256withRSA", Duration.ofSeconds(2)); BcX509CredentialFramework baseFramework = new BcX509CredentialFramework(); - CredentialFramework framework = proofVerifier - .map(verifier -> baseFramework.wired(statusGen, verifier)) + CredentialFramework framework = proofVerifier.map(verifier -> baseFramework.wired(statusGen, verifier)) .orElseGet(() -> baseFramework.wired(statusGen)); return new PkiTestRuntime(store, signingBus, signer, framework, issuerBackend, publicByRef, @@ -217,8 +216,8 @@ public final class PkiTestRuntime implements AutoCloseable { } SignatureWorkflow signer = new InMemorySignatureWorkflow(byRef, false); PkiSigningBus signingBus = new PkiSigningBus(store, signer, busFile); - BcX509CredentialIssuerBackend issuerBackend = new BcX509CredentialIssuerBackend(signingBus, - "SHA256withRSA", signingTtl); + BcX509CredentialIssuerBackend issuerBackend = new BcX509CredentialIssuerBackend(signingBus, "SHA256withRSA", + signingTtl); BcX509StatusObjectGenerator statusGen = new BcX509StatusObjectGenerator(signingBus, "SHA256withRSA", signingTtl); CredentialFramework framework = new BcX509CredentialFramework().wired(statusGen); @@ -258,7 +257,7 @@ public final class PkiTestRuntime implements AutoCloseable { /** * Replaces only the public key returned by the managed-key resolver. * - * @param keyRef managed key reference + * @param keyRef managed key reference * @param publicKey replacement resolved public key */ public void replaceResolvedKey(KeyRef keyRef, PublicKey publicKey) { @@ -335,9 +334,9 @@ public final class PkiTestRuntime implements AutoCloseable { public CaService caService(ProfileService profiles) { provisionCaProfiles(); - return new DefaultCaService(store, framework, issuerBackend, this::resolvePublicKeyInfo, signingBus, - auditSink, statusResolver, Objects.requireNonNull(profiles, "profiles"), Clock.systemUTC(), - "SHA256withRSA", Duration.ofSeconds(2)); + return new DefaultCaService(store, framework, issuerBackend, this::resolvePublicKeyInfo, signingBus, auditSink, + statusResolver, Objects.requireNonNull(profiles, "profiles"), Clock.systemUTC(), "SHA256withRSA", + Duration.ofSeconds(2)); } private synchronized void provisionCaProfiles() { @@ -348,8 +347,7 @@ public final class PkiTestRuntime implements AutoCloseable { .filter(template -> template.definition().profileId().equals("root-ca") || template.definition().profileId().equals("intermediate-ca")) .forEach(template -> { - zeroecho.pki.api.profile.CertificateProfileRef reference = - profileService.importBuiltIn(template); + zeroecho.pki.api.profile.CertificateProfileRef reference = profileService.importBuiltIn(template); profileService.activateProfile(reference.profileId(), reference.profileVersion()); }); caProfilesProvisioned = true; @@ -378,8 +376,8 @@ public final class PkiTestRuntime implements AutoCloseable { } public StatusObjectService statusObjectService(EffectiveCredentialStatusResolver resolver) { - return new DefaultStatusObjectService(store, framework, auditSink, Objects.requireNonNull(resolver, - "resolver")); + return new DefaultStatusObjectService(store, framework, auditSink, + Objects.requireNonNull(resolver, "resolver")); } /** diff --git a/pki/src/test/java/zeroecho/pki/util/async/DurableAsyncBusTest.java b/pki/src/test/java/zeroecho/pki/util/async/DurableAsyncBusTest.java index 6ce5fa6..1ea493a 100644 --- a/pki/src/test/java/zeroecho/pki/util/async/DurableAsyncBusTest.java +++ b/pki/src/test/java/zeroecho/pki/util/async/DurableAsyncBusTest.java @@ -107,19 +107,18 @@ public class DurableAsyncBusTest { assertTrue(bus.snapshot(opId).isEmpty()); assertTrue(bus.status(opId).isEmpty()); - DurableAsyncBus replayed = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, new ResultCodec() { - @Override - public String encode(String result) { - return result; - } + DurableAsyncBus replayed = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, new ResultCodec() { + @Override + public String encode(String result) { + return result; + } - @Override - public String decode(String token) { - return token; - } - }, new AppendOnlyLineStore(log)); + @Override + public String decode(String token) { + return token; + } + }, new AppendOnlyLineStore(log)); assertTrue(replayed.snapshot(opId).isEmpty()); assertTrue(replayed.status(opId).isEmpty()); assertTrue(replayed.consumeResult(opId).isEmpty()); @@ -127,7 +126,6 @@ public class DurableAsyncBusTest { System.out.println("...ok"); } - @Test public void failedStatus_remainsVisibleUntilExplicitPurge() { System.out.println("failedStatus_remainsVisibleUntilExplicitPurge"); @@ -169,9 +167,9 @@ public class DurableAsyncBusTest { assertEquals(AsyncState.FAILED, st.get().state()); assertTrue(bus.snapshot(opId).isEmpty()); - DurableAsyncBus replayed = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, ResultCodec.none(), new AppendOnlyLineStore(log)); + DurableAsyncBus replayed = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, ResultCodec.none(), + new AppendOnlyLineStore(log)); assertTrue(replayed.snapshot(opId).isEmpty()); assertEquals(AsyncState.FAILED, replayed.status(opId).orElseThrow().state()); @@ -233,18 +231,17 @@ public class DurableAsyncBusTest { return token; } }; - DurableAsyncBus successful = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, persistedStrings, new AppendOnlyLineStore(successLog)); + DurableAsyncBus successful = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, persistedStrings, + new AppendOnlyLineStore(successLog)); successful.submit(successId, "SIGN", owner, "endpoint-1", Instant.parse("2025-01-01T00:00:00Z"), Duration.ofHours(1)); - successful.update(successId, new AsyncStatus(AsyncState.SUCCEEDED, - Instant.parse("2025-01-01T00:00:10Z"), Optional.of("DONE"), java.util.Map.of()), - Optional.of("signature")); + successful.update(successId, new AsyncStatus(AsyncState.SUCCEEDED, Instant.parse("2025-01-01T00:00:10Z"), + Optional.of("DONE"), java.util.Map.of()), Optional.of("signature")); - DurableAsyncBus replayedSuccessful = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, persistedStrings, new AppendOnlyLineStore(successLog)); + DurableAsyncBus replayedSuccessful = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, persistedStrings, + new AppendOnlyLineStore(successLog)); assertTrue(replayedSuccessful.snapshot(successId).isEmpty()); assertEquals(AsyncState.SUCCEEDED, replayedSuccessful.status(successId).orElseThrow().state()); assertEquals("signature", replayedSuccessful.consumeResult(successId).orElseThrow()); @@ -252,33 +249,32 @@ public class DurableAsyncBusTest { for (AsyncState state : new AsyncState[] { AsyncState.CANCELLED, AsyncState.EXPIRED }) { Path log = tempDir.resolve("async-" + state.name() + ".log"); PkiId opId = new PkiId("op-" + state.name()); - DurableAsyncBus bus = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, ResultCodec.none(), new AppendOnlyLineStore(log)); - bus.submit(opId, "SIGN", owner, "endpoint-1", Instant.parse("2025-01-01T00:00:00Z"), - Duration.ofHours(1)); - bus.update(opId, new AsyncStatus(state, Instant.parse("2025-01-01T00:00:10Z"), - Optional.of(state.name()), java.util.Map.of()), Optional.empty()); + DurableAsyncBus bus = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, ResultCodec.none(), + new AppendOnlyLineStore(log)); + bus.submit(opId, "SIGN", owner, "endpoint-1", Instant.parse("2025-01-01T00:00:00Z"), Duration.ofHours(1)); + bus.update(opId, new AsyncStatus(state, Instant.parse("2025-01-01T00:00:10Z"), Optional.of(state.name()), + java.util.Map.of()), Optional.empty()); - DurableAsyncBus replayed = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, ResultCodec.none(), new AppendOnlyLineStore(log)); + DurableAsyncBus replayed = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, ResultCodec.none(), + new AppendOnlyLineStore(log)); assertTrue(replayed.snapshot(opId).isEmpty()); assertEquals(state, replayed.status(opId).orElseThrow().state()); } Path retiredLog = tempDir.resolve("async-retired.log"); PkiId retiredId = new PkiId("op-retired"); - DurableAsyncBus retired = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, ResultCodec.none(), new AppendOnlyLineStore(retiredLog)); + DurableAsyncBus retired = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, ResultCodec.none(), + new AppendOnlyLineStore(retiredLog)); retired.submit(retiredId, "SIGN", owner, "endpoint-1", Instant.parse("2025-01-01T00:00:00Z"), Duration.ofHours(1)); retired.retire(retiredId); - DurableAsyncBus replayedRetired = - new DurableAsyncBus(PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, - PkiCodecs.STRING, ResultCodec.none(), new AppendOnlyLineStore(retiredLog)); + DurableAsyncBus replayedRetired = new DurableAsyncBus( + PkiCodecs.PKI_ID, PkiCodecs.PRINCIPAL, PkiCodecs.STRING, ResultCodec.none(), + new AppendOnlyLineStore(retiredLog)); assertTrue(replayedRetired.snapshot(retiredId).isEmpty()); assertTrue(replayedRetired.status(retiredId).isEmpty()); } diff --git a/samples/src/test/java/demo/AesTest.java b/samples/src/test/java/demo/AesTest.java index f1f469b..8d0520b 100644 --- a/samples/src/test/java/demo/AesTest.java +++ b/samples/src/test/java/demo/AesTest.java @@ -68,8 +68,7 @@ class AesTest { private final ZeroEchoSession zeroEchoSession = new ZeroEchoSession(); SecretKey generateAesKey() throws GeneralSecurityException { - SecretKey key = zeroEchoSession.keyBuilders().symmetric() - .generate("AES", AesKeyGenSpec.aes256()); + SecretKey key = zeroEchoSession.keyBuilders().symmetric().generate("AES", AesKeyGenSpec.aes256()); LOG.log(Level.INFO, "AES256 key generated"); return key; @@ -203,7 +202,8 @@ class AesTest { // Sample message to encrypt byte[] msg = randomBytes(100); - AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader(); + AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .generateKey(256).modeGcm(128).withHeader(); // The builder stores generated IV and AAD inside the stream header DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg)).add(aesBuilder) @@ -220,7 +220,8 @@ class AesTest { dccb = DataContentChainBuilder.decrypt().add(PlainBytesBuilder.builder().bytes(encrypted)) // Use the same AES key for decryption; IV and AAD are restored from the header - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()) + .modeGcm(128).withHeader()) // Build the pipeline .build(); byte[] decrypted; diff --git a/samples/src/test/java/demo/AgreementVariantsTest.java b/samples/src/test/java/demo/AgreementVariantsTest.java index 21184a5..7890915 100644 --- a/samples/src/test/java/demo/AgreementVariantsTest.java +++ b/samples/src/test/java/demo/AgreementVariantsTest.java @@ -140,8 +140,7 @@ class AgreementVariantsTest { void kemAdapter_mlKem_roundTrip() throws Exception { LOG.info("kemAdapter_mlKem_roundTrip - KEM_ADAPTER (ML-KEM)"); - KeyPair recipient = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); + KeyPair recipient = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); MessageAgreementContext initiator = null; MessageAgreementContext responder = null; @@ -150,8 +149,7 @@ class AgreementVariantsTest { initiator = session.createContext("ML-KEM", KeyUsage.AGREEMENT, recipient.getPublic(), VoidSpec.INSTANCE); // Responder: constructed with recipient's private key (decapsulation side). - responder = session.createContext("ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(), - VoidSpec.INSTANCE); + responder = session.createContext("ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(), VoidSpec.INSTANCE); // One-shot outbound message: KEM ciphertext / encapsulation payload. byte[] enc = initiator.getPeerMessage(); diff --git a/samples/src/test/java/demo/CombinedDeliveryTest.java b/samples/src/test/java/demo/CombinedDeliveryTest.java index 8a13e3a..818ae99 100644 --- a/samples/src/test/java/demo/CombinedDeliveryTest.java +++ b/samples/src/test/java/demo/CombinedDeliveryTest.java @@ -75,24 +75,21 @@ class CombinedDeliveryTest { } KeyPair generateKyberKeys() throws GeneralSecurityException { - KeyPair kp = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); + KeyPair kp = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); LOG.log(Level.INFO, "ML-KEM key pair generated"); return kp; } KeyPair generateRsaKeys() throws GeneralSecurityException { - KeyPair kp = session.keyBuilders().asymmetric() - .generateKeyPair("RSA", RsaKeyGenSpec.rsa4096()); + KeyPair kp = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa4096()); LOG.log(Level.INFO, "RSA key pair generated"); return kp; } KeyPair generateElGamalKeys() throws GeneralSecurityException { - KeyPair kp = session.keyBuilders().asymmetric() - .generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); + KeyPair kp = session.keyBuilders().asymmetric().generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048()); LOG.log(Level.INFO, "ElGamal key pair generated"); return kp; diff --git a/samples/src/test/java/demo/HybridDerivedAesDemoTest.java b/samples/src/test/java/demo/HybridDerivedAesDemoTest.java index 9efc3ee..5ef1f99 100644 --- a/samples/src/test/java/demo/HybridDerivedAesDemoTest.java +++ b/samples/src/test/java/demo/HybridDerivedAesDemoTest.java @@ -116,8 +116,7 @@ class HybridDerivedAesDemoTest { // ...Generate PQC key pair for ML-KEM-768 (recipient; used by Bob side to // decapsulate). - KeyPair bobPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); // ...Build Alice initiator: classic agreement (out-of-band peer pub) + PQC // encapsulation. @@ -279,8 +278,7 @@ class HybridDerivedAesDemoTest { KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); // ...Generate PQC key pair for ML-KEM-768. - KeyPair bobPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); // ...Build Alice initiator in a step-by-step manner. HybridKexBuilder aliceBuilder = HybridKexBuilder.builder(session); @@ -461,8 +459,7 @@ class HybridDerivedAesDemoTest { KeyPair selfClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); // ...Generate PQC identity keys (ML-KEM-768). - KeyPair selfPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair selfPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); // ...Build local initiator (encapsulation) against our own public keys. HybridKexContext encKex = HybridKexBuilder.builder(session) diff --git a/samples/src/test/java/demo/HybridKexDemoTest.java b/samples/src/test/java/demo/HybridKexDemoTest.java index 665ab5c..542df39 100644 --- a/samples/src/test/java/demo/HybridKexDemoTest.java +++ b/samples/src/test/java/demo/HybridKexDemoTest.java @@ -128,8 +128,7 @@ class HybridKexDemoTest { KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); // PQC leg: Bob is the KEM recipient (has ML-KEM keypair). - KeyPair bobPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); // Hybrid profile: default HKDF label, 32-byte output suitable for symmetric // keys. @@ -142,14 +141,14 @@ class HybridKexDemoTest { // Alice (initiator): classic uses Alice private + Bob classic public // (out-of-band). // ...PQC uses Bob PQC public and will produce a KEM ciphertext. - alice = HybridKexContexts.initiator(session, profile, "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(), - XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null); + alice = HybridKexContexts.initiator(session, profile, "Xdh", aliceClassic.getPrivate(), + bobClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null); // Bob (responder): classic uses Bob private + Alice classic public // (out-of-band). // ...PQC uses Bob PQC private and will consume Alice's ciphertext. - bob = HybridKexContexts.responder(session, profile, "Xdh", bobClassic.getPrivate(), aliceClassic.getPublic(), - XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null); + bob = HybridKexContexts.responder(session, profile, "Xdh", bobClassic.getPrivate(), + aliceClassic.getPublic(), XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null); // Alice -> Bob: hybrid message carries PQC ciphertext; classic part is empty. byte[] msgA = alice.getPeerMessage(); @@ -183,8 +182,7 @@ class HybridKexDemoTest { KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); // PQC leg: Bob is the KEM recipient (has ML-KEM keypair). - KeyPair bobPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); HybridKexProfile profile = HybridKexProfile.defaultProfile(32); @@ -195,15 +193,15 @@ class HybridKexDemoTest { // Alice classic leg is message-based (PAIR_MESSAGE): it will emit her public // key as SPKI bytes. // ...PQC leg (KEM initiator) will emit ciphertext. - alice = HybridKexContexts.initiatorPairMessage(session, profile, "Xdh", new KeyPairKey(aliceClassic), XdhSpec.X25519, - "ML-KEM", bobPqc.getPublic(), null); + alice = HybridKexContexts.initiatorPairMessage(session, profile, "Xdh", new KeyPairKey(aliceClassic), + XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null); // Bob classic leg is message-based (PAIR_MESSAGE): it will emit his public key // as SPKI bytes. // ...PQC leg (KEM responder) consumes ciphertext and typically does not emit a // PQC message. - bob = HybridKexContexts.responderPairMessage(session, profile, "Xdh", new KeyPairKey(bobClassic), XdhSpec.X25519, - "ML-KEM", bobPqc.getPrivate(), null); + bob = HybridKexContexts.responderPairMessage(session, profile, "Xdh", new KeyPairKey(bobClassic), + XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null); // Alice -> Bob: hybrid message carries classic SPKI + PQC ciphertext. byte[] msgA = alice.getPeerMessage(); @@ -355,8 +353,7 @@ class HybridKexDemoTest { KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519); // ...Generate PQC (recipient) keys (ML-KEM-768). - KeyPair bobPqc = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); + KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768()); // ...Create a profile for HKDF (output length 32 bytes). HybridKexProfile profile = HybridKexProfile.defaultProfile(32); diff --git a/samples/src/test/java/demo/HybridSigningAesTest.java b/samples/src/test/java/demo/HybridSigningAesTest.java index d7c7d7d..5c7b062 100644 --- a/samples/src/test/java/demo/HybridSigningAesTest.java +++ b/samples/src/test/java/demo/HybridSigningAesTest.java @@ -102,7 +102,8 @@ class HybridSigningAesTest { byte[] msg = randomBytes(100); // AES-GCM with header, runtime params are stored in header - AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader(); + AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .generateKey(256).modeGcm(128).withHeader(); // Hybrid signature: Ed25519 + SPHINCS+ (AND) HybridSignatureProfile profile = new HybridSignatureProfile("Ed25519", "SPHINCS+", null, null, @@ -111,10 +112,10 @@ class HybridSigningAesTest { KeyPair ed = generateKeyPair("Ed25519"); KeyPair spx = generateKeyPair("SPHINCS+"); - SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024); - SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024); + SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024); + SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024); // For verification, make mismatch behavior explicit (builder also supports // throwOnMismatch()). @@ -144,7 +145,8 @@ class HybridSigningAesTest { // encrypted input .add(PlainBytesBuilder.builder().bytes(ciphertext)) // AES-GCM decryption - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()) + .modeGcm(128).withHeader()) // hybrid signature verification .add(new TagTrailerDataContentBuilder(tagDec).bufferSize(8192).throwOnMismatch()).build(); @@ -164,7 +166,8 @@ class HybridSigningAesTest { byte[] msg = randomBytes(100); // AES-GCM with header, runtime params are stored in header - AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader(); + AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .generateKey(256).modeGcm(128).withHeader(); // Hybrid signature: Ed25519 + SPHINCS+ (AND) HybridSignatureProfile profile = new HybridSignatureProfile("Ed25519", "SPHINCS+", null, null, @@ -173,10 +176,10 @@ class HybridSigningAesTest { KeyPair ed = generateKeyPair("Ed25519"); KeyPair spx = generateKeyPair("SPHINCS+"); - SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(), - 2 * 1024 * 1024); - SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(), - 2 * 1024 * 1024); + SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPrivate(), spx.getPrivate(), 2 * 1024 * 1024); + SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, + ed.getPublic(), spx.getPublic(), 2 * 1024 * 1024); tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch()); @@ -208,7 +211,8 @@ class HybridSigningAesTest { // hybrid signature verification .add(new TagTrailerDataContentBuilder(tagDec).bufferSize(8192).throwOnMismatch()) // AES-GCM decryption - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()) + .modeGcm(128).withHeader()) .build(); byte[] decrypted; @@ -224,10 +228,10 @@ class HybridSigningAesTest { private static KeyPair generateKeyPair(String algId) throws GeneralSecurityException { ZeroEchoSession session = new ZeroEchoSession(); return switch (algId) { - case "Ed25519" -> session.keyBuilders().asymmetric().generateKeyPair(algId, - Ed25519KeyGenSpec.defaultSpec()); - case "SPHINCS+" -> session.keyBuilders().asymmetric().generateKeyPair(algId, - SphincsPlusKeyGenSpec.defaultSpec()); + case "Ed25519" -> + session.keyBuilders().asymmetric().generateKeyPair(algId, Ed25519KeyGenSpec.defaultSpec()); + case "SPHINCS+" -> + session.keyBuilders().asymmetric().generateKeyPair(algId, SphincsPlusKeyGenSpec.defaultSpec()); default -> throw new IllegalArgumentException("Unsupported sample signature algorithm: " + algId); }; } diff --git a/samples/src/test/java/demo/PostQuantumTest.java b/samples/src/test/java/demo/PostQuantumTest.java index 638723c..49a4d02 100644 --- a/samples/src/test/java/demo/PostQuantumTest.java +++ b/samples/src/test/java/demo/PostQuantumTest.java @@ -70,8 +70,7 @@ class PostQuantumTest { } KeyPair generateKyberKeys() throws GeneralSecurityException { - KeyPair kp = session.keyBuilders().asymmetric() - .generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); + KeyPair kp = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024()); LOG.log(Level.INFO, "ML-KEM key public={0} private={1}", new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()), diff --git a/samples/src/test/java/demo/SigningAesTest.java b/samples/src/test/java/demo/SigningAesTest.java index d308ea8..fca9359 100644 --- a/samples/src/test/java/demo/SigningAesTest.java +++ b/samples/src/test/java/demo/SigningAesTest.java @@ -66,8 +66,7 @@ class SigningAesTest { private static final Logger LOG = Logger.getLogger(SigningAesTest.class.getName()); KeyPair generateRsaKeys() throws GeneralSecurityException { - KeyPair kp = session.keyBuilders().asymmetric() - .generateKeyPair("RSA", RsaKeyGenSpec.rsa4096()); + KeyPair kp = session.keyBuilders().asymmetric().generateKeyPair("RSA", RsaKeyGenSpec.rsa4096()); LOG.log(Level.INFO, "RSA key public={0} private={1}", new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()), @@ -85,7 +84,8 @@ class SigningAesTest { // Configure AES in GCM mode with a 128-bit authentication tag. A fresh 256-bit // AES key will be generated automatically, and runtime parameters (IV, AAD) // will be written into the header. - AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader(); + AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .generateKey(256).modeGcm(128).withHeader(); // Generate RSA-4096 key pair (retrieved via algorithm registry for convenience) KeyPair rsa = generateRsaKeys(); @@ -93,9 +93,11 @@ class SigningAesTest { // Configure PSS signature parameters: SHA-256 hash, salt length = 32 bytes RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32); // Create signing engine (RSA-PSS with private key) - TagEngine tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); + TagEngine tagEnc = TagEngineBuilder + .rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); // Create verification engine (RSA-PSS with public key) - TagEngine tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); + TagEngine tagDec = TagEngineBuilder + .rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); // Build the encryption pipeline DataContent dccb = DataContentChainBuilder.encrypt() @@ -123,7 +125,8 @@ class SigningAesTest { .add(PlainBytesBuilder.builder().bytes(encrypted)) // AES-GCM decryption using the same key; IV and AAD are restored automatically // from the header - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()) + .modeGcm(128).withHeader()) // Verify the RSA-PSS signature trailer at the end of the stream (configured to // throw on mismatch) .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()) @@ -146,15 +149,18 @@ class SigningAesTest { // Create a random sample message to be encrypted byte[] msg = randomBytes(100); - AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader(); + AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()) + .generateKey(256).modeGcm(128).withHeader(); // Generate RSA-4096 key pair (retrieved via algorithm registry for convenience) KeyPair rsa = generateRsaKeys(); // Configure PSS signature parameters: SHA-256 hash, salt length = 32 bytes RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32); - TagEngine tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); - TagEngine tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); + TagEngine tagEnc = TagEngineBuilder + .rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get(); + TagEngine tagDec = TagEngineBuilder + .rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get(); // Build the encryption pipeline DataContent dccb = DataContentChainBuilder.encrypt() @@ -187,7 +193,8 @@ class SigningAesTest { .add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch()) // AES-GCM decryption using the same key; IV and AAD are restored automatically // from the header - .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader()) + .add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()) + .modeGcm(128).withHeader()) // Build the pipeline .build(); byte[] decrypted;