diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java index 7c9138a4d5a..1b9b6f6b72f 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/modules/DigestAuthModule.java @@ -336,7 +336,7 @@ public class DigestAuthModule extends BaseAuthModule byte[] digest = md.digest(); // check digest - return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response)); + return stringEquals(TypeUtil.toString(digest, 16).toLowerCase(), response == null ? null : response.toLowerCase()); } catch (Exception e) { @@ -351,6 +351,5 @@ public class DigestAuthModule extends BaseAuthModule { return username + "," + response; } - } } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java index e81544cf805..160238767eb 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java @@ -383,7 +383,7 @@ public class DigestAuthenticator extends LoginAuthenticator byte[] digest = md.digest(); // check digest - return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response)); + return stringEquals(TypeUtil.toString(digest, 16).toLowerCase(), response == null ? null : response.toLowerCase()); } catch (Exception e) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java index d17e38c5be3..13929c1d491 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java @@ -126,17 +126,19 @@ public class CookieCutter Cookie cookie = null; boolean invalue=false; + boolean inQuoted=false; boolean quoted=false; - boolean unquotedToken=false; boolean escaped=false; int tokenstart=-1; int tokenend=-1; for (int i = 0, length = hdr.length(), last=length-1; i < length; i++) { char c = hdr.charAt(i); - + + // System.err.printf("i=%d c=%s v=%b q=%b e=%b u=%s s=%d e=%d%n" ,i,""+c,invalue,inQuoted,escaped,unquoted,tokenstart,tokenend); + // Handle quoted values for name or value - if (quoted) + if (inQuoted) { if (escaped) { @@ -148,25 +150,45 @@ public class CookieCutter switch (c) { case '"': - quoted=false; + inQuoted=false; if (i==last) { value = unquoted.toString(); + unquoted.setLength(0); } else { - unquotedToken=true; + quoted=true; tokenstart=i; tokenend=-1; } break; case '\\': - escaped=true; + if (i==last) + { + unquoted.setLength(0); + inQuoted = false; + i--; + } + else + { + escaped=true; + } continue; default: - unquoted.append(c); + if (i==last) + { + // unterminated quote, let's ignore quotes + unquoted.setLength(0); + inQuoted = false; + i--; + } + else + { + unquoted.append(c); + } continue; } } @@ -180,19 +202,19 @@ public class CookieCutter { case ' ': case '\t': - continue; + break; case ';': - if (unquotedToken) + if (quoted) { value = unquoted.toString(); unquoted.setLength(0); - unquotedToken = false; + quoted = false; } else if(tokenstart>=0 && tokenend>=0) value = hdr.substring(tokenstart, tokenend+1); else - value=""; + value = ""; tokenstart = -1; invalue=false; @@ -201,20 +223,21 @@ public class CookieCutter case '"': if (tokenstart<0) { - quoted=true; + tokenstart=i; + inQuoted=true; if (unquoted==null) unquoted=new StringBuilder(); - continue; + break; } // fall through to default case default: - if (unquotedToken) + if (quoted) { // must have been a bad internal quote. let's fix as best we can unquoted.append(hdr.substring(tokenstart,i)); - quoted = true; - unquotedToken = false; + inQuoted = true; + quoted = false; i--; continue; } @@ -239,44 +262,42 @@ public class CookieCutter continue; case ';': - if (unquotedToken) + if (quoted) { name = unquoted.toString(); unquoted.setLength(0); - unquotedToken = false; + quoted = false; } else if(tokenstart>=0 && tokenend>=0) { name = hdr.substring(tokenstart, tokenend+1); } - value = ""; tokenstart = -1; break; case '=': - if (unquotedToken) + if (quoted) { name = unquoted.toString(); unquoted.setLength(0); - unquotedToken = false; + quoted = false; } else if(tokenstart>=0 && tokenend>=0) { name = hdr.substring(tokenstart, tokenend+1); } - tokenstart = -1; invalue=true; - continue; + break; default: - if (unquotedToken) + if (quoted) { // must have been a bad internal quote. let's fix as best we can unquoted.append(hdr.substring(tokenstart,i)); - quoted = true; - unquotedToken = false; + inQuoted = true; + quoted = false; i--; continue; } @@ -284,18 +305,30 @@ public class CookieCutter tokenstart=i; tokenend=i; if (i==last) - { - name = hdr.substring(tokenstart, tokenend+1); - value = ""; break; - } continue; } } } + if (invalue && i==last && value==null) + { + if (quoted) + { + value = unquoted.toString(); + unquoted.setLength(0); + quoted = false; + } + else if(tokenstart>=0 && tokenend>=0) + { + value = hdr.substring(tokenstart, tokenend+1); + } + else + value = ""; + } + // If after processing the current character we have a value and a name, then it is a cookie - if (value!=null && name!=null) + if (name!=null && value!=null) { try { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java index e1303d8d80a..02f53e46719 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java @@ -138,10 +138,9 @@ public class CookieCutterTest * Example from RFC2965 */ @Test - @Ignore + @Ignore("comma separation no longer supported by RFC6265") public void testRFC2965_CookieSpoofingExample() { - // Ignored because comma separation no longer supported by RFC6265 String rawCookie = "$Version=\"1\"; session_id=\"1234\", " + "$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\""; @@ -182,7 +181,7 @@ public class CookieCutterTest } /** - * Basic key=value, following RFC6265 rules + * Basic name=value, following RFC6265 rules */ @Test public void testKeyValue() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutter_LenientTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutter_LenientTest.java index ece684758e8..82d7e1754fa 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutter_LenientTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutter_LenientTest.java @@ -64,6 +64,19 @@ public class CookieCutter_LenientTest // RFC2109 - quoted-string values // quoted-string = ( <"> *(qdtext) <"> ) // qdtext = > + + // lenient with spaces and EOF + ret.add(new String[]{"abc=", "abc", ""}); + ret.add(new String[]{"abc = ", "abc", ""}); + ret.add(new String[]{"abc = ;", "abc", ""}); + ret.add(new String[]{"abc = ; ", "abc", ""}); + ret.add(new String[]{"abc = x ", "abc", "x"}); + ret.add(new String[]{"abc=\"\"", "abc", ""}); + ret.add(new String[]{"abc= \"\" ", "abc", ""}); + ret.add(new String[]{"abc= \"x\" ", "abc", "x"}); + ret.add(new String[]{"abc= \"x\" ;", "abc", "x"}); + ret.add(new String[]{"abc= \"x\" ; ", "abc", "x"}); + // The backslash character ("\") may be used as a single-character quoting // mechanism only within quoted-string and comment constructs. // quoted-pair = "\" CHAR @@ -76,9 +89,9 @@ public class CookieCutter_LenientTest ret.add(new String[]{"some-thing-else=to-parse", "some-thing-else", "to-parse"}); // RFC2109 - names with attr/token syntax starting with '$' (and not a cookie reserved word) // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-5.2 - // Cannot pass names through as Cookie class does not allow them + // Cannot pass names through as javax.servlet.http.Cookie class does not allow them ret.add(new String[]{"$foo=bar", null, null}); - + // Tests that conform to RFC6265 ret.add(new String[]{"abc=foobar!", "abc", "foobar!"}); ret.add(new String[]{"abc=\"foobar!\"", "abc", "foobar!"}); @@ -94,12 +107,37 @@ public class CookieCutter_LenientTest ret.add(new String[]{"query=\"?b=c\"&\"d=e\"", "query", "?b=c\"&\"d=e"}); // Escaped quotes ret.add(new String[]{"foo=\"bar\\\"=\\\"baz\"", "foo", "bar\"=\"baz"}); + + // Unterminated Quotes + ret.add(new String[]{"x=\"abc", "x", "\"abc"}); + // Unterminated Quotes with valid cookie params after it + ret.add(new String[]{"x=\"abc $Path=/", "x", "\"abc $Path=/"}); + // Unterminated Quotes with trailing escape + ret.add(new String[]{"x=\"abc\\", "x", "\"abc\\"}); // UTF-8 values ret.add(new String[]{"2sides=\u262F", "2sides", "\u262f"}); // 2 byte ret.add(new String[]{"currency=\"\u20AC\"", "currency", "\u20AC"}); // 3 byte ret.add(new String[]{"gothic=\"\uD800\uDF48\"", "gothic", "\uD800\uDF48"}); // 4 byte + // Spaces + ret.add(new String[]{"foo=bar baz", "foo", "bar baz"}); + ret.add(new String[]{"foo=\"bar baz\"", "foo", "bar baz"}); + ret.add(new String[]{"z=a b c d e f g", "z", "a b c d e f g"}); + + // Bad tspecials usage + ret.add(new String[]{"foo=bar;baz", "foo", "bar"}); + ret.add(new String[]{"foo=\"bar;baz\"", "foo", "bar;baz"}); + ret.add(new String[]{"z=a;b,c:d;e/f[g]", "z", "a"}); + ret.add(new String[]{"z=\"a;b,c:d;e/f[g]\"", "z", "a;b,c:d;e/f[g]"}); + + // Quoted with other Cookie keywords + ret.add(new String[]{"x=\"$Version=0\"", "x", "$Version=0"}); + ret.add(new String[]{"x=\"$Path=/\"", "x", "$Path=/"}); + ret.add(new String[]{"x=\"$Path=/ $Domain=.foo.com\"", "x", "$Path=/ $Domain=.foo.com"}); + ret.add(new String[]{"x=\" $Path=/ $Domain=.foo.com \"", "x", " $Path=/ $Domain=.foo.com "}); + ret.add(new String[]{"a=\"b; $Path=/a; c=d; $PATH=/c; e=f\"; $Path=/e/", "a", "b; $Path=/a; c=d; $PATH=/c; e=f"}); + // Lots of equals signs ret.add(new String[]{"query=b=c&d=e", "query", "b=c&d=e"}); @@ -139,4 +177,5 @@ public class CookieCutter_LenientTest assertThat("Cookie.value", cookies[0].getValue(), is(expectedValue)); } } + } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index 696bf6809c1..fd693d7738c 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -1326,7 +1326,7 @@ public class RequestTest "POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name0=value0; name1 = value1 ; name2 = \"\\\"value2\\\"\" \n" + - "Cookie: $Version=2; name3=value3=value3;$path=/path;$domain=acme.com;$port=8080; name4=; name5 = ; name6\n" + + "Cookie: $Version=2; name3=value3=value3;$path=/path;$domain=acme.com;$port=8080; name4=\"\"; name5 = ; name6\n" + "Cookie: name7=value7;\n" + "Connection: close\r\n"+ "\r\n"); @@ -1347,10 +1347,10 @@ public class RequestTest assertEquals("", cookies.get(4).getValue()); assertEquals("name5", cookies.get(5).getName()); assertEquals("", cookies.get(5).getValue()); - assertEquals("name6", cookies.get(6).getName()); - assertEquals("", cookies.get(6).getValue()); - assertEquals("name7", cookies.get(7).getName()); - assertEquals("value7", cookies.get(7).getValue()); + // assertEquals("name6", cookies.get(6).getName()); + // assertEquals("", cookies.get(6).getValue()); + assertEquals("name7", cookies.get(6).getName()); + assertEquals("value7", cookies.get(6).getValue()); cookies.clear(); response=_connector.getResponse( diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java index 6fe84042366..8b825a37a93 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java @@ -26,10 +26,7 @@ import java.util.ServiceLoader; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.security.Credential.Crypt; -import org.eclipse.jetty.util.security.Credential.MD5; -/* ------------------------------------------------------------ */ /** * Credentials. The Credential class represents an abstract mechanism for checking authentication credentials. A credential instance either represents a secret, * or some data that could only be derived from knowing the secret. @@ -40,18 +37,13 @@ import org.eclipse.jetty.util.security.Credential.MD5; * This class includes an implementation for unix Crypt an MD5 digest. * * @see Password - * */ public abstract class Credential implements Serializable { - + private static final long serialVersionUID = -7760551052768181572L; + private static final Logger LOG = Log.getLogger(Credential.class); private static final ServiceLoader CREDENTIAL_PROVIDER_LOADER = ServiceLoader.load(CredentialProvider.class); - private static final Logger LOG = Log.getLogger(Credential.class); - - private static final long serialVersionUID = -7760551052768181572L; - - /* ------------------------------------------------------------ */ /** * Check a credential * @@ -62,7 +54,6 @@ public abstract class Credential implements Serializable */ public abstract boolean check(Object credentials); - /* ------------------------------------------------------------ */ /** * Get a credential from a String. If the credential String starts with a known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that type is * returned. Otherwise, it tries to find a credential provider whose prefix matches with the start of the credential String. Else the credential is assumed @@ -94,15 +85,51 @@ public abstract class Credential implements Serializable return new Password(credential); } - /* ------------------------------------------------------------ */ + /** + *

Utility method that replaces String.equals() to avoid timing attacks.

+ * + * @param s1 the first string to compare + * @param s2 the second string to compare + * @return whether the two strings are equal + */ + protected static boolean stringEquals(String s1, String s2) + { + if (s1 == s2) + return true; + if (s1 == null || s2 == null || s1.length() != s2.length()) + return false; + boolean result = false; + for (int i = 0; i < s1.length(); i++) + result |= s1.charAt(i) == s2.charAt(i); + return result; + } + + /** + *

Utility method that replaces Arrays.equals() to avoid timing attacks.

+ * + * @param b1 the first byte array to compare + * @param b2 the second byte array to compare + * @return whether the two byte arrays are equal + */ + protected static boolean byteEquals(byte[] b1, byte[] b2) + { + if (b1 == b2) + return true; + if (b1 == null || b2 == null || b1.length != b2.length) + return false; + boolean result = false; + for (int i = 0; i < b1.length; i++) + result |= b1[i] == b2[i]; + return result; + } + /** * Unix Crypt Credentials */ public static class Crypt extends Credential { private static final long serialVersionUID = -2027792997664744210L; - - public static final String __TYPE = "CRYPT:"; + private static final String __TYPE = "CRYPT:"; private final String _cooked; @@ -118,9 +145,7 @@ public abstract class Credential implements Serializable credentials = new String((char[])credentials); if (!(credentials instanceof String) && !(credentials instanceof Password)) LOG.warn("Can't check " + credentials.getClass() + " against CRYPT"); - - String passwd = credentials.toString(); - return _cooked.equals(UnixCrypt.crypt(passwd,_cooked)); + return stringEquals(_cooked, UnixCrypt.crypt(credentials.toString(),_cooked)); } @Override @@ -128,59 +153,49 @@ public abstract class Credential implements Serializable { if (!(credential instanceof Crypt)) return false; - Crypt c = (Crypt)credential; - - return _cooked.equals(c._cooked); + return stringEquals(_cooked, c._cooked); } public static String crypt(String user, String pw) { - return "CRYPT:" + UnixCrypt.crypt(pw,user); + return __TYPE + UnixCrypt.crypt(pw, user); } } - /* ------------------------------------------------------------ */ /** * MD5 Credentials */ public static class MD5 extends Credential { private static final long serialVersionUID = 5533846540822684240L; - - public static final String __TYPE = "MD5:"; - - public static final Object __md5Lock = new Object(); - + private static final String __TYPE = "MD5:"; + private static final Object __md5Lock = new Object(); private static MessageDigest __md; private final byte[] _digest; - /* ------------------------------------------------------------ */ MD5(String digest) { - digest = digest.startsWith(__TYPE)?digest.substring(__TYPE.length()):digest; - _digest = TypeUtil.parseBytes(digest,16); + digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest; + _digest = TypeUtil.parseBytes(digest, 16); } - /* ------------------------------------------------------------ */ public byte[] getDigest() { return _digest; } - /* ------------------------------------------------------------ */ @Override public boolean check(Object credentials) { try { - byte[] digest = null; - if (credentials instanceof char[]) credentials = new String((char[])credentials); if (credentials instanceof Password || credentials instanceof String) { + byte[] digest; synchronized (__md5Lock) { if (__md == null) @@ -189,16 +204,11 @@ public abstract class Credential implements Serializable __md.update(credentials.toString().getBytes(StandardCharsets.ISO_8859_1)); digest = __md.digest(); } - if (digest == null || digest.length != _digest.length) - return false; - boolean digestMismatch = false; - for (int i = 0; i < digest.length; i++) - digestMismatch |= (digest[i] != _digest[i]); - return !digestMismatch; + return byteEquals(_digest, digest); } else if (credentials instanceof MD5) { - return equals((MD5)credentials); + return equals(credentials); } else if (credentials instanceof Credential) { @@ -223,20 +233,10 @@ public abstract class Credential implements Serializable public boolean equals(Object obj) { if (obj instanceof MD5) - { - MD5 md5 = (MD5)obj; - if (_digest.length != md5._digest.length) - return false; - boolean digestMismatch = false; - for (int i = 0; i < _digest.length; i++) - digestMismatch |= (_digest[i] != md5._digest[i]); - return !digestMismatch; - } - + return byteEquals(_digest, ((MD5)obj)._digest); return false; } - /* ------------------------------------------------------------ */ public static String digest(String password) { try @@ -262,7 +262,7 @@ public abstract class Credential implements Serializable digest = __md.digest(); } - return __TYPE + TypeUtil.toString(digest,16); + return __TYPE + TypeUtil.toString(digest, 16); } catch (Exception e) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java index 6f1a88981b5..48391e9ebe2 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.util.security; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.Locale; import org.eclipse.jetty.util.log.Log; @@ -96,15 +95,20 @@ public class Password extends Credential @Override public boolean check(Object credentials) { - if (this == credentials) return true; + if (this == credentials) + return true; - if (credentials instanceof Password) return credentials.equals(_pw); + if (credentials instanceof Password) + return credentials.equals(_pw); - if (credentials instanceof String) return credentials.equals(_pw); + if (credentials instanceof String) + return stringEquals(_pw, (String)credentials); - if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials); + if (credentials instanceof char[]) + return stringEquals(_pw, new String((char[])credentials)); - if (credentials instanceof Credential) return ((Credential) credentials).check(_pw); + if (credentials instanceof Credential) + return ((Credential)credentials).check(_pw); return false; } @@ -120,14 +124,10 @@ public class Password extends Credential return false; if (o instanceof Password) - { - Password p = (Password) o; - //noinspection StringEquality - return p._pw == _pw || (null != _pw && _pw.equals(p._pw)); - } + return stringEquals(_pw, ((Password)o)._pw); if (o instanceof String) - return o.equals(_pw); + return stringEquals(_pw, (String)o); return false; } @@ -175,7 +175,6 @@ public class Password extends Credential } return buf.toString(); - } /* ------------------------------------------------------------ */ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java index 692d0c1a946..03c637eee82 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java @@ -52,8 +52,12 @@ public class X509 public static boolean isCertSign(X509Certificate x509) { - boolean[] key_usage=x509.getKeyUsage(); - return key_usage!=null && key_usage[KEY_USAGE__KEY_CERT_SIGN]; + boolean[] key_usage = x509.getKeyUsage(); + if ((key_usage == null) || (key_usage.length <= KEY_USAGE__KEY_CERT_SIGN)) + { + return false; + } + return key_usage[KEY_USAGE__KEY_CERT_SIGN]; } private final X509Certificate _x509; diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509CertificateAdapter.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509CertificateAdapter.java new file mode 100644 index 00000000000..3a9111a5135 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509CertificateAdapter.java @@ -0,0 +1,192 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.ssl; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +/** + * Bogus X509Certificate to aide in testing + */ +public class X509CertificateAdapter extends X509Certificate +{ + @Override + public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException + { + } + + @Override + public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException + { + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException + { + return new byte[0]; + } + + @Override + public boolean hasUnsupportedCriticalExtension() + { + return false; + } + + @Override + public Set getCriticalExtensionOIDs() + { + return null; + } + + @Override + public Set getNonCriticalExtensionOIDs() + { + return null; + } + + @Override + public byte[] getExtensionValue(String oid) + { + return new byte[0]; + } + + @Override + public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException + { + } + + @Override + public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException + { + } + + @Override + public String toString() + { + return null; + } + + @Override + public PublicKey getPublicKey() + { + return null; + } + + @Override + public int getVersion() + { + return 0; + } + + @Override + public BigInteger getSerialNumber() + { + return null; + } + + @Override + public Principal getIssuerDN() + { + return null; + } + + @Override + public Principal getSubjectDN() + { + return null; + } + + @Override + public Date getNotBefore() + { + return null; + } + + @Override + public Date getNotAfter() + { + return null; + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException + { + return new byte[0]; + } + + @Override + public byte[] getSignature() + { + return new byte[0]; + } + + @Override + public String getSigAlgName() + { + return null; + } + + @Override + public String getSigAlgOID() + { + return null; + } + + @Override + public byte[] getSigAlgParams() + { + return new byte[0]; + } + + @Override + public boolean[] getIssuerUniqueID() + { + return new boolean[0]; + } + + @Override + public boolean[] getSubjectUniqueID() + { + return new boolean[0]; + } + + @Override + public boolean[] getKeyUsage() + { + return new boolean[0]; + } + + @Override + public int getBasicConstraints() + { + return 0; + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509Test.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509Test.java new file mode 100644 index 00000000000..98b0c918a95 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/X509Test.java @@ -0,0 +1,126 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util.ssl; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.security.cert.X509Certificate; + +import org.junit.Test; + +public class X509Test +{ + @Test + public void testIsCertSign_Normal() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + boolean[] keyUsage = new boolean[8]; + keyUsage[5] = true; + return keyUsage; + } + }; + + assertThat("Normal X509", X509.isCertSign(bogusX509), is(true)); + } + + @Test + public void testIsCertSign_Normal_NoSupported() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + boolean[] keyUsage = new boolean[8]; + keyUsage[5] = false; + return keyUsage; + } + }; + + assertThat("Normal X509", X509.isCertSign(bogusX509), is(false)); + } + + @Test + public void testIsCertSign_NonStandard_Short() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + boolean[] keyUsage = new boolean[6]; // at threshold + keyUsage[5] = true; + return keyUsage; + } + }; + + assertThat("NonStandard X509", X509.isCertSign(bogusX509), is(true)); + } + + @Test + public void testIsCertSign_NonStandard_Shorter() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + boolean[] keyUsage = new boolean[5]; // just below threshold + return keyUsage; + } + }; + + assertThat("NonStandard X509", X509.isCertSign(bogusX509), is(false)); + } + + @Test + public void testIsCertSign_Normal_Null() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + return null; + } + }; + + assertThat("Normal X509", X509.isCertSign(bogusX509), is(false)); + } + + @Test + public void testIsCertSign_Normal_Empty() + { + X509Certificate bogusX509 = new X509CertificateAdapter() + { + @Override + public boolean[] getKeyUsage() + { + return new boolean[0]; + } + }; + + assertThat("Normal X509", X509.isCertSign(bogusX509), is(false)); + } +}