투케이2K

555. (javaScript) 자바스크립트 node-forge 라이브러리 사용해 X.509 Certificate , PrivateKey 인증성 생성 수행 본문

JavaScript

555. (javaScript) 자바스크립트 node-forge 라이브러리 사용해 X.509 Certificate , PrivateKey 인증성 생성 수행

투케이2K 2026. 6. 16. 20:04
728x90
반응형

[개발 환경 설정]

개발 툴 : Edit++ / Vscode

개발 언어 : JavaScript

 

[소스 코드]

-----------------------------------------------------------------------------------------
[사전 설명 및 설정 사항]
-----------------------------------------------------------------------------------------

- 개발 환경 : Web


- 개발 기술 : 자바스크립트 / node-forge / X.509


- 사전) 👉 X.509 간단 설명 : 

  >> X.509 는 공개키 기반 구조 (PKI, Public Key Infrastructure) 에서 사용되는 디지털 인증서의 표준 규격입니다.

  >> X.509 는 인터넷에서 사용하는 SSL/TLS 인증서, AWS IoT 인증서, 전자서명 인증서 등이 어떤 형식으로 구성되어야 하는지 정의한 국제 표준입니다.

  >> X.509 인증서는 다음 정보를 담고 있습니다.

    - "이 공개키는 누구의 것인가?"
    - "누가 이 공개키를 보증하는가?"
    - "언제까지 유효한가?"

  >> X.509 인증서 구조 : Base64로 인코딩된 ASN.1 데이터가 들어있습니다.

    -----BEGIN CERTIFICATE-----
    MIIDYzCCAkugAwIBAgI...
    ...
    ...
    -----END CERTIFICATE-----

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[소스 코드]
-----------------------------------------------------------------------------------------

<!DOCTYPE HTML>
<html lang="ko" translate="no">
<head>
    <title>javaScriptTest</title>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <!-- 반응형 구조 만들기 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">

    <!-- Chrome / Edge (Chromium)에서 자동 번역 기능을 완전히 비활성화 -->
    <meta name="google" content="notranslate">

    <!-- 내부 CSS 스타일 지정 -->
    <style>

      html, body {
        width: 100%;
        height: 100%;
        margin : 0 auto;
        padding : 0;
        border : none;
        background-color: #666;
      } 

    </style>





    <!-- [CDN 주소 설정] --> 
    <script src="https://code.jquery.com/jquery-latest.min.js"></script>

    <!-- node-forge 라이브러리 -->
    <script src="https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js"></script>






    <!-- [자바스크립트 코드 지정] -->
    <script type="module">    


      // -----------------------------------------------------------------
      // ✅ [Window.onload 웹 브라우저 로드 완료]
      // -----------------------------------------------------------------
      window.onload = async function() {
        console.log("[window onload] : [html 최초 로드 및 이벤트 상시 대기 실시] : [start]");

        try {


          // -----------------------------------------------------------------
          // ✅ [인증서 생성 요청]
          // -----------------------------------------------------------------
          const certCreateButton = document.getElementById('certCreateButton');

          certCreateButton.addEventListener('click', async () => {
            createCert();
          });


          // -----------------------------------------------------------------
          // ✅ [인증서 정보 확인]
          // -----------------------------------------------------------------
          const certGetInfoButton = document.getElementById('certGetInfoButton');

          certGetInfoButton.addEventListener('click', async () => {
            checkCert();
          });


          // -----------------------------------------------------------------
          // ✅ [인증서 된 인증서 정보 확인 로그]
          // -----------------------------------------------------------------
          /*
          [checkCert] : [start]
          📄 checkCert : 선택된 파일 : sampleCert.txt
          📄 checkCert : 인증서 파일 읽기 완료 : 

          -----BEGIN CERTIFICATE-----
          MI..0z
          -----END CERTIFICATE-----

          📄 checkCert : 인증서 파일 유효 기간 확인 :  Tue Jun 16 2026 08:31:50 GMT+0900 (한국 표준시) / Wed Jun 16 2027 08:31:50 GMT+0900 (한국 표준시)  /  유효
          📄 checkCert : 인증서 식별 정보 확인 :  D3:D8:21:79:76:C2:6F:33:58:88:34:F4:D3:A7:A8:5D:3F:CF:22:5E:01:5F:80:D4:D8:77:CE:8E:8F:06:1F:99
          📄 checkCert : 인증서 시리얼 정보 확인 :  77752971727d4beca730900cb07039100f406425
          📄 checkCert : 인증서 발급 정보 확인 :  {"attributes":[{"type":"2.5.4.11","value":"Amazon Web Services O=Amazon.com Inc. L=Seattle ST=Washington C=US","valueTagClass":19,"name":"organizationalUnitName","shortName":"OU"}],"hash":"482099f4f063632806e8b8e7c891a712e3df3faf"}
          📄 checkCert : 인증서 서명 정보 확인 :  {"algorithmOid":"1.2.840.113549.1.1.11","parameters":{}}
          */
          // -----------------------------------------------------------------

        }
        catch (exception) {
          console.error("[window onload] : [Exception] : ❌ 예외 상황 발생 : ", exception);

        }

      };






      // -----------------------------------------------------------------
      // ✅ [Certificate 인증서 생성]
      // -----------------------------------------------------------------
      async function createCert(){
        console.log("[createCert] : [start]");

        try {

          // -----------------------------------------
          // 🟦 [키 생성]
          // -----------------------------------------

          const keys = forge.pki.rsa.generateKeyPair(2048);

          
          // -----------------------------------------
          // 🟦 [인증서 생성]
          // -----------------------------------------          
          const cert = forge.pki.createCertificate();
          cert.publicKey = keys.publicKey;

          //cert.serialNumber = '01'; // 인증서 시리얼 정보

          // 인증서 serialNumber는 보통 CA에서 유니크하게 보장되도록 동적으로 생성해야 합니다.
          // forge에서는 문자열(hex)만 넣으면 되기 때문에 👉 직접 생성 로직을 만들어야 합니다.
          const serialNumber = '00' + forge.util.bytesToHex(
            forge.random.getBytesSync(20)
          );

          cert.serialNumber = serialNumber; // 인증서 시리얼 정보 : OpenSSL 스타일 (20 byte) : 실제 CA들이 많이 쓰는 방식



          // -----------------------------------------
          // 🟦 [유효기간 지정]
          // -----------------------------------------           
          cert.validity.notBefore = new Date();
          cert.validity.notAfter = new Date();
          cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); // 1년 지정


          // -----------------------------------------
          // 🟦 [subject / issuer 지정]
          // -----------------------------------------       
          // subject / issuer (같아야 함 ✅)   
          // ----------------------------------------- 
          /*
          const attrs = [
            { name: 'commonName', value: 'AWS IoT Certificate' }
          ];
          // */

          /*
          const attrs = [          
            { name: 'commonName', value: 'AWS IoT Certificate' },
            { name: 'organizationName', value: 'Amazon.com Inc.' },
            { name: 'countryName', value: 'US' }
          ];
          // */

          const attrs = [          
            { name: 'organizationalUnitName', value: 'Amazon Web Services O=Amazon.com Inc. L=Seattle ST=Washington C=US', shortName: "OU" }
          ];
          
          cert.setSubject(attrs);
          cert.setIssuer(attrs); 

          
          // -----------------------------------------
          // 🟦 [서명 및 PEM 변환]
          // -----------------------------------------
          cert.sign(keys.privateKey, forge.md.sha256.create()); // 👉 self-signed 
          
          const certPem = forge.pki.certificateToPem(cert);
          const privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey);

          console.log("📄 createCert : 인증서 파일 생성 완료 : " + "\n\n" + certPem + "\n\n");
          console.log("📄 createCert : PrivateKey 파일 생성 완료 : " + "\n\n" + privateKeyPem + "\n\n");

        }
        catch (exception) {
          console.error("[createCert] : [Exception] : ❌ 예외 상황 발생 : ", exception);

        }
      };





      // -----------------------------------------------------------------
      // ✅ [Certificate 정보 확인]
      // -----------------------------------------------------------------
      async function checkCert(){
        console.log("[checkCert] : [start]");

        try {

          // -----------------------------------------
          // 🟦 [선택 된 파일이 있는지 확인]
          // -----------------------------------------
          const certFileInput = document.getElementById("certFile");
          const certFile = certFileInput.files[0];

          if (!certFile) {
            console.error('❌ Certificate.Pem 파일을 선택하세요.');
            return;
          }

          console.log("📄 checkCert : 선택된 파일 : " + certFile.name);


          // -----------------------------------------
          // 🟦 [Certificate 선택 된 파일 내용 읽기]
          // -----------------------------------------
          const certReader = new FileReader();

          certReader.onerror = (e) => {
            console.error('❌ certReader 파일 읽기 에러 발생 : ', e.target.error);
          };

          certReader.onload = function(event) {

            let certPem = event.target.result;
            
            // -----------------------------------------
            // ✅ 중요: PEM 정리
            // -----------------------------------------
            certPem = certPem
              .replace(/^\uFEFF/, "")   // BOM 제거
              .replace(/\r\n/g, "\n")   // 줄바꿈 통일
              .trim();

            console.log("📄 checkCert : 인증서 파일 읽기 완료 : " + "\n\n" + certPem + "\n\n");

            if (certPem.includes("BEGIN CERTIFICATE") == false || certPem.includes("END CERTIFICATE") == false){
              console.error('❌ Certificate.Pem 파일 형식 확인이 필요합니다.');
              return;
            }


            // -----------------------------------------
            // X.509 인증서 파싱
            // -----------------------------------------
            const cert = forge.pki.certificateFromPem(certPem);


            // -----------------------------------------
            // X.509 인증서 만료 기간 확인
            // -----------------------------------------
            const now = new Date();

            const notBefore = cert.validity.notBefore;
            const notAfter = cert.validity.notAfter;

            let status = "유효";

            if (now < notBefore) {
              status = "아직 유효기간 시작 전";
            }
            else if (now > notAfter) {
              status = "만료";
            }

            console.log("📄 checkCert : 인증서 파일 유효 기간 확인 : ", notBefore + " / " + notAfter, ' / ', status);


            // -----------------------------------------
            // X.509 인증서 식별 정보 확인
            // -----------------------------------------
            const md = forge.md.sha256.create();

            md.update(
              forge.asn1.toDer(
                  forge.pki.certificateToAsn1(cert)
              ).getBytes()
            );

            const fingerPint = md.digest().toHex()
              .match(/.{1,2}/g)
              .join(":")
              .toUpperCase();

            console.log("📄 checkCert : 인증서 식별 정보 확인 : ", fingerPint);


            // -----------------------------------------
            // X.509 인증서 추가 정보 출력
            // -----------------------------------------
            console.log("📄 checkCert : 인증서 시리얼 정보 확인 : ", cert.serialNumber);
            console.log("📄 checkCert : 인증서 발급 정보 확인 : ", JSON.stringify(cert.issuer));
            console.log("📄 checkCert : 인증서 서명 정보 확인 : ", JSON.stringify(cert.siginfo));

          };

          // ✅ 반드시 TEXT 로 읽기
          certReader.readAsText(certFile);

        }
        catch (exception) {
          console.error("[checkCert] : [Exception] : ❌ 예외 상황 발생 : ", exception);

        }
      };
      

    </script>


</head>


<body>

  <br><br>
  <!-- ✅ 버튼 -->
  <button id="certCreateButton">인증서 생성</button>
  <br><br>


  <br><br>
  <h2>📄 Certificate 인증서 선택</h2>
  <br><br>

  <!-- ✅ 파일 선택 -->
  <br><br>
  <input type="file" id="certFile" accept=".pem,.crt,.txt" />
  <br><br>

  <!-- ✅ 버튼 -->
  <br><br>
  <button id="certGetInfoButton">인증서 확인</button>
  <br><br>

</body>

</html>

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[참고 사이트]
-----------------------------------------------------------------------------------------

▶️ [자바스크립트 node-forge 라이브러리 사용해 X.509 인증서 Certificate , PrivateKey 키 쌍 일치 여부 확인]

https://kkh0977.tistory.com/8918

https://blog.naver.com/kkh0977/224316777465


▶️ [자바스크립트 node-forge 라이브러리 사용해 X.509 인증서 식별 정보 확인 - getSha256Fingerprint]

https://kkh0977.tistory.com/8893

https://blog.naver.com/kkh0977/224307596780


▶️ [자바스크립트 node-forge 라이브러리 사용해 X.509 인증서 만료 기간 유효성 검증 수행]

https://kkh0977.tistory.com/8892

https://blog.naver.com/kkh0977/224307272979


▶️ [자바스크립트 AWS Iot registerCertificateWithoutCA 사용해 클레임 인증서 등록 요청 수행 - 공통 클레임 인증서 사용]

https://kkh0977.tistory.com/8886

https://blog.naver.com/kkh0977/224306059609


▶️ [Aws Iot Core] Aws 프로비저닝 수행에 필요한 클레임 인증서 (claim certificate) 설명 정리

https://kkh0977.tistory.com/7799

https://blog.naver.com/kkh0977/223784109192?trackingCode=blog_bloghome_searchlist


▶️ [Aws Iot Core] Aws Iot Core 프로비저닝에 사용 되는 클레임 인증서 종류 설명 및 생성 방법 정리

https://kkh0977.tistory.com/8100

https://blog.naver.com/kkh0977/223918338710?trackingCode=blog_bloghome_searchlist

-----------------------------------------------------------------------------------------
 
728x90
반응형
Comments