diff --git a/Jenkinsfile b/Jenkinsfile index 2f56f6b9a85..6684401e026 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ node { { stage 'Compile' withEnv(mvnEnv) { - timeout(15) { + timeout(time: 15, unit: 'MINUTES') { sh "mvn -B clean install -Dtest=None" } } @@ -33,7 +33,7 @@ node { { stage 'Javadoc' withEnv(mvnEnv) { - timeout(15) { + timeout(time: 15, unit: 'MINUTES') { sh "mvn -B javadoc:javadoc" } } @@ -46,7 +46,7 @@ node { { stage 'Test' withEnv(mvnEnv) { - timeout(60) { + timeout(time: 60, unit: 'MINUTES') { // Run test phase / ignore test failures sh "mvn -B install -Dmaven.test.failure.ignore=true" // Report failures in the jenkins UI @@ -54,6 +54,7 @@ node { testResults: '**/target/surefire-reports/TEST-*.xml']) // Collect up the jacoco execution results step([$class: 'JacocoPublisher', + inclusionPattern: "**/org/eclipse/jetty/**/*.class", execPattern: '**/target/jacoco.exec', classPattern: '**/target/classes', sourcePattern: '**/src/main/java']) diff --git a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc index a26491a2dea..b5c452875e3 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc @@ -396,18 +396,30 @@ jetty.sslContext.keyStorePassword:: To enable two-way authentication, you first need to activate the ssl module as shown in the previous section. +First you need load the `ssl` module and `https` module. [source%nowrap,ini,linenums] -.start.d/ssl.ini +.$JETTY_BASE/start.d/ssl.ini ---- +# Module: ssl --module=ssl -jetty.secure.port=8443 -jetty.keystore=etc/keystore -jetty.keystore.password=OBF: -jetty.keymanager.password=OBF: -jetty.truststore=etc/truststore -jetty.truststore.password=OBF: + +jetty.ssl.host=0.0.0.0 +jetty.ssl.port=8583 +jetty.sslContext.keyStorePath=etc/keystore +jetty.sslContext.trustStorePath=etc/keystore +jetty.sslContext.keyStorePassword=OBF: +jetty.sslContext.keyManagerPassword=OBF: +jetty.sslContext.trustStorePassword=OBF: +jetty.sslContext.trustStoreType=JKS # enable two way authentication -jetty.ssl.needClientAuth=true +jetty.sslContext.needClientAuth=true +---- + +[source%nowrap,ini,linenums] +.$JETTY_BASE/start.d/https.ini +---- +# Module: https +--module=https ---- [[layout-of-keystore-and-truststore]] @@ -415,19 +427,47 @@ jetty.ssl.needClientAuth=true `keystore` only contains the server's private key and certificate. +[[img-certificate-chain]] +image::images/certificate-chain.png[title="Certificate chain", alt="Certificate chain"] + +[literal] +.The structure of KeyStore file +.... +├── PrivateKeyEntry +│   ├── PrivateKey +│   ├── Certificate chain +│   │   ├── Server certificate (end entity) +│   │   ├── Intermediary CA certificate +│   │   └── Root CA certificate +├── TrustedCertEntry +│   └── Intermediary CA certificate +└── TrustedCertEntry +    └── Root CA certificate +.... + +[TIP] +==== +└── PrivateKeyEntry + +    └── Certificate chain + +       ├── Intermediary CA certificate + +       └── Root CA certificate + +are optional +==== + [source%nowrap,plain,linenums] ---- -$ keytool -list -keystore keystore -storetype jks -storepass '' -v +$ cd $JETTY_BASE +$ keytool -list -keystore etc/keystore -storetype jks -storepass '' -v Keystore type: JKS Keystore provider: SUN -Your keystore contains 1 entry +Your keystore contains 3 entries Alias name: *.example.com -Creation date: Sep 12, 2016 +Creation date: Sep 20, 2016 Entry type: PrivateKeyEntry -Certificate chain length: 1 +Certificate chain length: 3 Certificate[1]: Owner: CN=*.example.com, OU=Web Servers, O="Example.com Co.,Ltd.", C=CN Issuer: CN="Example.com Co.,Ltd. ETP CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN @@ -477,26 +517,98 @@ KeyIdentifier [ ] ] +Certificate[2]: +Owner: CN="Example.com Co.,Ltd. ETP CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN +Issuer: CN="Example.com Co.,Ltd. Root CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN +Serial number: f6e7b86f6fdb467f9498fb599310198f +Valid from: Wed Nov 18 00:00:00 CST 2015 until: Sun Nov 18 00:00:00 CST 2035 +Certificate fingerprints: + MD5: ED:A3:91:57:D8:B8:6E:B1:01:58:55:5C:33:14:F5:99 + SHA1: D9:A4:93:9D:A6:F8:A3:F9:FD:85:51:E2:C5:2E:0B:EE:80:E7:D0:22 + SHA256: BF:54:7A:F6:CA:0C:FA:EF:93:B6:6B:6E:2E:D7:44:A8:40:00:EC:69:3A:2C:CC:9A:F7:FE:8E:6F:C0:FA:22:38 + Signature algorithm name: SHA256withRSA + Version: 3 + +Extensions: + +#1: ObjectId: 2.5.29.35 Criticality=false +AuthorityKeyIdentifier [ +KeyIdentifier [ +0000: A6 BD 5F B3 E8 7D 74 3D 20 44 66 1A 16 3B 1B DF .._...t= Df..;.. +0010: E6 E6 04 46 ...F +] +] + +#2: ObjectId: 2.5.29.19 Criticality=true +BasicConstraints:[ + CA:true + PathLen:2147483647 +] + +#3: ObjectId: 2.5.29.15 Criticality=true +KeyUsage [ + Key_CertSign + Crl_Sign +] + +#4: ObjectId: 2.5.29.14 Criticality=false +SubjectKeyIdentifier [ +KeyIdentifier [ +0000: 44 9B AD 31 E7 FE CA D5 5A 8E 17 55 F9 F0 1D 6B D..1....Z..U...k +0010: F5 A5 8F C1 .... +] +] + +Certificate[3]: +Owner: CN="Example.com Co.,Ltd. Root CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN +Issuer: CN="Example.com Co.,Ltd. Root CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN +Serial number: f0a45bc9972c458cbeae3f723055f1ac +Valid from: Wed Nov 18 00:00:00 CST 2015 until: Sun Nov 18 00:00:00 CST 2114 +Certificate fingerprints: + MD5: 50:61:62:22:71:60:F7:69:2E:27:42:6B:62:31:82:79 + SHA1: 7A:6D:A6:48:B1:43:03:3B:EA:A0:29:2F:19:65:9C:9B:0E:B1:03:1A + SHA256: 05:3B:9C:5B:8E:18:61:61:D1:9C:AA:0E:8C:B1:EA:44:C2:6E:67:5D:96:30:EC:8C:F6:6F:E1:EC:AD:00:60:F1 + Signature algorithm name: SHA256withRSA + Version: 3 + +Extensions: + +#1: ObjectId: 2.5.29.35 Criticality=false +AuthorityKeyIdentifier [ +KeyIdentifier [ +0000: A6 BD 5F B3 E8 7D 74 3D 20 44 66 1A 16 3B 1B DF .._...t= Df..;.. +0010: E6 E6 04 46 ...F +] +] + +#2: ObjectId: 2.5.29.19 Criticality=true +BasicConstraints:[ + CA:true + PathLen:2147483647 +] + +#3: ObjectId: 2.5.29.15 Criticality=true +KeyUsage [ + Key_CertSign + Crl_Sign +] + +#4: ObjectId: 2.5.29.14 Criticality=false +SubjectKeyIdentifier [ +KeyIdentifier [ +0000: A6 BD 5F B3 E8 7D 74 3D 20 44 66 1A 16 3B 1B DF .._...t= Df..;.. +0010: E6 E6 04 46 ...F +] +] + ******************************************* ******************************************* ----- - -`truststore` contains intermediary CA and root CA. - -[source%nowrap,plain,linenums] ----- -$ keytool -list -keystore truststore -storetype jks -storepass '' -v - -Keystore type: JKS -Keystore provider: SUN - -Your keystore contains 2 entries Alias name: example.com co.,ltd. etp ca -Creation date: Sep 12, 2016 +Creation date: Sep 20, 2016 Entry type: trustedCertEntry Owner: CN="Example.com Co.,Ltd. ETP CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN @@ -547,7 +659,7 @@ KeyIdentifier [ Alias name: example.com co.,ltd. root ca -Creation date: Sep 12, 2016 +Creation date: Sep 20, 2016 Entry type: trustedCertEntry Owner: CN="Example.com Co.,Ltd. Root CA", OU=CA Center, O="Example.com Co.,Ltd.", C=CN @@ -597,10 +709,27 @@ KeyIdentifier [ ******************************************* ---- -____ -[NOTE] -If you use a keystore which contains only one `PrivateKeyEntry` item as the `keystore` and the `truststore`, you may get a `javax.net.ssl.SSLHandshakeException` with `null cert chain` message. -____ +In addition, you can split `$JETTY/etc/keystore` as two files. +One is `$JETTY/etc/keystore` which only contains the server’s private key and certificate, +the other is `$JETTY/etc/truststore` which contains intermediary CA and root CA. + +[literal] +.The structure of `$JETTY/etc/keystore` +.... +└── PrivateKeyEntry +    ├── PrivateKey +    └── Certificate chain +       └── Server certificate (end entity) +.... + +[literal] +.The structure of `$JETTY/etc/truststore` +.... +├── TrustedCertEntry +│   └── Intermediary CA certificate +└── TrustedCertEntry +    └── Root CA certificate +.... [[configuring-sslcontextfactory]] ==== Configuring the Jetty SslContextFactory diff --git a/jetty-documentation/src/main/asciidoc/configuring/connectors/images/certificate-chain.png b/jetty-documentation/src/main/asciidoc/configuring/connectors/images/certificate-chain.png new file mode 100644 index 00000000000..6d2a614526f Binary files /dev/null and b/jetty-documentation/src/main/asciidoc/configuring/connectors/images/certificate-chain.png differ diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java index aa4140d90b9..8421d02ca31 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/LdapLoginModule.java @@ -181,20 +181,22 @@ public class LdapLoginModule extends AbstractLoginModule public class LDAPUserInfo extends UserInfo { - + Attributes attributes; + /** * @param userName * @param credential */ - public LDAPUserInfo(String userName, Credential credential) + public LDAPUserInfo(String userName, Credential credential, Attributes attributes) { super(userName, credential); + this.attributes = attributes; } @Override public List doFetchRoles() throws Exception { - return getUserRoles(_rootContext, getUserName()); + return getUserRoles(_rootContext, getUserName(), attributes); } } @@ -214,7 +216,8 @@ public class LdapLoginModule extends AbstractLoginModule */ public UserInfo getUserInfo(String username) throws Exception { - String pwdCredential = getUserCredentials(username); + Attributes attributes = getUserAttributes(username); + String pwdCredential = getUserCredentials(attributes); if (pwdCredential == null) { @@ -223,7 +226,7 @@ public class LdapLoginModule extends AbstractLoginModule pwdCredential = convertCredentialLdapToJetty(pwdCredential); Credential credential = Credential.getCredential(pwdCredential); - return new LDAPUserInfo(username, credential); + return new LDAPUserInfo(username, credential, attributes); } protected String doRFC2254Encoding(String inputString) @@ -258,7 +261,7 @@ public class LdapLoginModule extends AbstractLoginModule } /** - * attempts to get the users credentials from the users context + * attempts to get the users LDAP attributes from the users context *

* NOTE: this is not an user authenticated operation * @@ -266,53 +269,39 @@ public class LdapLoginModule extends AbstractLoginModule * @return * @throws LoginException */ - private String getUserCredentials(String username) throws LoginException + private Attributes getUserAttributes(String username) throws LoginException { - String ldapCredential = null; + Attributes attributes = null; - SearchControls ctls = new SearchControls(); - ctls.setCountLimit(1); - ctls.setDerefLinkFlag(true); - ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - String filter = "(&(objectClass={0})({1}={2}))"; - - LOG.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); - - try - { - Object[] filterArguments = {_userObjectClass, _userIdAttribute, username}; - NamingEnumeration results = _rootContext.search(_userBaseDn, filter, filterArguments, ctls); - - LOG.debug("Found user?: " + results.hasMoreElements()); - - if (!results.hasMoreElements()) - { - throw new LoginException("User not found."); - } - - SearchResult result = findUser(username); - - Attributes attributes = result.getAttributes(); - - Attribute attribute = attributes.get(_userPasswordAttribute); - if (attribute != null) - { - try - { - byte[] value = (byte[]) attribute.get(); - - ldapCredential = new String(value); - } - catch (NamingException e) - { - LOG.debug("no password available under attribute: " + _userPasswordAttribute); - } - } - } - catch (NamingException e) - { + SearchResult result; + try { + result = findUser(username); + attributes = result.getAttributes(); + } + catch (NamingException e) { throw new LoginException("Root context binding failure."); + } + + return attributes; + } + + private String getUserCredentials(Attributes attributes) throws LoginException + { + String ldapCredential = null; + + Attribute attribute = attributes.get(_userPasswordAttribute); + if (attribute != null) + { + try + { + byte[] value = (byte[]) attribute.get(); + + ldapCredential = new String(value); + } + catch (NamingException e) + { + LOG.debug("no password available under attribute: " + _userPasswordAttribute); + } } LOG.debug("user cred is: " + ldapCredential); @@ -330,9 +319,22 @@ public class LdapLoginModule extends AbstractLoginModule * @return * @throws LoginException */ - private List getUserRoles(DirContext dirContext, String username) throws LoginException, NamingException + private List getUserRoles(DirContext dirContext, String username, Attributes attributes) throws LoginException, NamingException { - String userDn = _userRdnAttribute + "=" + username + "," + _userBaseDn; + String rdnValue = username; + Attribute attribute = attributes.get(_userRdnAttribute); + if (attribute != null) + { + try + { + rdnValue = (String) attribute.get(); // switch to the value stored in the _userRdnAttribute if we can + } + catch (NamingException e) + { + } + } + + String userDn = _userRdnAttribute + "=" + rdnValue + "," + _userBaseDn; return getUserRolesByDn(dirContext, userDn); } @@ -537,7 +539,7 @@ public class LdapLoginModule extends AbstractLoginModule String filter = "(&(objectClass={0})({1}={2}))"; if (LOG.isDebugEnabled()) - LOG.debug("Searching for users with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); + LOG.debug("Searching for user " + username + " with filter: \'" + filter + "\'" + " from base dn: " + _userBaseDn); Object[] filterArguments = new Object[]{ _userObjectClass,