code

OpenSSL을 사용하여 프로그래밍 방식으로 X509 인증서 생성

codestyles 2020. 11. 6. 08:18
반응형

OpenSSL을 사용하여 프로그래밍 방식으로 X509 인증서 생성


C / C ++ 애플리케이션이 있고 공개 및 개인 키가 모두 포함 된 X509 pem 인증서를 만들어야합니다. 인증서는 자체 서명되거나 서명되지 않은 것일 수 있습니다.

명령 줄이 아닌 앱 내에서이 작업을 수행하고 싶습니다.

어떤 OpenSSL 기능이이 작업을 수행합니까? 모든 샘플 코드는 보너스입니다!


먼저 용어와 메커니즘을 숙지해야합니다.

X.509 인증서 는 정의에 따라 개인 키를 포함하지 않습니다. 대신 CA가 서명 한 공개 키의 CA 서명 버전입니다 (CA가 서명에 넣는 모든 속성 포함). PEM 형식은 실제로 키와 인증서의 별도 저장소 만 지원합니다. 그런 다음 둘을 연결할 수 있습니다.

어쨌든 키와 자체 서명 된 인증서를 생성하려면 OpenSSL API의 20 개 이상의 다른 기능을 호출해야합니다. 예제는 demos / x509 / mkcert.c 의 OpenSSL 소스 자체에 있습니다.

자세한 답변은 아래 Nathan Osman의 설명을 참조하십시오.


나는 이것이 매우 늦고 긴 대답이라는 것을 알고 있습니다. 그러나이 질문이 검색 엔진 결과에서 얼마나 좋은지 고려해 보면 괜찮은 대답을 쓸 가치가 있다고 생각했습니다.

아래에서 읽을 내용은 이 데모 와 OpenSSL 문서 에서 빌린 것 입니다. 아래 코드는 C와 C ++ 모두에 적용됩니다.


실제로 인증서를 생성하기 전에 개인 키를 생성해야합니다. OpenSSL은 EVP_PKEY알고리즘 독립적 인 개인 키를 메모리에 저장하기위한 구조를 제공합니다 . 이 구조는에서 선언 openssl/evp.h되었지만 openssl/x509.h(나중에 필요함)에 의해 포함되므로 헤더를 명시 적으로 포함 할 필요가 없습니다.

EVP_PKEY구조 를 할당하기 위해 다음을 사용합니다 EVP_PKEY_new.

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

또한 구조를 해제하기위한 해당 함수가 있습니다.-- EVP_PKEY_free단일 인수 ( EVP_PKEY위에서 초기화 된 구조) 를 허용합니다 .

이제 키를 생성해야합니다. 이 예에서는 RSA 키를 생성합니다. RSA_generate_key에서 선언 된 함수 로 수행 됩니다 openssl/rsa.h. 이 함수는 RSA구조에 대한 포인터를 반환합니다 .

함수의 간단한 호출은 다음과 같습니다.

RSA * rsa;
rsa = RSA_generate_key(
    2048,   /* number of bits for the key - 2048 is a sensible value */
    RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
    NULL,   /* callback - can be NULL if we aren't displaying progress */
    NULL    /* callback argument - not needed in this case */
);

의 반환 값 경우 RSA_generate_keyIS NULL, 다음 뭔가 잘못 갔다. 그렇지 않은 경우 이제 RSA 키가 있으며 EVP_PKEY이전 구조에 할당 할 수 있습니다 .

EVP_PKEY_assign_RSA(pkey, rsa);

RSA때 구조는 자동으로 해제됩니다 EVP_PKEY구조가 해제됩니다.


이제 인증서 자체입니다.

OpenSSL은 X509구조를 사용하여 메모리에서 x509 인증서를 나타냅니다. 이 구조체의 정의는 openssl/x509.h. 우리가 필요로 할 첫 번째 기능은 X509_new. 사용은 비교적 간단합니다.

X509 * x509;
x509 = X509_new();

의 경우와 마찬가지로 EVP_PKEY구조를 해제하는 해당 기능이 X509_free있습니다.

이제 몇 가지 X509_*기능을 사용하여 인증서의 몇 가지 속성을 설정해야 합니다.

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

이렇게하면 인증서의 일련 번호가 '1'로 설정됩니다. 일부 오픈 소스 HTTP 서버는 일련 번호가 기본값 인 '0'인 인증서 수락을 거부합니다. 다음 단계는 인증서가 실제로 유효한 기간을 지정하는 것입니다. 다음 두 가지 함수 호출로이를 수행합니다.

X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

첫 번째 줄은 인증서의 notBefore속성을 현재 시간으로 설정합니다. (이 X509_gmtime_adj함수는 지정된 시간 (초)을 현재 시간에 추가합니다 ( 이 경우 없음).) 두 번째 행은 인증서의 notAfter속성을 지금부터 365 일 (60 초 * 60 분 * 24 시간 * 365 일)로 설정합니다.

이제 앞서 생성 한 키를 사용하여 인증서에 대한 공개 키를 설정해야합니다.

X509_set_pubkey(x509, pkey);

자체 서명 된 인증서이므로 발급자 이름을 주체 이름으로 설정합니다. 이 프로세스의 첫 번째 단계는 주제 이름을 가져 오는 것입니다.

X509_NAME * name;
name = X509_get_subject_name(x509);

이전에 명령 줄에서 자체 서명 된 인증서를 만든 적이 있다면 국가 코드를 묻는 것을 기억할 것입니다. 조직 ( 'O') 및 일반 이름 ( 'CN')과 함께 제공하는 위치는 다음과 같습니다.

X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
                           (unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
                           (unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
                           (unsigned char *)"localhost", -1, -1, 0);

(저는 캐나다 사람이고 국가 코드이기 때문에 여기서 'CA'값을 사용하고 있습니다. 또한 매개 변수 # 4는 명시 적으로으로 캐스팅해야합니다 unsigned char *.)

이제 실제로 발급자 이름을 설정할 수 있습니다.

X509_set_issuer_name(x509, name);

마지막으로 서명 프로세스를 수행 할 준비가되었습니다. X509_sign이전에 생성 한 키로 호출 합니다. 이를위한 코드는 매우 간단합니다.

X509_sign(x509, pkey, EVP_sha1());

키에 서명하기 위해 SHA-1 해싱 알고리즘을 사용하고 있습니다. 이것은 mkcert.cMD5를 사용하는이 답변의 시작 부분에서 언급 한 데모 와 다릅니다 .


이제 자체 서명 된 인증서가 있습니다! 하지만 아직 끝나지 않았습니다.이 파일을 디스크에 써야합니다. 다행히 OpenSSL을 우리가 너무 거기에 덮여가 PEM_*선언되는 함수 openssl/pem.h. 첫 번째로 필요한 것은 PEM_write_PrivateKey개인 키를 저장하는 것입니다.

FILE * f;
f = fopen("key.pem", "wb");
PEM_write_PrivateKey(
    f,                  /* write the key to the file we've opened */
    pkey,               /* our key from earlier */
    EVP_des_ede3_cbc(), /* default cipher for encrypting the key on disk */
    "replace_me",       /* passphrase required for decrypting the key on disk */
    10,                 /* length of the passphrase string */
    NULL,               /* callback for requesting a password */
    NULL                /* data to pass to the callback */
);

개인 키를 암호화하지 않으려면 NULL위의 세 번째 및 네 번째 매개 변수 를 전달 하면됩니다. 어느 쪽이든, 파일이 세계에서 읽을 수 없도록 확실히하고 싶을 것입니다. (Unix 사용자의 경우 이는를 의미 chmod 600 key.pem합니다.)

아휴! 이제 우리는 하나의 기능으로 내려갔습니다. 인증서를 디스크에 써야합니다. 이를 위해 필요한 기능은 PEM_write_X509다음과 같습니다.

FILE * f;
f = fopen("cert.pem", "wb");
PEM_write_X509(
    f,   /* write the certificate to the file we've opened */
    x509 /* our certificate */
);

그리고 우리는 끝났습니다! 이 답변의 정보가 OpenSSL의 표면을 거의 긁지 않았지만 모든 것이 어떻게 작동하는지 대략적인 아이디어를 제공하기에 충분합니다.

실제 응용 프로그램에서 같은 외모 위의 코드의 모든 무엇을보고에 관심있는 사람, 나는 함께 당신이 볼 수있는 요점 (C ++로 작성) 발생했습니다 여기를 .


system앱 내 에서 호출을 통해이 작업을 수행 할 가능성이 있습니까? 이를 수행하는 몇 가지 좋은 이유 :

  • 라이선싱 : openssl실행 파일을 호출하면 응용 프로그램에서 분리되고 특정 이점을 제공 할 수 있습니다. 면책 조항 : 이에 대해 변호사와 상담하십시오.

  • Documentation: OpenSSL comes with phenomenal command-line documentation that greatly simplifies a potentially complicated tool.

  • Testability: you can exercise OpenSSL from the command line until you understand exactly how to create your certs. There are a lot of options; expect to spend about a day on this until you get all the details right. After that, it's trivial to incorporate the command into your app.

If you choose to use the API, check the openssl-dev developers' list on www.openssl.org.

Good luck!


Very simple tutorial to create digital certificates http://publib.boulder.ibm.com/infocenter/rsthelp/v8r0m0/index.jsp?topic=/com.ibm.rational.test.lt.doc/topics/tcreatecertopenssl.html

About executing these commands from your code I'm not sure.


Nathan Osman explained it greatly and fully, had the same problem to be solved in C++ so here is my small addition, cpp-style rewritten concept with a couple of caveats taken into account:

bool generateX509(const std::string& certFileName, const std::string& keyFileName, long daysValid)
{
    bool result = false;

    std::unique_ptr<BIO, void (*)(BIO *)> certFile  { BIO_new_file(certFileName.data(), "wb"), BIO_free_all  };
    std::unique_ptr<BIO, void (*)(BIO *)> keyFile { BIO_new_file(keyFileName.data(), "wb"), BIO_free_all };

    if (certFile && keyFile)
    {
        std::unique_ptr<RSA, void (*)(RSA *)> rsa { RSA_new(), RSA_free };
        std::unique_ptr<BIGNUM, void (*)(BIGNUM *)> bn { BN_new(), BN_free };

        BN_set_word(bn.get(), RSA_F4);
        int rsa_ok = RSA_generate_key_ex(rsa.get(), RSA_KEY_LENGTH, bn.get(), nullptr);

        if (rsa_ok == 1)
        {
            // --- cert generation ---
            std::unique_ptr<X509, void (*)(X509 *)> cert { X509_new(), X509_free };
            std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)> pkey { EVP_PKEY_new(), EVP_PKEY_free};

            // The RSA structure will be automatically freed when the EVP_PKEY structure is freed.
            EVP_PKEY_assign(pkey.get(), EVP_PKEY_RSA, reinterpret_cast<char*>(rsa.release()));
            ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1); // serial number

            X509_gmtime_adj(X509_get_notBefore(cert), 0); // now
            X509_gmtime_adj(X509_get_notAfter(cert), daysValid * 24 * 3600); // accepts secs

            X509_set_pubkey(cert.get(), pkey.get());

            // 1 -- X509_NAME may disambig with wincrypt.h
            // 2 -- DO NO FREE the name internal pointer
            X509_name_st* name = X509_get_subject_name(cert.get());

            const uchar country[] = "RU";
            const uchar company[] = "MyCompany, PLC";
            const uchar common_name[] = "localhost";

            X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, country, -1, -1, 0);
            X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, company, -1, -1, 0);
            X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0);

            X509_set_issuer_name(cert.get(), name);
            X509_sign(cert.get(), pkey.get(), EVP_sha256()); // some hash type here


            int ret  = PEM_write_bio_PrivateKey(keyFile.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
            int ret2 = PEM_write_bio_X509(certFile.get(), cert.get());

            result = (ret == 1) && (ret2 == 1); // OpenSSL return codes
        }
    }

    return result;
}

Of course, there should be more checks of function's return values, actually all of them should be checked but that would make a sample too "branchy" and is pretty easy to improve anyway.

참고URL : https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl

반응형