https學習筆記三----OpenSSL生成root CA及簽發證書


         在https學習筆記二,已經弄清了數字證書的概念,組成和在https連接過程中,客戶端是如何驗證服務器端的證書的。這一章,主要介紹下如何使用openssl庫來創建key file,以及生成root CA及簽發子證書。學習主要參考官方文檔:https://www.feistyduck.com/library/openssl-cookbook/online/ch-openssl.html#

一、openssl 簡介

        openssl 是目前最流行的 SSL 密碼庫工具,其提供了一個通用、健壯、功能完備的工具套件,用以支持SSL/TLS 協議的實現。官網:https://www.openssl.org/source/,其中有3個主要的用途:1、密碼算法庫(建立 RSA、DH、DSA key 參數,計算消息摘要,使用各種 Cipher加密/解密) 2、密鑰和證書封裝管理功能(建立 X.509 證書、證書簽名請求(CSR)和CRLs(證書回收列表));3、SSL通信API接口(SSL/TLS 客戶端以及服務器的測試,處理S/MIME 或者加密郵件)。

二、安裝openssl(linux CentOS7 32位)

        如果使用的是unix操作系統,可能安裝系統的時候,這個庫就已經有且存在了。但是在使用前,需要注意下當前openssl的庫的版本     

openssl version
OpenSSL 1.0.1 14 Mar 2012

        因為版本1.0.1是一個很重要的風水嶺版本。因為1.0.1是第一個支持TLS1.1和1.2的版本。支持新的協議。操作系統的選擇也很重要,比如Ubuntu 12.04 LTS,客戶端不支持SSL2。這里安裝以CentOS7系統為例:

A、 下載openssl庫文件:https://www.openssl.org/source/  

B、將下載的壓縮包放在根目錄下,解壓縮,進入解壓縮文件(得到openssl-openssl-1.0.0文件夾)cd openssl-1.0.0

C、編譯前配置openssl,執行命令:./config --prefix=/usr/local/openssl,其中 ( --prefix )參數為欲安裝之目錄,也就是安裝后的檔案會出現在該目錄下。

D、編譯openssl,執行命令: make install 

小插曲:安裝openssl報錯

1、問題描述:安裝完成,查看版本信息的時候報錯了,缺少一個庫文件libssl.so.1.1。
[root@b6e4cbd27773 /usr/local/openssl/bin]# openssl version
      openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared 

object file: No such file or directory
2、解決方法有依賴沒裝libssl。在/etc/ld.so.conf文件中寫入openssl庫文件的搜索路徑,使用修改后的conf生效即可:
echo "/usr/local/lib64" >> /etc/ld.so.conf
ldconfig -v

三、使用openssl生成RSA密鑰對

         使用openssl的私鑰產生公鑰前,需要了解以下幾點:

1、key算法:openssl 支持生成RSA,DSA,ECDSA的密鑰對,但是RSA是目前使用最普遍的。

2、Key長度:RSA的2048是公認較比較安全的key長度。

3、密碼(Passphrase):在key上使用密碼是一個可選值,但是一般都是強烈建議的(官網這樣寫的,實際項目中很多都沒有設置口令),這樣每次使用key文件時,都需要輸入這個密碼才能使用,增強了其安全性,但是隨之而來的易用性也會變差。  

         使用genrsa命令來生成RSA key( 產生DSA其他算法的key文件,可以直接參考學習官網教程,在此處以常用的為例),2步驟能完成:

A、生成私鑰:

         使用命令:openssl genrsa -aes128 -out fd.key 2048 。以下輸入了為這個key值設置了密碼,且密碼使用aes128加密保存。

openssl genrsa -aes128 -out fd.key 2048
Generating RSA private key, 2048 bit long modulus ....+++ ...................................................................................+++ e is 65537 (0x10001) Enter pass phrase for fd.key: **************** Verifying - Enter pass phrase for fd.key: ****************

         這個key文件就是私鑰文件。可以查看下文件內容:

 cat fd.key
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,01EC21976A463CE36E9DB59FF6AF689A vERmFJzsLeAEDqWdXX4rNwogJp+y95uTnw+bOjWRw1+O1qgGqxQXPtH3LWDUz1Ym mkpxmIwlSidVSUuUrrUzIL+V21EJ1W9iQ71SJoPOyzX7dYX5GCAwQm9Tsb40FhV/ [21 lines removed...] 4phGTprEnEwrffRnYrt7khQwrJhNsw6TTtthMhx/UCJdpQdaLW/TuylaJMWL1JRW i321s5me5ej6Pr4fGccNOe7lZK+563d7v5znAx+Wo1C+F7YgF+g8LOQ8emC+6AVV -----END RSA PRIVATE KEY-----       

 B、生成公鑰:

         使用命令:openssl rsa -in fd.key -pubout -out fd-public.key

openssl rsa -in fd.key -pubout -out fd-public.key
Enter pass phrase for fd.key: ****************

         查看這個key文件,就是公鑰:

 cat fd-public.key
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnlccwQ9FRyJYHM8sFNsY PUHJHJzhJdwcS7kBptutf/L6OvoEAzCVHi/m0qAA4QM5BziZgnvv+FNnE3sgE5pz iovEHJ3C959mNQmpvnedXwfcOIlbrNqdISJiP0js6mDCzYjSO1NCQoy3UpYwvwj7 0ryR1F+abARehlts/Xs/PtX3VamrljiJN6JNgFICy3ZvEhLZEKxR7oob7TnyZDrj IHxBbqPNzeiqLCFLFPGgJPa0cH8DdovBTesvu7wr/ecsf8CYyUCdEwGkZh9DKtdU HFa9H8tWW2mX6uwYeHCnf2HTw0E8vjtOb8oYQxlQxtL7dpFyMgrpPOoOVkZZW/P0 NQIDAQAB -----END PUBLIC KEY-----

四、獲取權威機構頒發證書步驟

        獲取權威機構頒發的證書,需要先得到私鑰的key文件(.key),然后使用私鑰的key文件生成sign req 文件(.csr),最后把csr文件發給權威機構,等待權威機構認證,認證成功后,會返回證書文件(.crt)。 

A:生成私鑰key。

        與第二節使用openssl生成RSA密鑰對的步驟A一致。使用命令:openssl genrsa -aes128 -out fd.key 2048 

B:私鑰的key文件生成sign req 文件(.csr)

      生成csr文件時,需要填寫一些關於待簽人或者公司的一些信息,比如國家名,省份名,組織機構名,主機名,email名,有些信息可以不填寫,使用.標識。

        使用命令:openssl req -new -key fd.key -out fd.csr。過程如下:           

$ openssl req -new -key fd.key -out fd.csr
Enter pass phrase for fd.key: ****************
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Feisty Duck Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.feistyduck.com
Email Address []:webmaster@feistyduck.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

 C、把csr文件發給權威機構,等待權威機構認證,交費獲取證書即可。

五、OpenSSL生成root CA及簽發證書

         有時候,使用SSL協議是自己內部服務器使用的,這時可以不必去找第三方權威的CA機構做證書,可以做自簽證書(自己創建root CA(非權威))主要有以下三個步驟。

A:創建openssl.cnf在使用default-ca時需要使用的SSL的工作目錄(第一次必須要設置)。

         1、查看openssl的配置文件:

openssl version -a
OpenSSL 1.0.1e-fips 17 Nov 2016
built on: Fri Nov 18 16:28:23 CST 2016
platform: linux-x86_64
options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) idea(int) blowfish(idx) 
compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -DTERMIO -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -Wa,--noexecstack -DPURIFY -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/etc/pki/tls"
engines:  rdrand dynamic 

         2、找到OPENSSLDIR: "/etc/pki/tls"的配置文件openssl.cnf

        根據配置文件下的[ CA_default ]節點默認值,創建對應文件夾和文件。

         按順序在/etc/pki/CA下執行以下命令創建文件夾和文件:

        

        其中,certs:存放已頒發的證書;newcerts:存放CA指令生成的新證書;private:存放私鑰;crl:存放已吊銷的整數;index.txt:penSSL定義的已簽發證書的文本數據庫文件,這個文件通常在初始化的時候是空的;serial:證書簽發時使用的序列號參考文件,該文件的序列號是以16進制格式進行存放的,該文件必須提供並且包含一個有效的序列號。

        執行完后,當前目錄為:

[tt@SWEBMYVMM000210 /etc/pki/CA]$ll
total 20
drwxrwxrwx 2 root root 4096 Mar 12 11:08 certs
drwxrwxrwx 2 root root 4096 Nov 18  2016 crl
-rwxrwxrwx 1 root root    0 Mar 12 11:06 index.txt
drwxrwxrwx 2 root root 4096 Nov 18  2016 newcerts
drwxrwxrwx 2 root root 4096 Nov 18  2016 private
-rwxrwxrwx 1 root root   33 Mar 12 11:12 serial
 小插曲:使用自簽證書簽名用戶證書時報錯,文件不存在

1、問題描述:

     openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key

     Using configuration from /etc/pki/tls/openssl.cnf

/etc/pki/CA/serial: No such file or directory
       error while loading serial number
       139996157081440:error:02001002:system library:fopen:No such file or directory:bss_file.c:398:fopen('/etc/pki/CA/serial','r')
       139996157081440:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:400:
 2、問題解決:
如果不設置工作目錄,后續第三步的最后一小步,使用openssl的ca命令產生用戶的ca證書時會報錯,創建openssl.cnf在使用default-ca時需要使用的SSL的工作目錄即可。

 B:生成CA根證書(root ca證書)。

         步驟:生成CA私鑰(.key)-->生成CA證書請求(.csr)-->自簽名得到根證書(.crt)(CA給自已頒發的證書)。

# Generate CA private key   --->ca.key

openssl genrsa -out ca.key 2048   

# Generate CSR   --->ca.csr 

openssl req -new -key ca.key -out ca.csr  

# Generate Self Signed certificate(CA 根證書)  ---> ca.crt  

openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt  

小插曲:直接根據key文件獲取CA根證書的命令

方法:在得到key文件后,執行以下命令:
openssl req -new -x509 -days 365 -key fd.key -out fd.crt
如果不想填寫那些注冊信息,執行以下命令:
openssl req -new -x509 -days 365 -key fd.key -out fd.crt subj "/C=GB/L=London/O=Feisty Duck Ltd/CN=www.feistyduck.com

 C:用自簽根證書 ca.crt  給用戶證書簽名。

          步驟:生成私鑰(.key)-->生成證書請求(.csr)-->用CA根證書簽名得到證書(.crt)

#  private key  --->server.key

openssl genrsa -out server.key 1024
# generate csr  --->server.csr

openssl req -new -key server.key -out server.csr
# generate certificate  --->server.crt 
openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key
小插曲:用CA根證書簽名時報錯,The mandatory stateOrProvinceName field was missing
1、問題描述: sudo openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key Using configuration from /etc/pki/tls/openssl.cnf Check that the request matches the signature Signature ok The mandatory stateOrProvinceName field was missing 2、出現原因:openssl.cnf中CA policy有三個match,必須要填一樣的,或者改成optional
3、解決方法:修改配置文件,修改后為:

  # For the CA policy
  [ policy_match ]
  countryName = optional
  stateOrProvinceName = optional
  organizationName = optional
  organizationalUnitName = optional
  commonName = supplied
  emailAddress = optional

D:證書的簡單使用。

      把server.crt以及server.key保存在服務器端等待程序加載使用;把ca.key保存在客戶端,如果客戶端需要驗證服務器端發的證書時使用。

六、遇到過的問題小結

問題1: 對服務器發送https請求失敗,連上服務器之后,服務器返回為空。

            需求:測試樁實現轉發功能,可以按需求處理數據,或者把數據轉發給真實的目標環境處理,實現透傳轉發數據功能,現需要向目標url:https://10.202.2.222:12336/preSvr轉發數據。

            測試后台服務器寫的測試樁,更多的是寫服務器端接收處理數據,因為現在樁需要實現透傳功能,所以樁是客戶端也是服務器端,於是引入了以前測試時使用的客戶端,代碼如下:

 
         
class HttpsClient(object):
def __init__(self, target_ip, target_port,pem_path):
self.ip = target_ip
self.port = target_port
self.pem_path = pem_path
self.ssl_content =self.get_pem(self.pem_path)

def get_pem(self,pem_path):
'''
:return: 獲取content_ssl
'''
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.set_default_verify_paths()
context.verify_mode = ssl.CERT_NONE
# context.load_verify_locations(self.pem_path)
context.load_verify_locations('C:/Users/Administrator/PycharmProjects/untitled/test/loleina/ca.pem')
return context

def https_send(self,data,timeout=30):
'''
'''


package = self.__socket_send_recive( self.ssl_content ,data)
return package
# return self.__parse_response(package)

def __socket_send_recive(self,ssl_content, data,timeout=30):
'''

:param request: 需要發送的內容
:param type: 請求類型,https type=1 http type=0
:param timeout: 請求超時時間
:return: 收到的響應內容
'''

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
connect_sock = ssl_content.wrap_socket(sock)
connect_sock.connect((self.ip,int(self.port)))
connect_sock.send(data)

BUFLEN = 1024
package = ''
while True:
buf = connect_sock.recv(BUFLEN)
package += buf
# chunked
if package.find('Transfer-Encoding: chunked') > 0:
#print 'chunked'
ls = package.strip().split('\r\n');
if ls[-1].strip() == '0':
break;
else:
head, body, blength = self.__analyze_package(package);
# 如果沒有分解出head部分,說明返回的不是標准的http協議
if head is None:
print 'header is None'
if len(buf) < BUFLEN:
break;
# 100-continue
elif head.find('Expect: 100-continue') > 0:
print 'Expect: 100-continue'
if len(body) >= blength:
package = package.replace('Expect: 100-continue\r\n', '');
break;
else:
if len(buf) < BUFLEN:
connect_sock.send('HTTP/1.1 100 Continue\r\n\r\n')
else:
pass;
else:
# 如果head中沒有content-length,
if blength == -1:
if len(buf) < BUFLEN:
break;
else:
if len(body) >= blength:
break;

return package

def __analyze_package(self, package):
'''

:param package: 收到的socket響應包
:return: 按照http協議解析出來的(head,body,body長度字段)
'''

idx = package.find('\r\n\r\n');
# 如果沒有發現分隔行,返回None
if idx < 0:
return (None, None, None);
head = package[:idx];
body = package[idx + 4:];
blength = -1;
# 分析body長度
for ln in head.split('\r\n'):
if ln.find('Content-Length') == 0:
blength = ln.replace('Content-Length:', '').strip();
break;
return (head, body, int(blength))
 

   調用后,就一直收不到真實環境的返回,抓包分析發現3次握手能建立,但是之后服務器端就斷開了與客戶端的連接。后面換了一個httplib庫, httplib.HTTPSConnection獲取httpClient后,調用 httpClient.request(method='POST', url=url.path, body=params, headers=headers)調用成功。對比了下2次調用的不同點,更多的不同在發包的時候request有寫出資源路徑/preSvr,而之前的沒寫。下面是關鍵代碼段:

url = urlparse(url_str)

        httpClient = httplib.HTTPSConnection(url.hostname + ':' + str(url.port))
        httpClient.host = url.hostname
        httpClient.port = url.port
        httpClient.key_file = ''
        httpClient.cert_file = ''

        sock = socket.create_connection((httpClient.host, httpClient.port))
        httpClient.sock = ssl.wrap_socket(sock, httpClient.key_file, httpClient.cert_file,ssl_version=ssl.PROTOCOL_TLSv1_2)
        httpClient.request(method='POST', url=url.path, body=params, headers=headers)
        response = httpClient.getresponse().read()

問題2:服務器端返回報文亂碼。雙方約定好的編碼格式就是utf-8,服務器端都正常返回了,客戶端收到的數據就是亂碼....

      當時嘗試了各種轉碼的方法一直都有問題,后面帶着問題百度查了下嘗試從其他角度來分析問題,后面果然問題是服務器端返回的數據是壓縮的,客戶端接收到數據后需要解壓。

compressedstream = StringIO.StringIO(returnData)
gziper = gzip.GzipFile(fileobj=compressedstream)
returnData = gziper.read()   

      而之所以返回壓縮格式是因為請求包的頭文件里有設置Accept-Encoding: deflate, gzip,這條信息代表本地可以接收壓縮格式的數據,而服務器在處理時就將大文件壓縮再發回客戶端。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM