11/* dCache - http://www.dcache.org/
22 *
3- * Copyright (C) 2015 - 2023 Deutsches Elektronen-Synchrotron
3+ * Copyright (C) 2015 - 2025 Deutsches Elektronen-Synchrotron
44 *
55 * This program is free software: you can redistribute it and/or modify
66 * it under the terms of the GNU Affero General Public License as
3939import eu .emi .security .authn .x509 .ValidationErrorCategory ;
4040import eu .emi .security .authn .x509 .X509CertChainValidator ;
4141import eu .emi .security .authn .x509 .X509Credential ;
42+ import eu .emi .security .authn .x509 .helpers .AbstractDelegatingX509Credential ;
43+ import eu .emi .security .authn .x509 .helpers .AbstractX509Credential ;
44+ import eu .emi .security .authn .x509 .helpers .KeyStoreHelper ;
45+ import eu .emi .security .authn .x509 .helpers .PasswordSupplier ;
4246import eu .emi .security .authn .x509 .helpers .ssl .SSLTrustManager ;
47+ import eu .emi .security .authn .x509 .impl .CertificateUtils ;
48+ import eu .emi .security .authn .x509 .impl .KeystoreCredential ;
4349import eu .emi .security .authn .x509 .impl .OpensslCertChainValidator ;
44- import eu .emi .security .authn .x509 .impl .PEMCredential ;
4550import eu .emi .security .authn .x509 .impl .ValidatorParams ;
4651import io .netty .handler .ssl .SslContext ;
4752import io .netty .handler .ssl .SslContextBuilder ;
4853import java .io .File ;
54+ import java .io .FileInputStream ;
4955import java .io .FileNotFoundException ;
5056import java .io .IOException ;
57+ import java .io .InputStream ;
5158import java .nio .file .FileSystems ;
5259import java .nio .file .Path ;
5360import java .security .GeneralSecurityException ;
61+ import java .security .InvalidKeyException ;
62+ import java .security .KeyStoreException ;
63+ import java .security .NoSuchAlgorithmException ;
64+ import java .security .PrivateKey ;
65+ import java .security .PublicKey ;
5466import java .security .SecureRandom ;
67+ import java .security .Signature ;
68+ import java .security .SignatureException ;
69+ import java .security .cert .CertificateException ;
5570import java .security .cert .X509Certificate ;
71+ import java .security .interfaces .RSAPrivateKey ;
72+ import java .security .interfaces .RSAPublicKey ;
5673import java .util .EnumSet ;
5774import java .util .concurrent .Callable ;
5875import java .util .concurrent .TimeUnit ;
@@ -313,8 +330,8 @@ public <T> Callable<T> buildWithCaching(Class<T> contextType) throws Exception {
313330 * https://github.com/eu-emi/canl-java/issues/114
314331 */
315332 Callable newContext = () -> {
316- PEMCredential credential
317- = new PEMCredential (keyPath .toString (), certificatePath .toString (), new char []{});
333+ PEMCredential0 credential
334+ = new PEMCredential0 (keyPath .toString (), certificatePath .toString (), new char []{});
318335 LOGGER .info ("Reloading host credential {} {}" , certificatePath , keyPath );
319336 return factory .getContext (contextType , credential );
320337 };
@@ -326,4 +343,115 @@ public <T> Callable<T> buildWithCaching(Class<T> contextType) throws Exception {
326343 credentialUpdateIntervalUnit );
327344 }
328345 }
346+
347+ // REVISIT: PEMCredential0 is a copy of PEMCredential that uses custom KeyAndCertCredential
348+ // This workaround is needed as long as CaNL library with desired fix ( > 2.8.3 ) is not released.
349+
350+ private static class PEMCredential0 extends AbstractDelegatingX509Credential {
351+
352+ public PEMCredential0 (String keyPath , String certificatePath , char [] keyPasswd ) throws IOException , CertificateException , KeyStoreException {
353+ this (new FileInputStream (keyPath ), new FileInputStream (certificatePath ), keyPasswd );
354+ }
355+
356+ public PEMCredential0 (InputStream privateKeyStream , InputStream certificateStream , char [] keyPasswd )
357+ throws IOException , KeyStoreException , CertificateException {
358+ this (privateKeyStream , certificateStream , CertificateUtils .getPF (keyPasswd ));
359+ }
360+
361+ public PEMCredential0 (InputStream privateKeyStream , InputStream certificateStream , PasswordSupplier pf )
362+ throws IOException , KeyStoreException {
363+ X509Certificate [] chain = CertificateUtils .loadCertificateChain (
364+ certificateStream , CertificateUtils .Encoding .PEM );
365+ PrivateKey pk = CertificateUtils .loadPEMPrivateKey (privateKeyStream , pf );
366+ privateKeyStream .close ();
367+ delegate = new KeyAndCertCredential0 (pk , chain );
368+ }
369+ }
370+
371+ /**
372+ * KeyAndCertCredential0 is a copy of KeyAndCertCredential that support EC and ECDSA keys.
373+ */
374+ private static class KeyAndCertCredential0 extends AbstractX509Credential {
375+
376+ // a semirandom byte array for ecrypt/decrypt testing
377+ private static final byte [] TEST = new byte []{1 , 2 , 3 , 4 , 100 };
378+
379+ /**
380+ * Creates a new instance from the provided key and certificates.
381+ *
382+ * @param privateKey private key to be placed in this {@link X509Credential}'s KeyStore
383+ * @param certificateChain certificates to be placed in this {@link X509Credential}'s KeyStore.
384+ * those certificates must match the provided privateKey. The user's certificate is assumed
385+ * to be the first entry in the chain.
386+ * @throws KeyStoreException if private key is invalid or doesn't match the certificate.
387+ */
388+ public KeyAndCertCredential0 (PrivateKey privateKey , X509Certificate [] certificateChain )
389+ throws KeyStoreException {
390+ try {
391+ ks = KeyStoreHelper .getInstanceForCredential ("JKS" );
392+ } catch (KeyStoreException e ) {
393+ throw new RuntimeException ("Can't create JKS KeyStore - JDK is misconfgured?" , e );
394+ }
395+
396+ try {
397+ ks .load (null );
398+ } catch (Exception e ) {
399+ throw new RuntimeException ("Can't init JKS KeyStore - JDK is misconfgured?" , e );
400+ }
401+
402+ PublicKey pubKey = certificateChain [0 ].getPublicKey ();
403+ String pubKeyAlgorithm = pubKey .getAlgorithm ();
404+ // REVISIT: BouncyCastle uses "ECDSA" as the private key algorithm and "EC" as the public key algorithm names for elliptic curve keys.
405+ if (!privateKey .getAlgorithm ().equals (pubKeyAlgorithm ) && !(privateKey .getAlgorithm ().equals ("ECDSA" ) && pubKeyAlgorithm .equals ("EC" )))
406+ throw new KeyStoreException ("Private and public keys are not matching: different algorithms: "
407+ + privateKey .getAlgorithm () + " vs. " + pubKeyAlgorithm );
408+
409+ switch (pubKeyAlgorithm ) {
410+ case "DSA" :
411+ if (!checkKeysViaSignature ("SHA1withDSA" , privateKey , pubKey ))
412+ throw new KeyStoreException ("Private and public keys are not matching: DSA" );
413+ break ;
414+ case "RSA" :
415+ RSAPublicKey rpub = (RSAPublicKey ) pubKey ;
416+ RSAPrivateKey rpriv = (RSAPrivateKey ) privateKey ;
417+ if (!rpub .getModulus ().equals (rpriv .getModulus ()))
418+ throw new KeyStoreException ("Private and public keys are not matching: RSA parameters" );
419+ break ;
420+ case "GOST3410" :
421+ if (!checkKeysViaSignature ("GOST3411withGOST3410" , privateKey , pubKey ))
422+ throw new KeyStoreException ("Private and public keys are not matching: GOST 34.10" );
423+ break ;
424+ case "ECGOST3410" :
425+ if (!checkKeysViaSignature ("GOST3411withECGOST3410" , privateKey , pubKey ))
426+ throw new KeyStoreException ("Private and public keys are not matching: EC GOST 34.10" );
427+ break ;
428+ case "ECDSA" :
429+ if (!checkKeysViaSignature ("SHA1withECDSA" , privateKey , pubKey ))
430+ throw new KeyStoreException ("Private and public keys are not matching: EC DSA" );
431+ break ;
432+ }
433+
434+ ks .setKeyEntry (KeystoreCredential .ALIAS , privateKey ,
435+ KeystoreCredential .KEY_PASSWD , certificateChain );
436+ }
437+
438+ private static boolean checkKeysViaSignature (String alg , PrivateKey privKey , PublicKey pubKey ) throws KeyStoreException {
439+ try {
440+ Signature s = Signature .getInstance (alg );
441+ s .initSign (privKey );
442+ s .update (TEST );
443+ byte [] signature = s .sign ();
444+ Signature s2 = Signature .getInstance (alg );
445+ s2 .initVerify (pubKey );
446+ s2 .update (TEST );
447+ return s2 .verify (signature );
448+ } catch (InvalidKeyException e ) {
449+ throw new KeyStoreException ("Invalid key when checking key match" , e );
450+ } catch (NoSuchAlgorithmException e ) {
451+ throw new RuntimeException ("Bug: BC provider not available in checkKeysMatching()" , e );
452+ } catch (SignatureException e ) {
453+ throw new RuntimeException ("Bug: can't sign/verify test data" , e );
454+ }
455+ }
456+ }
329457}
0 commit comments