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)되었음을 탐지할 수 있다
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
- [WIKIPEDIA]PCKS
- [University of Toronto] OpenSSL Tutorial
- [OpenSSL_1_1_1-stable] openssl/demos/pkcs12/pkread.c
- [OpenSSL_1_1_1-stable] openssl/demos/pkcs12/pkwrite.c
- [OpenSSL Documentation 1.1.1]
- [OpenSSL Documentation 3.0] migration_guide
- [WIKIPEDIA] ASN1
- A Warm Welcome to ASN.1 and DER