Java 클라이언트에서 서버의 자체 서명 된 SSL 인증서 수락


215

일반적인 질문처럼 보이지만 어디에서나 명확한 방향을 찾을 수 없었습니다.

자체 서명 된 (또는 만료 된) 인증서로 서버에 연결하려고하는 Java 코드가 있습니다. 코드는 다음 오류를보고합니다.

[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

내가 이해하기 때문에 keytool 을 사용해야 하고이 연결을 허용해도 괜찮다고 Java에 알려야합니다.

이 문제를 해결하기위한 모든 지침은 다음과 같은 keytool에 능숙하다고 가정합니다.

서버의 개인 키를 생성하여 키 저장소로 가져 오기

자세한 지침을 게시 할 수있는 사람이 있습니까?

유닉스를 실행 중이므로 bash 스크립트가 가장 좋습니다.

중요한지 확실하지 않지만 코드는 jboss에서 실행됩니다.


2
Java HttpsURLConnection으로 자체 서명 된 인증서를 승인하려면 어떻게합니까?를 참조하십시오 . . 사이트에서 유효한 인증서를 사용할 수있게하는 것이 좋습니다.
Matthew Flaschen

2
링크 주셔서 감사합니다, 나는 검색하는 동안 그것을 보지 못했습니다. 그러나 두 솔루션에는 요청을 보내는 특수 코드가 포함되어 있으며 기존 코드 (Java의 경우 amazon ws 클라이언트)를 사용하고 있습니다. 각각, 내가 연결하는 사이트이고 인증서 문제를 해결할 수 없습니다.
Nikita Rybak

2
@MatthewFlaschen- " 유효하게도 , 사이트에서 유효한 인증서를 사용할 수 있다면 더 좋을 것입니다 ..." -클라이언트가 신뢰하는 경우 자체 서명 된 인증서는 유효한 인증서입니다. 많은 사람들이 CA / 브라우저 카르텔에 신뢰를 부여하는 것이 보안 결함이라고 생각합니다.
jww

3
관련, 세계에서 가장 위험한 코드 : 브라우저 이외의 소프트웨어에서 SSL 인증서 유효성 검사를 참조하십시오 . (링크는 유효성 검사를 비활성화하는 스팸성 답변을 얻는 것처럼 보이기 때문에 제공됩니다).
jww

답변:


306

기본적으로 두 가지 옵션이 있습니다. 자체 서명 된 인증서를 JVM 신뢰 저장소에 추가하거나 클라이언트가

옵션 1

브라우저에서 인증서를 내보내고 JVM 신뢰 저장소로 가져 와서 신뢰 체인을 설정하십시오.

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

옵션 2

인증서 유효성 검사 비활성화 :

// 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) {
} 

참고 가 전혀 옵션의 # 2를하지 않는 것이 좋습니다 . 트러스트 관리자를 비활성화하면 SSL의 일부가 무효화되고 중간 공격에서 사람에게 취약합니다. 옵션 # 1을 선호하거나 서버가 잘 알려진 CA에서 서명 한 "실제"인증서를 사용하도록하는 것이 좋습니다.


7
MIM 공격뿐만 아니라 잘못된 사이트에 연결하는 데 취약합니다. 완전히 안전하지 않습니다. RFC 2246을 참조하십시오.이 TrustManager를 항상 게시하는 것에 반대합니다. 자체 사양으로는 정확하지 않습니다.
Lorne의 후작 8:24에

10
@ EJP 나는 두 번째 옵션을 권장하지 않습니다 (나는 그것을 명확하게하기 위해 대답을 업데이트했습니다). 그러나 게시하지 않으면 아무것도 해결되지 않으며 (이것은 공개 정보 임) IMHO에게는 공감대가 필요하지 않습니다.
Pascal Thivent

135
@EJP 사물을 숨기지 말고 교육하는 사람들을 가르치는 것입니다. 따라서 비밀을 유지하거나 모호한 상태를 유지하는 것은 전혀 해결책이 아닙니다. 이 코드는 공개적이며 Java API는 공개적이므로 무시하는 것보다 이야기하는 것이 좋습니다. 그러나 나는 동의하지 않고 당신과 함께 살 수 있습니다.
Pascal Thivent

10
언급하지 않은 다른 옵션 은 서버 인증서를 직접 수정 하거나 관련 지원 담당자 에게 문의하여 서버 인증서 를 수정하는 것입니다. 단일 호스트 인증서는 실제로 매우 저렴합니다. 자체 서명 된 물건으로 장난 치는 것은 페니 현명한 어리석은 일입니다 (즉, 영어 관용구에 익숙하지 않은 사람들에게는 거의 아무것도 절약하기 위해 많은 비용이 드는 멍청한 우선 순위 세트).
Donal Fellows

2
@Rich, 6 년 늦었지만 잠금 아이콘-> 추가 정보-> 인증서보기-> 세부 정보 탭-> 내보내기 ...를 클릭하여 firefox에서 서버 인증서를 보유 할 수도 있습니다. 숨겨져 있습니다.
Siddhartha

9

이 문제는 기본 JVM 신뢰할 수있는 호스트의 일부가 아닌 인증서 공급자에게 쫓겨났습니다 JDK 8u74. 공급자는 www.identrust.com 이지만 연결하려는 도메인이 아닙니다. 해당 도메인은이 공급자로부터 인증서를 받았습니다. JDK / JRE의 기본 목록으로 교차 루트 커버 신뢰를 참조 합니까?를 참조하십시오. -몇 가지 항목을 읽습니다. Let 's Encrypt를 지원하는 브라우저 및 운영 체제 도 참조하십시오 .

그래서 내가 관심있는 도메인에 연결하기 위해 인증서를 발급받은 identrust.com다음 단계를 수행했습니다. 기본적으로 DST Root CA X3JVM 에서 identrust.com ( ) 인증서를 신뢰해야했습니다. Apache HttpComponents 4.5를 사용하여 그렇게 할 수있었습니다.

1 : Certificate Chain Download Instructions (인증서 체인 다운로드 지침) 에서 불 신탁으로부터 인증서를 얻습니다 . 온 클릭 DST 루트 CA X3의 링크.

2 : 문자열을 "DST Root CA X3.pem"파일에 저장하십시오. 시작과 끝에 파일에 "----- BEGIN CERTIFICATE -----"및 "----- END CERTIFICATE -----"행을 추가해야합니다.

3 : 다음 명령으로 java 키 저장소 파일 cacerts.jks를 작성하십시오.

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

4 : 결과 cacerts.jks 키 저장소를 java / (maven) 애플리케이션의 resources 디렉토리에 복사하십시오.

5 : 다음 코드를 사용하여이 파일을로드하고 Apache 4.5 HttpClient에 첨부하십시오. 이렇게하면 indetrust.comutil oracle 에서 발급 한 인증서가있는 모든 도메인의 문제 가 JRE 기본 키 저장소에 인증서를 포함합니다.

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();

프로젝트가 빌드되면 cacerts.jks가 클래스 경로에 복사되어로드됩니다. 이 시점에서 다른 ssl 사이트에 대해 테스트하지는 않았지만이 인증서의 위의 코드 "체인"도 작동하지만 다시는 모릅니다.

참조 : 사용자 정의 SSL 컨텍스트Java HttpsURLConnection으로 자체 서명 된 인증서를 수락하는 방법은 무엇입니까?


문제는 자체 서명 된 인증서에 관한 것입니다. 이 답변은 그렇지 않습니다.
Lorne의 후작

4
질문을 조금 더 자세히 읽으십시오.
K.Nicholas 2016 년

9

Apache HttpClient 4.5는 자체 서명 된 인증서 수락을 지원합니다.

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);

이것은 SSL 소켓 팩토리를 빌드하여 SSL 소켓 팩토리를 사용하고 TrustSelfSignedStrategy이를 사용자 정의 연결 관리자에 등록한 다음 해당 연결 관리자를 사용하여 HTTP GET을 수행합니다.

"제작에서이 작업을 수행하지 마십시오"라는 노래에 동의하지만 프로덕션 외부에서 자체 서명 된 인증서를 수락하는 사용 사례가 있습니다. 자동화 된 통합 테스트에 사용하므로 프로덕션 하드웨어에서 실행되지 않는 경우에도 프로덕션과 같은 SSL을 사용합니다.


6

기본 소켓 팩토리를 설정하는 대신 (IMO가 나쁜 것입니다)-열려는 모든 SSL 연결이 아닌 현재 연결에만 영향을 미칩니다.

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
당신은 checkServerTrusted실제로 인증서를 신뢰하는 데 필요한 논리를 구현하지 않습니다 신뢰할 수없는 인증서가 거부되어 있는지 확인합니다. : 이것은 좋은 독서 할 수있는 세계에서 가장 위험한 코드 : 브라우저가 아닌 소프트웨어에서 SSL 인증서 유효성 검사를 .
jww

1
귀하의 getAcceptedIssuers()방법은 사양을 따르지 않으며,이 '솔루션'은 근본적으로 안전하지 않습니다.
Lorne의 후작

4

모든 인증서를 TrustStore신뢰할 수 있는 더 나은 대안이 있습니다. 지정된 인증서를 구체적으로 신뢰하는 인증서를 작성하고이 인증서를 사용 SSLContext하여 SSLSocketFactory에서 설정할 인증서를 작성 하십시오 HttpsURLConnection. 완전한 코드는 다음과 같습니다.

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());

KeyStore또는 파일 에서 직접 파일을 로드 하거나 신뢰할 수있는 소스에서 X.509 인증서를 검색 할 수 있습니다.

이 코드를 사용하면 인증서 cacerts가 사용되지 않습니다. 이 HttpsURLConnection인증서는이 특정 인증서 만 신뢰합니다.


1

모든 SSL 인증서 신뢰 :-테스트 서버에서 테스트하려는 경우 SSL을 무시할 수 있습니다. 그러나 프로덕션에이 코드를 사용하지 마십시오.

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) { 
    }
}

}

Activity 또는 Application 클래스의 onCreate () 함수에서이 함수를 호출하십시오.

NukeSSLCerts.nuke();

Android의 Volley에 사용할 수 있습니다.


코드가 작동하지 않는 한 왜 투표를 중단했는지 이유를 완전히 알지 못합니다. 아마도 원래 질문이 Android에 태그를 지정하지 않았을 때 Android가 어떻게 든 관련되어 있다고 가정합니다.
Lo-Tan

1

허용되는 답변은 괜찮지 만 Mac에서 IntelliJ를 사용하고 있고 JAVA_HOMEpath 변수를 사용하여 작동하지 못했기 때문에 여기에 무언가를 추가하고 싶습니다 .

IntelliJ에서 응용 프로그램을 실행할 때 Java Home이 다릅니다.

정확한 위치를 파악하려면 System.getProperty("java.home")신뢰할 수있는 인증서를 읽는 위치에서 수행하면됩니다.


0

'그들'이 자체 서명 된 인증서를 사용하는 경우 서버를 사용 가능하게하는 데 필요한 단계를 수행해야합니다. 특히 이는 신뢰할 수있는 방식으로 인증서를 오프라인으로 제공하는 것을 의미합니다. 그렇게하도록하세요. 그런 다음 JSSE 참조 안내서에 설명 된대로 keytool을 사용하여이를 신뢰 저장소로 가져 오십시오. 여기에 게시 된 안전하지 않은 TrustManager에 대해서는 생각조차하지 마십시오.

편집 내가 여기에 쓴 내용을 실제로 읽지 않은 17 명의 downvoters 및 아래의 수많은 의견 제시 자 들의 이익을 위해 , 이것은 자체 서명 된 인증서에 대한 저변 이 아닙니다 . 올바르게 구현 된 자체 서명 인증서에는 아무런 문제가 없습니다 . 그러나 이를 구현하는 올바른 방법 은 인증에 사용될 인증되지 않은 채널을 통하지 않고 오프라인 프로세스를 통해 인증서를 안전하게 전달 하는 것입니다. 분명히 이것이 분명합니까? 수천 개의 지점을 가진 은행에서부터 내 회사에 이르기까지 내가 일해온 모든 보안 인식 조직에게는 분명합니다. 모든 것을 신뢰하는 클라이언트 측 코드베이스 '솔루션'절대적으로 다른 사람이 서명 한 자체 서명 된 인증서 또는 CA로 설정된 임의의 기관을 포함한 인증서는 사실상 안전하지 않습니다. 방금 보안 중입니다. 무의미합니다. 당신은 ... 누군가와 개인, 변조 방지, 회신 방지, 주사 방지 대화를하고 있습니다. 아무도. 중간에 남자. 가장 자. 아무도. 평문을 사용할 수도 있습니다.


2
일부 서버가 https를 사용하기로 결정했다고 해서 클라이언트가진 사람이 자신의 목적을 위해 보안에 대해 허약 한 것을 의미하지는 않습니다 .
거스

3
이 답변이 너무 많이 다운되어 놀랐습니다. 나는 왜 더 많은 것을 이해하고 싶습니다. EJP가 옵션 2를 수행하지 말아야한다고 제안하는 것 같습니다. 옵션 2는 보안에 큰 결함이 있기 때문입니다. 누군가 (전 배치 설정 이외의) 왜이 답변의 품질이 낮은 지 설명 할 수 있습니까?
cr1pto

2
글쎄, 나는 모든 것을 추측한다. 무엇합니까 "그것은 그들에게 것은 단계는 자신의 서버를 사용할 수 있도록하는 데 필요한 데리고" 평균? 서버 운영자가 사용 가능하도록하려면 어떻게해야합니까? 기억해야 할 단계는 무엇입니까? 당신은 그들의 목록을 제공 할 수 있습니까? 그러나 1,000 피트로 돌아가서 OP가 요청한 문제와 어떻게 관련이 있습니까? 그는 클라이언트가 Java에서 자체 서명 된 인증서를 승인하도록하는 방법을 알고 싶어합니다. OP가 인증서를 수락 할 수 있기 때문에 코드에 대한 신뢰를 부여하는 것은 간단합니다.
jww

2
@EJP-내가 틀렸다면 정정하되 자체 서명 된 인증서를 수락하는 것은 클라이언트 측 정책 결정입니다. 서버 측 작업과는 아무런 관련이 없습니다. 클라이언트는 결정을 내려야합니다. 패리티는 키 분배 문제를 해결하기 위해 함께 작동해야하지만 서버를 사용 가능하게하는 것과는 아무런 관련이 없습니다.
jww

3
@EJP-나는 "모든 인증서를 신뢰한다" 고 말하지 않았다 . 어쩌면 나는 뭔가를 잃어 버렸거나 (또는 ​​당신이 너무 많이 읽었을 것입니다 ...) OP에는 인증서가 있으며 그에게 허용됩니다. 그는 그것을 신뢰하는 방법을 알고 싶어합니다. 머리카락을 쪼개고 있지만 인증서를 오프라인으로 전달할 필요는 없습니다. 대역 외 검증을 사용해야하지만 "클라이언트에 오프라인으로 전달해야" 과 동일하지 않습니다 . 그는 서버와 클라이언트를 생산하는 작업장 일 수도 있으므로 사전 지식 이 필요 합니다 .
jww


0

상위 의견에서 제안한대로 keytool을 사용하는 대신 RHEL에서는 최신 버전의 RHEL 6부터 update-ca-trust를 사용할 수 있습니다. 인증서는 pem 형식이어야합니다. 그때

trust anchor <cert.pem>

/etc/pki/ca-trust/source/cert.p11-kit을 편집하고 "인증서 카테고리 : 기타 항목"을 "인증서 카테고리 : 권한"으로 변경하십시오. 또는 스크립트에서 sed를 사용하여이 작업을 수행하십시오.

update-ca-trust

몇 가지주의 사항 :

  • RHEL 6 서버에서 "신뢰"를 찾을 수 없었으며 yum은 설치를 제안하지 않았습니다. RHEL 7 서버에서 사용하고 .p11-kit 파일을 복사했습니다.
  • 이 작업을 수행하려면해야 할 수도 있습니다 update-ca-trust enable. 이것은 / etc / pki / java / cacerts를 / etc / pki / ca-trust / extracted / java / cacerts를 가리키는 심볼릭 링크로 대체합니다. (따라서 전자를 먼저 백업 할 수 있습니다.)
  • Java 클라이언트가 다른 위치에 저장된 cacert를 사용하는 경우 수동으로 / etc / pki / ca-trust / extracted / java / cacerts에 대한 심볼릭 링크로 바꾸거나 해당 파일로 바꾸십시오.

0

url.openConnection();jon-daniel의 답변을 수정 했다고 부르는 라이브러리에 URL을 전달하는 문제가있었습니다.

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;
    }

}

이 클래스를 사용하면 다음을 사용하여 새 URL을 만들 수 있습니다.

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

이것은 지역화되어 기본값을 대체하지 않는 장점이 있습니다 URL.openConnection.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.