জাভা কী স্টোরে পিএম আমদানি করুন


144

আমি কোনও এসএসএল সার্ভারের সাথে সংযোগ দেওয়ার চেষ্টা করছি যার জন্য আমাকে নিজের প্রমাণীকরণ করা প্রয়োজন। অ্যাপাচি মিনার উপর এসএসএল ব্যবহার করার জন্য আমার একটি উপযুক্ত জেকেএস ফাইল দরকার। তবে আমাকে কেবল একটি .PEM ফাইল দেওয়া হয়েছে।

আমি কীভাবে একটি পিইএম ফাইল থেকে জেকেএস ফাইল তৈরি করতে যাব?


1
এই লিঙ্কটি সহায়ক হতে পারে: http://www.agentbob.info/agentbob/79-AB.html
লরেন্ট কে

উত্তর:


235

প্রথমে আপনার শংসাপত্রটি একটি ডিইআর ফর্ম্যাটে রূপান্তর করুন:

openssl x509 -outform der -in certificate.pem -out certificate.der

এবং পরে, কীস্টোরে এটি আমদানি করুন:

keytool -import -alias your-alias -keystore cacerts -file certificate.der

7
.Pem ফাইলটিতে একাধিক শংসাপত্র থাকলে কাজ করে না।
মারিওভিলাস

14
আমি একটি একক শংসাপত্র পেয়েছি। পেম এবং এটি কার্যকর হয় না। 1795: ত্রুটি: 0906D06C: PEM রুটিনগুলি: PEM_read_bio: কোনও প্রারম্ভিক লাইন: / usr / src / নিরাপদ / lib / libcrypto /../../../ ক্রিপ্টো / ওপেনসেল / ক্রিপ্টো / পেম / pem_lib.c: 648: প্রত্যাশা : বিশ্বাসযোগ্য শংসাপত্র
ব্রায়ান নোব্লাচ

4
আমি সমাধান খুঁজে পেয়েছি। রুট এবং মধ্যবর্তী শংসাপত্রগুলি .pem এ প্রি-পেন্ড করুন, তারপরে রূপান্তর করুন।
ব্রায়ান নোব্লাচ

1
@ অ্যান্থনি এই কমান্ডটি কেবল জেএসএসে কীভাবে একটি পিইএম আমদানি করবেন তা জানায়। দোকান থেকে জেকেএস রফতানি করার জন্য একটি কমান্ড যুক্ত করা ভাল ধারণা হতে পারে।
বিশাল বিয়ানি

2
.Pem এ আমার যদি একাধিক শংসাপত্র থাকে, তবে আমি কীভাবে জাভা কীস্টোরে আমদানি করব?
এরিক

55

আপনি যদি কেবল কী স্টোরে পিইএম ফর্ম্যাটে একটি শংসাপত্র আমদানি করতে চান তবে কীটোল কাজটি করবেন:

keytool -import -alias *alias* -keystore cacerts -file *cert.pem*

11
আমি যদি এভাবে চলে যাই তবে আমি একটি ত্রুটি পেয়েছি: কীটোল ত্রুটি:
জাভা.লং। অনুগ্রহ:

1
@ ফ্রেণ্ডেভেল, এই ত্রুটিটি পিইএম ইনপুট ফাইলের মাধ্যমে --- বীগইন ডিলিমিটারের উপরে বা একটি ফাইলে বা উভয়ই একাধিক পিইএম থাকার কারণে হতে পারে। হয় সমস্ত বহিরাগত ডেটা মুছে ফেলুন এবং একবারে প্রতিটি পিইএমগুলিতে ফিড দিন বা আমার উত্তরে বর্ণিত হিসাবে আমার সরঞ্জামটি ব্যবহার করুন।
অ্যালাস্টার ম্যাককর্মাক

ধন্যবাদ @ ফাজিফেল্ট, আমি একবার দেখে নেব
ফ্রেডভেল

1
একই সমস্যা এবং .PEM ফাইলটি সমস্ত উপযুক্ত শিরোনাম সহ পরিষ্কার।
ব্রায়ান নোব্লাচ

17

আমি http://code.google.com/p/java-keyutil/ তৈরি করেছি যা পিএএম শংসাপত্রগুলি সরাসরি জাভা কীস্টোরে আমদানি করে। এর প্রাথমিক উদ্দেশ্য হল সিএ-বান্ডেল.crt এর মতো একটি বহু অংশের PEM অপারেটিং সিস্টেম শংসাপত্র বান্ডিলগুলি আমদানি করা। এর মধ্যে প্রায়শই শিরোনাম অন্তর্ভুক্ত থাকে যা কীটল হ্যান্ডেল করতে পারে না

</self promotion>

4
বাজে খেলনা প্রকল্প নয়, তবে keytoolইতিমধ্যে এটি আপনার (এবং আরও অনেক কিছু) এর জন্য করে। (যাইহোক , যদি কোনও ব্যতিক্রম ঘটে তবে FileOutputStreamআপনার বন্ধ করে দেওয়া উচিত এবং আপনার আই / ও স্ট্রিমগুলি বন্ধ করা উচিত finally
ব্রুনো

8
হাই ব্রুনো, টিপসের জন্য ধন্যবাদ। আসল ব্যবহারের ক্ষেত্রে /etc/pki/tls/certs/ca-bundle.crt (আরএইচইএল / সেন্টোস) এর সমস্ত এন্ট্রি একসাথে আমদানি করা। আফাইক, কীটোল কেবল প্রথম প্রবেশটি আমদানি করবে। আমি বেশিরভাগ লোককে এটি ভিন্নভাবে করতে দেখেছি তবে এটিতে প্রতিটি সার্টের জন্য একাধিকবার মূলসূত্র চাওয়া জড়িত। উবুন্টুর একটি আপডেট স্ক্রিপ্ট রয়েছে যা উবুন্টু একটি ডিরেক্টরিতে তার শংসাপত্রগুলি সংরক্ষণ করে exactly আমি অদূর ভবিষ্যতে ডিরেক্টরিগুলির জন্য সমর্থন যুক্ত করব। কোডটি পর্যালোচনা করার জন্য আবার ধন্যবাদ
অ্যালাস্টার ম্যাককর্ম্যাক

14

আমার ক্ষেত্রে আমার কাছে একটি পেম ফাইল ছিল যা পারস্পরিক এসএসএল প্রমাণীকরণে ব্যবহার করার জন্য দুটি শংসাপত্র এবং একটি এনক্রিপ্ট করা ব্যক্তিগত কী রয়েছে। সুতরাং আমার পেম ফাইলটি দেখতে এমন দেখাচ্ছে:

-----BEGIN CERTIFICATE-----

...

-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----

Proc-Type: 4,ENCRYPTED

DEK-Info: DES-EDE3-CBC,C8BF220FC76AA5F9

...

-----END RSA PRIVATE KEY-----

-----BEGIN CERTIFICATE-----

...

-----END CERTIFICATE-----

এখানে আমি কি করেছি

ফাইলটি তিনটি পৃথক পৃথক ফাইলে বিভক্ত করুন, যাতে প্রত্যেকে একটি করে প্রবেশ করে, লাইন ---BEGIN..দিয়ে শুরু এবং শেষ হয় ---END..। অনুমান করতে দেয় আমরা এখন তিনটি ফাইল আছে: cert1.pem, cert2.pem, এবংpkey.pem

pkey.pemওপেনসেল এবং নিম্নলিখিত সিনট্যাক্স ব্যবহার করে DER ফর্ম্যাটে রূপান্তর করুন :

openssl pkcs8 -topk8 -Nocrypt-in pkey.pem -infor PEM -out pkey.der -outfor DER

দ্রষ্টব্য, যদি ব্যক্তিগত কীটি এনক্রিপ্ট করা থাকে তবে ডিইআর ফর্ম্যাটে রূপান্তর করতে আপনাকে একটি পাসওয়ার্ড সরবরাহ করতে হবে (মূল পেম ফাইলের সরবরাহকারী থেকে এটি গ্রহণ করুন), opensslআপনাকে পাসওয়ার্ডের জন্য অনুরোধ করবে: "এর জন্য একটি পাসফ্রেজ লিখুনpkey.pem :"।

যদি রূপান্তর সফল হয়, আপনি কল করা একটি নতুন ফাইল পাবেন pkey.der

একটি নতুন জাভা কীস্টোর তৈরি করুন এবং ব্যক্তিগত কী এবং শংসাপত্রগুলি আমদানি করুন:

String keypass = "password";  // this is a new password, you need to come up with to protect your java key store file
String defaultalias = "importkey";
KeyStore ks = KeyStore.getInstance("JKS", "SUN");

// this section does not make much sense to me, 
// but I will leave it intact as this is how it was in the original example I found on internet:   
ks.load( null, keypass.toCharArray());
ks.store( new FileOutputStream ( "mykeystore"  ), keypass.toCharArray());
ks.load( new FileInputStream ( "mykeystore" ),    keypass.toCharArray());
// end of section..


// read the key file from disk and create a PrivateKey

FileInputStream fis = new FileInputStream("pkey.der");
DataInputStream dis = new DataInputStream(fis);
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

byte[] key = new byte[bais.available()];
KeyFactory kf = KeyFactory.getInstance("RSA");
bais.read(key, 0, bais.available());
bais.close();

PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec ( key );
PrivateKey ff = kf.generatePrivate (keysp);


// read the certificates from the files and load them into the key store:

Collection  col_crt1 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert1.pem"));
Collection  col_crt2 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert2.pem"));

Certificate crt1 = (Certificate) col_crt1.iterator().next();
Certificate crt2 = (Certificate) col_crt2.iterator().next();
Certificate[] chain = new Certificate[] { crt1, crt2 };

String alias1 = ((X509Certificate) crt1).getSubjectX500Principal().getName();
String alias2 = ((X509Certificate) crt2).getSubjectX500Principal().getName();

ks.setCertificateEntry(alias1, crt1);
ks.setCertificateEntry(alias2, crt2);

// store the private key
ks.setKeyEntry(defaultalias, ff, keypass.toCharArray(), chain );

// save the key store to a file         
ks.store(new FileOutputStream ( "mykeystore" ),keypass.toCharArray());

(alচ্ছিক) আপনার নতুন কী স্টোরের সামগ্রীটি যাচাই করুন:

$ keytool -list -keystore mykeystore -storepass password

কীস্টোর প্রকার: জে কেএস কীস্টোর সরবরাহকারী: সান

আপনার কীস্টোরটিতে 3 টি প্রবেশ রয়েছে:

  • সিএন = ..., আউ = ..., ও = .., ২ সেপ্টেম্বর, 2014, বিশ্বস্ত ক্যারেন্টেট্রি, শংসাপত্রের ফিঙ্গারপ্রিন্ট (এসএএএ 1): 2 সি: বি 8: ...

  • আমদানি, সেপ্টেম্বর 2, 2014, প্রাইভেটকিএন্ট্রি, শংসাপত্রের ফিঙ্গারপ্রিন্ট (SHA1): 9 সি: বি 0: ...

  • সিএন = ..., ও = ...., সেপ্টেম্বর 2, 2014, বিশ্বস্তকার্টেনট্রি, শংসাপত্রের ফিঙ্গারপ্রিন্ট (SHA1): 83:63: ...

(alচ্ছিক) আপনার এসএসএল সার্ভারের বিপরীতে আপনার নতুন কী স্টোর থেকে আপনার শংসাপত্র এবং ব্যক্তিগত কী পরীক্ষা করুন: (আপনি ভিএম বিকল্প হিসাবে ডিবাগিং সক্ষম করতে চাইতে পারেন: -জাজাক্সটনে.দেবগ = সব)

        char[] passw = "password".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS", "SUN");
        ks.load(new FileInputStream ( "mykeystore" ), passw );

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, passw);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        TrustManager[] tm = tmf.getTrustManagers();

        SSLContext sclx = SSLContext.getInstance("TLS");
        sclx.init( kmf.getKeyManagers(), tm, null);

        SSLSocketFactory factory = sclx.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket( "192.168.1.111", 443 );
        socket.startHandshake();

        //if no exceptions are thrown in the startHandshake method, then everything is fine..

অবশেষে আপনার শংসাপত্রগুলি HTTPURL সংযোগের সাথে নিবন্ধন করুন যদি এটি ব্যবহার করার পরিকল্পনা করা হয়:

        char[] passw = "password".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS", "SUN");
        ks.load(new FileInputStream ( "mykeystore" ), passw );

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, passw);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        TrustManager[] tm = tmf.getTrustManagers();

        SSLContext sclx = SSLContext.getInstance("TLS");
        sclx.init( kmf.getKeyManagers(), tm, null);

        HostnameVerifier hv = new HostnameVerifier()
        {
            public boolean verify(String urlHostName, SSLSession session)
            {
                if (!urlHostName.equalsIgnoreCase(session.getPeerHost()))
                {
                    System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
                }
                return true;
            }
        };

        HttpsURLConnection.setDefaultSSLSocketFactory( sclx.getSocketFactory() );
        HttpsURLConnection.setDefaultHostnameVerifier(hv);

আপনার হোস্টনাম ভেরিফায়ারটি ভুল, session.getPeerHost()শংসাপত্রে নামটি ফিরিয়ে দেয় না, তবে যে নামটির সাথে আপনি সংযুক্ত ছিলেন (যেমন urlHostNameএখানে), তাই এটি সর্বদা সত্য হতে চলেছে। আপনি সর্বদা trueযেভাবেই ফিরে আসছেন ।
ব্রুনো

9

আপনার যদি বাহ্যিক সরঞ্জামগুলি (ওপেনসেল, কীটল) মোকাবেলা না করে জাভাতে পিইএম ফাইলগুলি লোড করার কোনও সহজ উপায়ের প্রয়োজন হয় তবে আমি আমার কোডটি এখানে প্রযোজনায় ব্যবহার করছি:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.xml.bind.DatatypeConverter;

public class PEMImporter {

    public static SSLServerSocketFactory createSSLFactory(File privateKeyPem, File certificatePem, String password) throws Exception {
        final SSLContext context = SSLContext.getInstance("TLS");
        final KeyStore keystore = createKeyStore(privateKeyPem, certificatePem, password);
        final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keystore, password.toCharArray());
        final KeyManager[] km = kmf.getKeyManagers();
        context.init(km, null, null);
        return context.getServerSocketFactory();
    }

    /**
     * Create a KeyStore from standard PEM files
     * 
     * @param privateKeyPem the private key PEM file
     * @param certificatePem the certificate(s) PEM file
     * @param the password to set to protect the private key
     */
    public static KeyStore createKeyStore(File privateKeyPem, File certificatePem, final String password)
            throws Exception, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        final X509Certificate[] cert = createCertificates(certificatePem);
        final KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(null);
        // Import private key
        final PrivateKey key = createPrivateKey(privateKeyPem);
        keystore.setKeyEntry(privateKeyPem.getName(), key, password.toCharArray(), cert);
        return keystore;
    }

    private static PrivateKey createPrivateKey(File privateKeyPem) throws Exception {
        final BufferedReader r = new BufferedReader(new FileReader(privateKeyPem));
        String s = r.readLine();
        if (s == null || !s.contains("BEGIN PRIVATE KEY")) {
            r.close();
            throw new IllegalArgumentException("No PRIVATE KEY found");
        }
        final StringBuilder b = new StringBuilder();
        s = "";
        while (s != null) {
            if (s.contains("END PRIVATE KEY")) {
                break;
            }
            b.append(s);
            s = r.readLine();
        }
        r.close();
        final String hexString = b.toString();
        final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
        return generatePrivateKeyFromDER(bytes);
    }

    private static X509Certificate[] createCertificates(File certificatePem) throws Exception {
        final List<X509Certificate> result = new ArrayList<X509Certificate>();
        final BufferedReader r = new BufferedReader(new FileReader(certificatePem));
        String s = r.readLine();
        if (s == null || !s.contains("BEGIN CERTIFICATE")) {
            r.close();
            throw new IllegalArgumentException("No CERTIFICATE found");
        }
        StringBuilder b = new StringBuilder();
        while (s != null) {
            if (s.contains("END CERTIFICATE")) {
                String hexString = b.toString();
                final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
                X509Certificate cert = generateCertificateFromDER(bytes);
                result.add(cert);
                b = new StringBuilder();
            } else {
                if (!s.startsWith("----")) {
                    b.append(s);
                }
            }
            s = r.readLine();
        }
        r.close();

        return result.toArray(new X509Certificate[result.size()]);
    }

    private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
        final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory factory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) factory.generatePrivate(spec);
    }

    private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
        final CertificateFactory factory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
    }

}

আনন্দ কর.


প্রশ্নটি ছিল "এসএমএস ওভার অ্যাপাচি এমআইএনএ" সম্পর্কে যা সরবরাহিত "পিইএমএস ফাংশন থেকে এসএসএল সার্ভারসকেটফ্যাক্টরি" দিয়ে কনফিগার করা সহজ, মিনা.পাচি.আর.আমিনা -প্রজেক্ট / ইউজারগাইড/ch11-ssl- filter/… দেখুন
ব্লুওএস

8

আমি কীভাবে এটি করব তা সবসময় ভুলে যাচ্ছি কারণ এটি এমন কিছু যা আমি একবারে একবারে করি, এটি একটি সম্ভাব্য সমাধান এবং এটি কেবল কার্যকর:

  1. আপনার প্রিয় ব্রাউজারে যান এবং সুরক্ষিত ওয়েবসাইট থেকে মূল শংসাপত্রটি ডাউনলোড করুন।
  2. নিম্নলিখিত দুটি কোডের লাইন কার্যকর করুন:

    $ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
    $ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
  3. জাভা এসই পরিবেশে নির্বাহ করা হলে নিম্নলিখিত বিকল্পগুলি যুক্ত করুন:

    $ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
  4. বা জাভা কোডে নিম্নলিখিতগুলি যুক্ত করুন:

    System.setProperty("javax.net.ssl.trustStore", "GlobalSignRootCA.jks");
    System.setProperty("javax.net.ssl.trustStorePassword","trustStorePassword");

পদক্ষেপ 2 এর জন্য অন্য বিকল্পটি কেবল keytoolকমান্ডটি ব্যবহার করা । বেলো হ'ল শংসাপত্রের একটি শৃঙ্খলাযুক্ত উদাহরণ:

$ keytool -import -file org.eu.crt -alias orgcrt -keystore globalsignrs.jks
$ keytool -import -file GlobalSignOrganizationValidationCA-SHA256-G2.crt -alias globalsignorgvalca -keystore globalsignrs.jks
$ keytool -import -file GlobalSignRootCA.crt -alias globalsignrootca -keystore globalsignrs.jks

6

আমি কীস্টোর এক্সপ্লোরার ব্যবহার করেছি

  1. একটি ব্যক্তিগত কী দিয়ে জেকেএস খুলুন
  2. সিএ থেকে স্বাক্ষরিত পিইএম পরীক্ষা করুন
  3. কী আমদানি করুন
  4. জে কে এস সংরক্ষণ করুন

3
কীস্টোর এক্সপ্লোরার দুর্দান্ত এবং খুব বহুমুখী। টার্মিনালে কয়েকটা নির্বোধ মিনিট ব্যয় করা থেকে এক সময় বাঁচায়।
TheRealChx101

3

এছাড়াও একটি জিইউআই সরঞ্জাম রয়েছে যা ভিজ্যুয়াল জেকেএস তৈরি এবং শংসাপত্রগুলি আমদানির অনুমতি দেয়।

http://portecle.sourceforge.net/

পোর্টটেকাল হ'ল কীস্টোর, কী, শংসাপত্র, শংসাপত্রের অনুরোধ, শংসাপত্র প্রত্যাহার তালিকা এবং আরও অনেক কিছুর তৈরি, পরিচালনা ও পরীক্ষা করার জন্য একটি ব্যবহারকারী বান্ধব জিইউআই অ্যাপ্লিকেশন।


1
কী স্টোর এক্সপ্লোরারটি কর্টেলের আধুনিক সংস্করণ। তাদের মেনু এবং কার্যকারিতার মধ্যে কোনও পার্থক্য নেই।
সেটাম্যাক্স

0

আমি এটি ইন্টারনেট থেকে পেয়েছি। এটি একাধিক এন্ট্রিযুক্ত পেম ফাইলগুলির জন্য বেশ ভাল কাজ করে।

#!/bin/bash
pemToJks()
{
        # number of certs in the PEM file
        pemCerts=$1
        certPass=$2
        newCert=$(basename "$pemCerts")
        newCert="${newCert%%.*}"
        newCert="${newCert}"".JKS"
        ##echo $newCert $pemCerts $certPass
        CERTS=$(grep 'END CERTIFICATE' $pemCerts| wc -l)
        echo $CERTS
        # For every cert in the PEM file, extract it and import into the JKS keystore
        # awk command: step 1, if line is in the desired cert, print the line
        #              step 2, increment counter when last line of cert is found
        for N in $(seq 0 $(($CERTS - 1))); do
          ALIAS="${pemCerts%.*}-$N"
          cat $pemCerts |
                awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
                $KEYTOOLCMD -noprompt -import -trustcacerts \
                                -alias $ALIAS -keystore $newCert -storepass $certPass
        done
}
pemToJks <pem to import> <pass for new jks>
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.