| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.internal.net; |
| |
| import com.android.internal.net.DomainNameValidator; |
| import com.android.frameworks.coretests.R; |
| import com.android.frameworks.coretests.R.raw; |
| |
| import android.test.AndroidTestCase; |
| |
| import java.io.InputStream; |
| 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.CertificateFactory; |
| import java.security.cert.CertificateNotYetValidException; |
| import java.security.cert.CertificateParsingException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.security.auth.x500.X500Principal; |
| |
| public class DomainNameValidatorTest extends AndroidTestCase { |
| private static final int ALT_UNKNOWN = 0; |
| private static final int ALT_DNS_NAME = 2; |
| private static final int ALT_IPA_NAME = 7; |
| |
| /** |
| * Tests {@link DomainNameValidator#match}, using a simple {@link X509Certificate} |
| * implementation. |
| */ |
| public void testMatch() { |
| checkMatch("11", new StubX509Certificate("cn=imap.g.com"), "imap.g.com", true); |
| checkMatch("12", new StubX509Certificate("cn=imap2.g.com"), "imap.g.com", false); |
| checkMatch("13", new StubX509Certificate("cn=sub.imap.g.com"), "imap.g.com", false); |
| |
| // If a subjectAltName extension of type dNSName is present, that MUST |
| // be used as the identity |
| checkMatch("21", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com") |
| , "imap.g.com", false); |
| checkMatch("22", new StubX509Certificate("cn=imap.g.com") // This cn should be ignored |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com") |
| , "imap.g.com", false); |
| checkMatch("23", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") |
| , "imap.g.com", true); |
| |
| // With wildcards |
| checkMatch("24", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com") |
| , "imap.g.com", true); |
| |
| // host name is ip address |
| checkMatch("31", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4") |
| , "1.2.3.4", true); |
| checkMatch("32", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4") |
| , "1.2.3.5", false); |
| checkMatch("32", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1") |
| , "192.168.100.1", true); |
| |
| // Has unknown subject alternative names |
| checkMatch("41", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3") |
| , "imap.g.com", true); |
| |
| checkMatch("42", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3") |
| , "2.33.44.55", true); |
| |
| checkMatch("43", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3") |
| , "g.com", false); |
| |
| checkMatch("44", new StubX509Certificate("") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com") |
| .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") |
| .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") |
| .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3") |
| , "2.33.44.1", false); |
| } |
| |
| private void checkMatch(String message, X509Certificate certificate, String thisDomain, |
| boolean expected) { |
| Boolean actual = DomainNameValidator.match(certificate, thisDomain); |
| assertEquals(message, (Object) expected, (Object) actual); |
| } |
| |
| /** |
| * Tests {@link DomainNameValidator#matchDns} |
| */ |
| public void testMatchDns() { |
| checkMatchDns("11", "a.b.c.d", "a.b.c.d", true); |
| checkMatchDns("12", "a.b.c.d", "*.b.c.d", true); |
| checkMatchDns("13", "b.c.d", "*.b.c.d", true); |
| checkMatchDns("14", "b.c.d", "b*.c.d", true); |
| |
| checkMatchDns("15", "a.b.c.d", "*.*.c.d", false); |
| checkMatchDns("16", "a.b.c.d", "*.c.d", false); |
| |
| checkMatchDns("21", "imap.google.com", "imap.google.com", true); |
| checkMatchDns("22", "imap2.google.com", "imap.google.com", false); |
| checkMatchDns("23", "imap.google.com", "*.google.com", true); |
| checkMatchDns("24", "imap2.google.com", "*.google.com", true); |
| checkMatchDns("25", "imap.google.com", "*.googl.com", false); |
| checkMatchDns("26", "imap2.google2.com", "*.google3.com", false); |
| checkMatchDns("27", "imap.google.com", "ima*.google.com", true); |
| checkMatchDns("28", "imap.google.com", "imap*.google.com", true); |
| checkMatchDns("29", "imap.google.com", "*.imap.google.com", true); |
| |
| checkMatchDns("41", "imap.google.com", "a*.google.com", false); |
| checkMatchDns("42", "imap.google.com", "ix*.google.com", false); |
| |
| checkMatchDns("51", "imap.google.com", "iMap.Google.Com", true); |
| } |
| |
| private void checkMatchDns(String message, String thisDomain, String thatDomain, |
| boolean expected) { |
| boolean actual = DomainNameValidator.matchDns(thisDomain, thatDomain); |
| assertEquals(message, expected, actual); |
| } |
| |
| /** |
| * Test {@link DomainNameValidator#match} with actual certificates. |
| */ |
| public void testWithActualCert() throws Exception { |
| // subject_only |
| // |
| // subject: C=JP, CN=www.example.com |
| // subject alt names: n/a |
| checkWithActualCert("11", R.raw.subject_only, "www.example.com", true); |
| checkWithActualCert("12", R.raw.subject_only, "www2.example.com", false); |
| |
| // subject_alt_only |
| // |
| // subject: C=JP (no CN) |
| // subject alt names: DNS:www.example.com |
| checkWithActualCert("21", R.raw.subject_alt_only, "www.example.com", true); |
| checkWithActualCert("22", R.raw.subject_alt_only, "www2.example.com", false); |
| |
| // subject_with_alt_names |
| // |
| // subject: C=JP, CN=www.example.com |
| // subject alt names: DNS:www2.example.com, DNS:www3.example.com |
| // * Subject should be ignored, because it has subject alt names. |
| checkWithActualCert("31", R.raw.subject_with_alt_names, "www.example.com", false); |
| checkWithActualCert("32", R.raw.subject_with_alt_names, "www2.example.com", true); |
| checkWithActualCert("33", R.raw.subject_with_alt_names, "www3.example.com", true); |
| checkWithActualCert("34", R.raw.subject_with_alt_names, "www4.example.com", false); |
| |
| // subject_with_wild_alt_name |
| // |
| // subject: C=JP, CN=www.example.com |
| // subject alt names: DNS:*.example2.com |
| // * Subject should be ignored, because it has subject alt names. |
| checkWithActualCert("41", R.raw.subject_with_wild_alt_name, "www.example.com", false); |
| checkWithActualCert("42", R.raw.subject_with_wild_alt_name, "www2.example.com", false); |
| checkWithActualCert("43", R.raw.subject_with_wild_alt_name, "www.example2.com", true); |
| checkWithActualCert("44", R.raw.subject_with_wild_alt_name, "abc.example2.com", true); |
| checkWithActualCert("45", R.raw.subject_with_wild_alt_name, "www.example3.com", false); |
| |
| // wild_alt_name_only |
| // |
| // subject: C=JP |
| // subject alt names: DNS:*.example.com |
| checkWithActualCert("51", R.raw.wild_alt_name_only, "www.example.com", true); |
| checkWithActualCert("52", R.raw.wild_alt_name_only, "www2.example.com", true); |
| checkWithActualCert("53", R.raw.wild_alt_name_only, "www.example2.com", false); |
| |
| // wild_alt_name_only |
| // |
| // subject: C=JP |
| // subject alt names: IP Address:192.168.10.1 |
| checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.1", true); |
| checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.2", false); |
| } |
| |
| private void checkWithActualCert(String message, int resId, String domain, |
| boolean expected) throws Exception { |
| CertificateFactory factory = CertificateFactory.getInstance("X509"); |
| InputStream certStream = getContext().getResources().openRawResource(resId); |
| X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream); |
| |
| checkMatch(message, certificate, domain, expected); |
| } |
| |
| /** |
| * Minimal {@link X509Certificate} implementation for {@link DomainNameValidator}. |
| */ |
| private static class StubX509Certificate extends X509Certificate { |
| private final X500Principal subjectX500Principal; |
| private Collection<List<?>> subjectAlternativeNames; |
| |
| public StubX509Certificate(String subjectDn) { |
| subjectX500Principal = new X500Principal(subjectDn); |
| subjectAlternativeNames = null; |
| } |
| |
| public StubX509Certificate addSubjectAlternativeName(int type, String name) { |
| if (subjectAlternativeNames == null) { |
| subjectAlternativeNames = new ArrayList<List<?>>(); |
| } |
| LinkedList<Object> entry = new LinkedList<Object>(); |
| entry.add(type); |
| entry.add(name); |
| subjectAlternativeNames.add(entry); |
| return this; |
| } |
| |
| @Override |
| public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException { |
| return subjectAlternativeNames; |
| } |
| |
| @Override |
| public X500Principal getSubjectX500Principal() { |
| return subjectX500Principal; |
| } |
| |
| @Override |
| public void checkValidity() throws CertificateExpiredException, |
| CertificateNotYetValidException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public void checkValidity(Date date) throws CertificateExpiredException, |
| CertificateNotYetValidException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public int getBasicConstraints() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public Principal getIssuerDN() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public boolean[] getIssuerUniqueID() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public boolean[] getKeyUsage() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public Date getNotAfter() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public Date getNotBefore() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public BigInteger getSerialNumber() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public String getSigAlgName() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public String getSigAlgOID() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public byte[] getSigAlgParams() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public byte[] getSignature() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public Principal getSubjectDN() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public boolean[] getSubjectUniqueID() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public byte[] getTBSCertificate() throws CertificateEncodingException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public int getVersion() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public byte[] getEncoded() throws CertificateEncodingException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public PublicKey getPublicKey() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public String toString() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, |
| InvalidKeyException, NoSuchProviderException, SignatureException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| @Override |
| public void verify(PublicKey key, String sigProvider) throws CertificateException, |
| NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, |
| SignatureException { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| public Set<String> getCriticalExtensionOIDs() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| public byte[] getExtensionValue(String oid) { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| public Set<String> getNonCriticalExtensionOIDs() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| |
| public boolean hasUnsupportedCriticalExtension() { |
| throw new RuntimeException("Method not implemented"); |
| } |
| } |
| } |