Java istemcisinde sunucunun kendinden imzalı SSL sertifikasını kabul edin


215

Standart bir soru gibi görünüyor, ama hiçbir yerde net talimatlar bulamadım.

Muhtemelen kendinden imzalı (veya süresi dolmuş) sertifikaya sahip bir sunucuya bağlanmaya çalışan java kodu var. Kod aşağıdaki hatayı bildirir:

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Anladığım kadarıyla, keytool kullanmalı ve java'ya bu bağlantıya izin vermenin iyi olduğunu söylemeliyim.

Bu sorunu gidermek için tüm talimatlar, tuş takımında tamamen yetkin olduğumu varsayar.

sunucu için özel anahtar oluşturun ve anahtar deposuna alın

Ayrıntılı talimatlar gönderebilecek biri var mı?

Unix kullanıyorum, bu yüzden bash betiği en iyisi olurdu.

Önemli olup olmadığından emin değilim, ancak kod jboss'ta yürütüldü.


2
Bkz Bir Java HttpsURLConnection ile kendinden imzalı sertifikayı kabul nasıl? . Açıkçası, siteyi geçerli bir sertifika kullanmanız daha iyi olur.
Matthew Flaschen

2
Bağlantı için teşekkürler, arama yaparken görmedim. Ancak her iki çözüm de bir istek göndermek için özel kod içerir ve ben mevcut kodu kullanıyorum (java için amazon ws istemcisi). Sırasıyla, bağlandığım site bu ve sertifika sorunlarını çözemiyorum.
Nikita Rybak

2
@MatthewFlaschen - "Açıkçası, siteyi geçerli bir sertifika kullanmanız için daha iyi olur ..." - İstemcinin güvendiği durumlarda kendinden imzalı bir sertifika geçerli bir sertifikadır. Birçoğu CA / Tarayıcı karteline güven vermenin bir güvenlik kusuru olduğunu düşünüyor.
jww

3
İlgili, bkz. Dünyadaki en tehlikeli kod: tarayıcı dışı yazılımlarda SSL sertifikalarını doğrulama . (Doğrulamayı devre dışı bırakan spam içerikli yanıtlar aldığınız için bağlantı sağlanmıştır).
jww

Yanıtlar:


307

Burada temel olarak iki seçeneğiniz var: kendinden imzalı sertifikayı JVM güven mağazanıza ekleyin veya istemcinizi

seçenek 1

Sertifikayı tarayıcınızdan dışa aktarın ve JVM güven mağazanıza aktarın (bir güven zinciri oluşturmak için):

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

seçenek 2

Sertifika Doğrulamasını Devre Dışı Bırak:

// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { 
    new X509TrustManager() {     
        public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
            return new X509Certificate[0];
        } 
        public void checkClientTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
            } 
        public void checkServerTrusted( 
            java.security.cert.X509Certificate[] certs, String authType) {
        }
    } 
}; 

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL"); 
    sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

O Not Ben hiç İsteğe Bağlı 2. önermiyoruz . Güven yöneticisini devre dışı bırakmak SSL'nin bazı bölümlerini yener ve sizi orta saldırılarda insana karşı savunmasız hale getirir. Seçenek # 1'i veya daha da iyisi, sunucunun iyi bilinen bir CA tarafından imzalanmış "gerçek" bir sertifika kullanmasını tercih edin.


7
Sadece MIM saldırısı değil. Bu, sizi yanlış siteye bağlanmaya karşı savunmasız hale getirir. Tamamen güvensiz. Bkz. RFC 2246. Bu TrustManager'ı her zaman yayınlamaya karşıyım. Kendi şartnamesiyle bile doğru değil.
Lorne Marquis

10
@EJP Gerçekten ikinci seçeneği önermiyorum (cevabımı netleştirmek için güncelledim). Bununla birlikte, gönderilmemesi hiçbir şeyi çözmez (bu kamuya açık bilgilerdir) ve IMHO bir düşüşü hak etmiyor.
Pascal Thivent

135
@EJP İnsanlara bir şeyler saklayarak değil, onları eğittiğinizi öğreterek. Dolayısıyla şeyleri gizli ya da belirsiz tutmak hiç de bir çözüm değildir. Bu kod herkese açıktır, Java API herkese açıktır, bunun hakkında konuşmak, görmezden gelmekten daha iyidir. Ama seninle hemfikir değil yaşayabilirim.
Pascal Thivent

10
Diğer seçenek - bahsetmediğiniz - sunucunun sertifikasını kendiniz düzelterek veya ilgili destek kişilerini arayarak düzeltmektir. Tek ana bilgisayar sertifikaları gerçekten çok ucuzdur; Kendinden imzalı şeylerle uğraşmak kuruş bilge kiloluk aptallıktır (yani, bu İngilizce deyime aşina olmayanlar için, neredeyse hiçbir şeyden tasarruf etmek için çok pahalıya mal olan tamamen aptal bir öncelik seti).
Donal Fellows

2
@Rich, 6 yıl geç, ancak kilit simgesini -> daha fazla bilgi -> Sertifikayı Görüntüle -> Ayrıntılar sekmesi -> Dışa aktar ...> Firefox'ta sunucu sertifikasını da alabilirsiniz.
Siddhartha

9

Bu sorunu, varsayılan JVM güvenilen ana bilgisayarlarının bir parçası olmayan bir sertifika sağlayıcısına kovaladım JDK 8u74. Sağlayıcı www.identrust.com , ancak bağlanmaya çalıştığım alan adı bu değildi. Bu alan adı sertifikasını bu sağlayıcıdan almıştı. Bkz . Çapraz kök kapak JDK / JRE'deki varsayılan listeye güvenecek mi? - birkaç girişi okuyun. Ayrıca bkz. Hangi tarayıcılar ve işletim sistemleri Let's Encrypt'i destekler .

Bu nedenle, ilgilendiğim alan adı ile bağlantı kurmak için identrust.comaşağıdaki adımları izleyen bir sertifika aldım. Temel olarak, identrust.com (DST Root CA X3 JVM tarafından güvenilecek ) sertifikası . Bunu Apache HttpComponents 4.5 kullanarak böyle yapabildim:

1: Sertifika , Sertifika Zinciri İndirme Talimatlarındaki güvencesinden alın . Tıklayın DST Kök CA X3 bağlantısını.

2: Dizeyi "DST Root CA X3.pem" adlı bir dosyaya kaydedin. Dosyaya başında ve sonunda "----- BAŞLANGIÇ BELGESİ -----" ve "----- SON BELGESİ -----" satırlarını eklediğinizden emin olun.

3: Aşağıdaki komutla bir java keystore dosyası, cacerts.jks oluşturun:

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4: Ortaya çıkan cacerts.jks anahtar deposunu java / (maven) uygulamanızın kaynaklar dizinine kopyalayın.

5: Bu dosyayı yüklemek ve Apache 4.5 HttpClient'e eklemek için aşağıdaki kodu kullanın. Bu, indetrust.comutil oracle tarafından verilen sertifikalara sahip olan tüm etki alanı için sorunu JRE varsayılan anahtar deposuna ekler.

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

Proje oluşturulduğunda, cacerts.jks sınıf yoluna kopyalanacak ve oradan yüklenecektir. Bu noktada, diğer ssl sitelerine karşı test etmedim, ancak yukarıdaki kod bu sertifikada "zincirler" ise o zaman onlar da çalışacak, ama yine bilmiyorum.

Başvuru: Özel SSL içeriği ve Java HttpsURLConnection ile kendinden imzalı bir sertifikayı nasıl kabul edebilirim?


Soru, kendinden imzalı bir sertifika ile ilgilidir. Bu cevap değil.
Lorne Marquis

4
Soruyu biraz daha okuyun (ve cevaplayın).
K.Nicholas

9

Apache HttpClient 4.5 kendinden imzalı sertifikaları kabul etmeyi destekler:

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

Bu, kullanılacak bir SSL soket fabrikası oluşturur, TrustSelfSignedStrategyözel bir bağlantı yöneticisine kaydeder ve daha sonra bu bağlantı yöneticisini kullanarak bir HTTP GET yapar.

"Bunu üretimde yapma" diyenlere katılıyorum, ancak üretim dışında kendinden imzalı sertifikaları kabul etmek için kullanım örnekleri var; bunları otomatik entegrasyon testlerinde kullanıyoruz, böylece üretim donanımında çalışmazken bile SSL (üretimde olduğu gibi) kullanıyoruz.


6

Varsayılan soket fabrikasını (IMO kötü bir şeydir) ayarlamak yerine, açmaya çalıştığınız her SSL bağlantısı yerine mevcut bağlantıyı etkiler:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

2
Sizin checkServerTrustedaslında sertifikaya güvenmek için gerekli mantığı uygulamıyor ve güvenilir olmayan sertifikaları reddedilir olun. Bu iyi bir okuma yapabilir: Dünyanın en tehlikeli kodu: tarayıcı olmayan yazılımlarda SSL sertifikalarını doğrulama .
jww

1
Kişisel getAcceptedIssuers()yöntem teknik özellikler uymuyor ve bu 'çözüm' radikal güvensiz kalır.
Lorne Marquis

4

Tüm sertifikalara güvenmenin daha iyi bir alternatifi vardır: TrustStoreBelirli bir sertifikaya özel olarak güvenen bir sertifika oluşturun ve bunu ayarlamak için bir sertifika oluşturmak SSLContextiçin SSLSocketFactorykullanın HttpsURLConnection. İşte tam kod:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

Alternatif olarak KeyStoredoğrudan bir dosyadan yükleyebilir veya X.509 Sertifikasını herhangi bir güvenilir kaynaktan alabilirsiniz.

Bu kodla, içindeki sertifikaların cacertskullanılmayacağını unutmayın. Bu özel HttpsURLConnectionyalnızca bu özel sertifikaya güvenir.


1

Tüm SSL sertifikalarına güven: - Test sunucusunda test etmek istiyorsanız SSL'yi atlayabilirsiniz. Ancak bu kodu üretim için kullanmayın.

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

Lütfen bu işlevi Etkinlik'teki veya Uygulama Sınıfınızdaki onCreate () işlevinde çağırın.

NukeSSLCerts.nuke();

Bu Android'de Volley için kullanılabilir.


Kod çalışmadığı sürece neden oylandığınızdan tam olarak emin değilim. Belki de orijinal soru Android'i etiketlemediğinde Android'in bir şekilde dahil olduğu varsayımıdır.
Lo-Tan

1

Kabul edilen cevap gayet iyi, ancak Mac'te IntelliJ kullandığım ve JAVA_HOMEpath değişkenini kullanarak çalışamadığım için buna bir şey eklemek istiyorum .

IntelliJ'den uygulamayı çalıştırırken Java Home'un farklı olduğu ortaya çıkıyor.

Tam olarak nerede olduğunu bulmak için, System.getProperty("java.home")güvenilir sertifikaların okunduğu yer olduğu gibi bunu yapabilirsiniz .


0

'Onlar' kendinden imzalı bir sertifika kullanıyorsa, sunucularını kullanılabilir hale getirmek için gerekli adımları atmak onlara kalmıştır. Özellikle bu, sertifikalarını çevrimdışı olarak güvenilir bir şekilde sağlamak anlamına gelir. Bu yüzden onları yapmalarını sağlayın. Daha sonra JSSE Başvuru Kılavuzu'nda açıklandığı şekilde tuş takımını kullanarak güven mağazanıza aktarırsınız. TrustManager'ın burada yayınladığı güvensizliği bile düşünmeyin.

EDIT Burada yazdıklarımı açıkça okumamış olan on yedi (!) Downvoter ve aşağıdaki çok sayıda yorumcunun yararı için , bu kendinden imzalı sertifikalara karşı bir jeremiad değildir . Doğru şekilde uygulandığında kendinden imzalı sertifikalarda yanlış bir şey yoktur . Ancak, bunları uygulamanın doğru yolu , kimlik doğrulaması için kullanılacak kimliği doğrulanmamış kanal yerine sertifikanın çevrimdışı bir işlemle güvenli bir şekilde teslim edilmesidir. Elbette bu açık mı? Binlerce şubesi olan bankalardan kendi şirketlerime kadar çalıştığım güvenlikle ilgili her organizasyon için kesinlikle açıktır. İstemci tarafı kod tabanı güvenmenin 'çözümü' TümünüKesinlikle herkes tarafından imzalanan kendinden imzalı sertifikalar veya kendisini CA olarak ayarlayan herhangi bir arbitary body dahil olmak üzere sertifikalar ipso facto güvenli değildir. Sadece güvenlikle oynuyor. Anlamsız. Birisi ile özel, kurcalamaya dayanıklı, cevap geçirmez, enjeksiyon geçirmez bir görüşme yapıyorsunuz. Herkes. Ortada bir adam. Bir taklitçi. Herkes. Sadece düz metin de kullanabilirsiniz.


2
Bazı sunucuların https kullanmaya karar vermesi , istemciye sahip kişinin kendi amaçları için güvenlik konusunda bir saçmalık sağladığı anlamına gelmez .
Gus

3
Bu cevabın çok aşağı düşmesine şaşırdım. Neden biraz daha anlamak isterim. Görünüşe göre EJP, büyük bir güvenlik açığına izin verdiği için 2. seçeneği yapmamanızı öneriyor. Birisi bu konuşmanın neden düşük kaliteli olduğunu (dağıtım öncesi ayarlar dışında) açıklayabilir mi?
cr1pto

2
Sanırım hepsini. Ne yapar "o onlara kalmış adımlar onların sunucu kullanılabilir hale getirmek için gerekli çekmek için" ortalama? Bir sunucu operatörünün kullanılabilir olması için ne yapması gerekir? Aklınızda olan adımlar nelerdir? Bunların bir listesini verebilir misiniz? Ama 1000 feet'e geri dönmek, OP'nin sorduğu problemle nasıl bir ilişki kuruyor? İstemcinin Java'da kendinden imzalı sertifikayı kabul etmesini nasıl sağlayacağını bilmek istiyor. Bu, OP sertifikayı kabul edilebilir bulduğu için koda güvenmek için basit bir konudur.
jww

2
@EJP - Yanlışsam beni düzeltin, ancak kendinden imzalı bir sertifikayı kabul etmek müşteri tarafında bir politika kararıdır. Sunucu tarafı işlemleri ile ilgisi yoktur. Müşteri kararı vermelidir. Pariteler, anahtar dağıtım sorununu çözmek için birlikte çalışmalıdır, ancak bunun sunucuyu kullanılabilir hale getirmeyle ilgisi yoktur.
jww

3
@EJP - Kesinlikle "tüm sertifikalara güven" demedim . Belki bir şeyleri kaçırıyorum (ya da şeylere çok fazla okuyorsun) ... OP'nin sertifikası var ve onun için kabul edilebilir. Ona nasıl güveneceğini bilmek istiyor. Muhtemelen tüyleri ayırıyorum, ancak sertifikanın çevrimdışı teslim edilmesi gerekmez. Bant dışı doğrulama kullanılmalıdır, ancak bu "istemciye çevrimdışı olarak teslim edilmelidir" ile aynı değildir . Sunucuyu ve istemciyi üreten dükkan bile olabilir, bu yüzden gerekli a priori bilgisine sahiptir.
jww


0

Üst yorumda önerildiği gibi keytool kullanmak yerine, RHEL'de RHEL 6'nın yeni sürümlerinden başlayarak update-ca-trust kullanabilirsiniz. Sonra

trust anchor <cert.pem>

/Etc/pki/ca-trust/source/cert.p11-kit dosyasını düzenleyin ve "sertifika kategorisi: diğer giriş" i "sertifika kategorisi: yetki" olarak değiştirin. (Veya bunu bir komut dosyasında yapmak için sed kullanın.) Sonra yapın

update-ca-trust

Birkaç uyarı:

  • RHEL 6 sunucumda "güven" bulamadım ve yum yüklemek için teklif vermedi. Bir RHEL 7 sunucusunda kullandım ve .p11-kit dosyasını kopyaladım.
  • Bunun sizin için çalışması için yapmanız gerekebilir update-ca-trust enable. Bu, / etc / pki / java / cacerts'ın yerini / etc / pki / ca-trust / extracted / java / cacerts'i gösteren sembolik bir bağlantıyla değiştirir. (Yani önce birincisini yedeklemek isteyebilirsiniz.)
  • Java istemciniz başka bir konumda depolanan cacerts kullanıyorsa, bunu / etc / pki / ca-trust / extracted / java / cacerts yerine bir sembolik işaretle manuel olarak değiştirmek veya bu dosyayla değiştirmek istersiniz.

0

url.openConnection();Jon-daniel'in cevabını adapte ettiğimi söyleyen bir kütüphaneye bir URL geçiriyordum,

public class TrustHostUrlStreamHandler extends URLStreamHandler {

    private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);

    @Override
    protected URLConnection openConnection(final URL url) throws IOException {

        final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();

        // adapated from
        // /programming/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
        if (urlConnection instanceof HttpsURLConnection) {
            final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;

            try {
                // Set up a Trust all manager
                final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }

                    @Override
                    public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }
                } };

                // Get a new SSL context
                final SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                // Set our connection to use this SSL context, with the "Trust all" manager in place.
                conHttps.setSSLSocketFactory(sc.getSocketFactory());
                // Also force it to trust all hosts
                final HostnameVerifier allHostsValid = new HostnameVerifier() {
                    @Override
                    public boolean verify(final String hostname, final SSLSession session) {
                        return true;
                    }
                };

                // and set the hostname verifier.
                conHttps.setHostnameVerifier(allHostsValid);

            } catch (final NoSuchAlgorithmException e) {
                LOG.warn("Failed to override URLConnection.", e);
            } catch (final KeyManagementException e) {
                LOG.warn("Failed to override URLConnection.", e);
            }

        } else {
            LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
        }

        return urlConnection;
    }

}

Bu sınıfı kullanarak aşağıdakilerle yeni bir URL oluşturmak mümkündür:

trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();

Bunun yerelleştirilmiş olması ve varsayılanın yerini almaması avantajı vardır URL.openConnection.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.