OpenSSL PKC12S API

OpenSSL을 사용하면 다양한 인증서를 안전하게 하나의 파일로 보관할 수 있다


PKCS#12

  • Public Key Cryptography Standards
  • RSA Security에서 제정한 public-key cryptography 표준들의 묶음을 PKCS라고 한다
  • 그 중에서도 PKCS#12는 여러 개의 cryptography object들을 하나의 파일에 저장하는 파일의 포맷을 정의한다
  • 보통 private key와 X.509 certificate를 묶거나(bundle) 모든 chain of trust(일련의 인증서들)를 묶는데 사용한다
    • X.509 - public key certificate(인증서) 포맷에 대한 국제 표준
    • chain of trust - root certificate(CA에서 발급한 인증서)에서부터 end entity(애플리케이션, 사용자, 장치 등)까지 이어지는 검증
    • CA(Certificate Authority) - 신뢰할 수 있는 인증기관. 디지털 인증서를 발급하고 관리한다
  • OpenSSL의 pkcs12 명령어를 사용하면 읽을 수 있다

공개키 인증서(Public Key Certificate)

  • 공개키의 진위 여부를 보증하기 위한 문서로 공개키 자체와 그와 관련된 정보를 포함한다
    • public key
    • 공개키를 소유한 subject의 신원에 관한 정보
    • 이 인증서를 발급한 CA의 전자서명
  • 단말 A와 서버 B가 통신할때 서버 B는 자신의 인증서를 A에게 보낸다. A는 이 인증서를 검증하여 B가 신뢰할 수 있는 entity임을 확신한다
    • 인증서를 검증하기 위해서는 CA의 전자서명이 유효한지를 검사해야 하는데 그러려면 CA의 공개키를 알아야한다. CA의 공개키는 단말A(e.g. web browser)의 root certificate에 저장되어 있다
  • E1의 공개키를 검증하기 위해서는 상위 주체인 E2의 공개키가 필요하고 E2의 공개키를 검증하려면 상위 주체인 E3의 공개키가 필요하고 ... 이런 체인의 가장 위에 있는 인증서가 root certificate이다
    • 이런 루트 인증서는 하드웨어 제조사에서 배포한다

디지털 서명(Digital Signature)

  • electronic signature(전자 서명)이라고도 한다
  • 디지털 정보가 진본임을 확인할 수 있는 수학적 방법(scheme)이다
  • A가 B에게 메세지와 자신의 디지털 서명을 같이 보낸다 --> B는 디지털 서명을 검증하여 메세지가 A로부터 온것임을 확신할 수 있다
    • A는 자신의 공개키가 포함된 인증서를 B에게 제공한다
    • A의 공개키로 디지털 서명을 복호화하면 메세지의 해시값이 나온다. 매세지를 해싱한 값과 이 값을 비교해서 메세지가 변조되지 않았음을 확신할 수 있다
  • 디지털 서명은 대부분의 암호화 프로토콜에 포함되는 요소이다
  • 디지털 서명을 통해 데이터가 위조되었거나 부 변경(tampering)되었음을 탐지할 수 있다

digital-signature-diagram

By FlippyFlink - Combined changed the image https://en.wikipedia.org/wiki/File:Public_key_encryption.svg from encryption to signing., CC BY-SA 4.0, Link

  • Alice의 공개키가 진짜임을 확인하는 방법은 Alice가 CA로부터 자신의 공개키를 포함하는 CA를 발급받아서 Bob에게 전달하는 것이다
  • 단, B의 루트 인증서에 CA의 공개키가 저장되어 있어야 한다

ASN1(Abstract Syntax Notation 1)

  • 데이터의 구조(파일의 포맷과 동치)를 정의하는 표준 언어
  • 주로 네트워크 통신, 인증서, 보안 프로토콜 등에서 데이터의 표현 방식과 인코딩을 정의하는 데 사용
  • ASN.1은 특히 보안 관련 프로토콜에서 널리 사용됨
  • ASN.1의 주요 직렬화 포맷은 DER(Distinguished Encoding Rules)이다
struct point {
  int x, y;
  char label[10];
};
Point ::= SEQUENCE {
  x INTEGER,
  y INTEGER,
  label UTF8String
}

OpenSSL API

  • EVP_PKEY - 공개키와 비밀키를 저장하는 구조체
  • X509 - X.509인증서를 표현하는 구조체
    • X.509는 공개키 인증서 포맷에 대한 국제 표준
  • PKCS12 *PKCS12_create(const char *pass, const char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ca, int nid_key, int nid_cert, int iter, int mac_iter, int keytype);
    • pass - 인증서를 보호할 비밀번호
    • name - 인증서를 가리키는 이름을 지정
    • pkey - 개인 키가 저장된 공간
    • cert - pkey를 보증하는 인증서
    • ca - NULL 전달 가능. CA가 발급한 인증서. cert를 검증하는데 사용할수 있다
  • int i2d_TYPE_fp(FILE *fp, TYPE *a);
    • TYPE 인스턴스를 DER 포맷으로 인코딩하여 fp에 쓴다

PKCS#12 생성 프로그램

  • usage: ./pkwrite <X.509파일> <비밀번호> <name> <p12file>
  • 인증서(X.509파일)를 저장하는 p12파일을 새로 생성한다
  • p12file 내부에서 이 인증서의 이름은 name으로 설정하고 비밀번호로 보호한다
/*
 * Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>

/* Simple PKCS#12 file creator */

int main(int argc, char **argv)
{
    FILE *fp;
    EVP_PKEY *pkey;
    X509 *cert;
    PKCS12 *p12;
    if (argc != 5) {
        fprintf(stderr, "Usage: pkwrite infile password name p12file\n");
        exit(1);
    }
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    if ((fp = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        exit(1);
    }
    cert = PEM_read_X509(fp, NULL, NULL, NULL);
    rewind(fp);
    pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    p12 = PKCS12_create(argv[2], argv[3], pkey, cert, NULL, 0, 0, 0, 0, 0);
    if (!p12) {
        fprintf(stderr, "Error creating PKCS#12 structure\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    if ((fp = fopen(argv[4], "wb")) == NULL) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    i2d_PKCS12_fp(fp, p12);
    PKCS12_free(p12);
    fclose(fp);
    return 0;
}

PKCS#12 읽기 프로그램

/*
 * Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>

/* Simple PKCS#12 file reader */

static char *find_friendly_name(PKCS12 *p12)
{
    STACK_OF(PKCS7) *safes;
    int n, m;
    char *name = NULL;
    PKCS7 *safe;
    STACK_OF(PKCS12_SAFEBAG) *bags;
    PKCS12_SAFEBAG *bag;

    if ((safes = PKCS12_unpack_authsafes(p12)) == NULL)
        return NULL;

    for (n = 0; n < sk_PKCS7_num(safes) && name == NULL; n++) {
        safe = sk_PKCS7_value(safes, n);
        if (OBJ_obj2nid(safe->type) != NID_pkcs7_data
                || (bags = PKCS12_unpack_p7data(safe)) == NULL)
            continue;

        for (m = 0; m < sk_PKCS12_SAFEBAG_num(bags) && name == NULL; m++) {
            bag = sk_PKCS12_SAFEBAG_value(bags, m);
            name = PKCS12_get_friendlyname(bag);
        }
        sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free);
    }

    sk_PKCS7_pop_free(safes, PKCS7_free);

    return name;
}

int main(int argc, char **argv)
{
    FILE *fp;
    EVP_PKEY *pkey = NULL;
    X509 *cert = NULL;
    STACK_OF(X509) *ca = NULL;
    PKCS12 *p12 = NULL;
    char *name = NULL;
    int i, ret = EXIT_FAILURE;

    if (argc != 4) {
        fprintf(stderr, "Usage: pkread p12file password opfile\n");
        exit(EXIT_FAILURE);
    }

    if ((fp = fopen(argv[1], "rb")) == NULL) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    p12 = d2i_PKCS12_fp(fp, NULL);
    fclose(fp);
    if (p12 == NULL) {
        fprintf(stderr, "Error reading PKCS#12 file\n");
        ERR_print_errors_fp(stderr);
        goto err;
    }
    if (!PKCS12_parse(p12, argv[2], &pkey, &cert, &ca)) {
        fprintf(stderr, "Error parsing PKCS#12 file\n");
        ERR_print_errors_fp(stderr);
        goto err;
    }
    name = find_friendly_name(p12);
    PKCS12_free(p12);
    if ((fp = fopen(argv[3], "w")) == NULL) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        goto err;
    }
    if (name != NULL)
        fprintf(fp, "***Friendly Name***\n%s\n", name);
    if (pkey != NULL) {
        fprintf(fp, "***Private Key***\n");
        PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL);
    }
    if (cert != NULL) {
        fprintf(fp, "***User Certificate***\n");
        PEM_write_X509_AUX(fp, cert);
    }
    if (ca != NULL && sk_X509_num(ca) > 0) {
        fprintf(fp, "***Other Certificates***\n");
        for (i = 0; i < sk_X509_num(ca); i++)
            PEM_write_X509_AUX(fp, sk_X509_value(ca, i));
    }
    fclose(fp);

    ret = EXIT_SUCCESS;

 err:
    OPENSSL_free(name);
    X509_free(cert);
    EVP_PKEY_free(pkey);
    sk_X509_pop_free(ca, X509_free);

    return ret;
}

references