OpenSSL自签名证书

本文只讨论使用RSA算法的x509证书。本文在撰写时使用特定版本的openssl,但大部分情况下也应该适用于其它版本。

$ openssl version
OpenSSL 1.1.0f  25 May 2017

1 生成

生成自签名证书可分为使用基本步骤和快捷步骤。快捷步骤只是通过特殊选项合并基本步骤中的一步或两步。通过openssl生成的文件都是PEM格式,可直接用于apache或nginx。如使用IIS需要在生成后转换为pfx格式。

1.1 基本步骤

生成自签名证书的基本步骤如下:
– 生成私钥(Private Key)文件
– 生成证书(签名)请求文件CSR,需要使用私钥
– 生成自签名证书(Certificate)文件,需要使用证书请求文件

1.1.1 步骤1 生成RSA私钥(Private Key)

RSA私钥中也包含公钥。命令为openssl genrsa,格式:

openssl genrsa [<options>] <bit-length>
    [-des3 | -aes128 | ...]     #指定用于保护私钥的对称加密算法
        [-passout pass:<password> | -passout file:<path-name> | -passout env:<variable> | -passout fd:<file-descriptor> | -passout stdin]   #指定输出私钥的加密密码来源
    -out <private-key-file>     #输出私钥到文件,不指定则输出到stdout

举例,生成一个长度为2048位的RSA私钥(含公钥):

openssl genrsa -out ca.key 2048

举例,生成一个长度为2048位,并用aes128加密的私钥,密码为1234:

openssl genrsa -aes128 -passout pass:1234 -out ca-pass.key

1.1.2 步骤2 用私钥生成证书签名请求(Certificate Signing Request, CSR)

命令为openssl req,格式:

openssl req
    -new    #生成新的证书签名请求
    -key <private-key>  #在步骤1中生成的私钥
    [-passin pass:<password> | -passin file:<path-name> | -passin env:<variable> | -passin fd:<file-descriptor> | -passin stdin]    #指定所用私钥的密码
    -subj '/C=<country>/ST=<province>/L=<locality>/O=<organiation>/OU=<organiation-unit>/CN=<common-name>/emailAddress=<email>'
    -out <csr-file>     #输出证书请求到文件,不指定则输出到stdout

选项-subj指定证书所有人主体信息,如果证书用于网站,则其中的CN域必须指定为网站域名,其他域不是必填项。
举例,为www.mysite.com生成证书签名请求:

openssl req -new -key ca.key -subj '/C=CN/ST=ZheJiang/L=HangZhou/O=CompanyName/OU=DepartmentName/CN=www.mysite.com' -out mysite.csr

举例,为www.mysite.com生成证书签名请求,同时指定之前生成私钥时所用密码:

openssl req -new -key ca-pass.key -passin pass:1234 -subj '/C=CN/ST=ZheJiang/L=HangZhou/O=CompanyName/OU=DepartmentName/CN=www.mysite.com' -out mysite.csr

1.1.3 步骤3 生成自签名证书

步骤3有两种命令可以实现,分别是openssl req -x509和openssl x509 -req。有一点小区别是前者使用-key指定私钥,后者用-signkey指定私钥。

步骤3方法1 openssl req -x509

命令openssl req格式

openssl req
    -x509   #生成自签名证书,而不是证书请求
    -days <days>    #指定证书有效期天数
    -md2 | -md5 | -sha1 | -sha256 | -sha512     #签名所用哈希算法
    -key <private-key-file>
    -in <csr-file>
    -out <certificate-file>

举例,为证书请求文件签名并生成证书:

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

步骤3方法2 openssl x509 -req

命令openssl x509格式:

openssl x509
    -req    #输入内容为证书请求,而不是证书
    -days <days>    #指定证书有效期天数
    -md2 | -md5 | -sha1 | -sha256 | -sha512     #签名所用哈希算法
    -signkey <private-key-file>
    -in <csr-file>
    -out <certificate-file>     #输出证书到文件,不指定则输出到stdout

举例,为证书请求文件签名并生成证书:

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

此方法似乎不会读取/etc下的openssl.cnf的设置(不同发行版路径不同,使用find /etc/ -name openssl.cnf查找),如果在其中有设置subjectAltName不会起作用。

1.2 快捷步骤

可以合并基本步骤中的一步或两步,来减少命令输入量。快捷步骤总是以基本步骤2(openssl req)为基础,通过增加额外的选项来合并其它步骤。

1.2.1 基本步骤1,2一起做

如果还未生成私钥,可以在基本步骤2的命令openssl req的基础上,用-newkey代替-key,来指明生成证书请求的同时生成私钥。与私钥有关的基本步骤1中的选项便可以同时出现。

    -newkey <type:length>   #指定私钥类型及长度,如rsa:2048
    -keyout <private-key-file>
    -nodes  #不要为私钥使用对称密钥加密,相当于基本步骤1中不指定加密算法

举例,使用一条命令生成私钥和证书签名请求:

openssl req -new -newkey rsa:2048 -keyout ca.key -passout pass:1234 -subj '/C=CN/ST=ZheJiang/L=HangZhou/O=CompanyName/OU=DepartmentName/CN=www.mysite.com' -out mysite.csr

其中-passout来自于基本步骤1中的选项。

1.2.1 基本步骤2,3一起做

如果已经有了私钥,也可以合并基本步骤2,3,来直接生成证书,而跳过证书请求。在基本步骤2的命令openssl req的基础上,用-x509代替-new:

    -x509   #生成自签名证书,而不是证书请求

相当于把基本步骤2(openssl req)和基本步骤3方法1(openssl req -x509)合并起来。
举例,利用已有私钥,通过一条命令来生成自签名证书:

openssl req -new -x509 -key ca.key -subj '/C=CN/ST=ZheJiang/L=HangZhou/O=CompanyName/OU=DepartmentName/CN=www.mysite.com' -days 365 -sha256 -out mysite.crt

1.2.2 基本步骤1,2,3一起做

如果要同时生成私钥和自签名证书,也可以用一条命令完成,只要把合并步骤1,2及合并步骤2,3的选项放在一起便可。
举例,通过一条命令生成私钥和自签名证书

openssl req -x509 -newkey rsa:2048 -keyout ca.key -nodes -subj '/C=CN/ST=ZheJiang/L=HangZhou/O=CompanyName/OU=DepartmentName/CN=www.mysite.com' -days 365 -sha256 -out mysite.crt

2. 利用私钥和现有证书重新生成证书签名请求

可以通过基本步骤1产生的私钥和基本步骤3产生的证书来重新生成证书请求文件,而无须重新指明主体信息。
在使用自签名证书的情形下作用不大,但如果曾经把证书请求提供给第三方进行过签名,那么通过此方法可以快速重新生成证书请求文件,从而再次申请签名。

命令为openssl x509 -x509toreq,格式:

    -x509toreq  #将x509证书转换为证书请求
    -signkey <private-key-file>
    -in <certificate-file>
    -out <csr-file>     #输出证书请求到文件,不指定则输出到stdout

举例,通过私钥和证书,提取证书请求文件:

openssl x509 -x509toreq -signkey ca.key -in mysite.crt -out mysite.csr

3 查看信息

查看信息的共有选项如下,不再单独列出:

    -text   #解析原始内容,以可理解的方式输出信息
    -noout  #不输出原始内容
    [-passin pass:<password> | -passin file:<path-name> | -passin env:<variable> | -passin fd:<file-descriptor> | -passin stdin]
    -in <input-file>    #指定输入内容的来源
    -out <output-file>  #指定输出到文件,不指定则输出到stdout

3.1 查看私钥信息

使用openssl rsa命令,举例:

openssl rsa -text -noout -in ca.key

3.2 查看证书签名请求信息

使用openssl req命令,举例:

openssl req -text -noout -in mysite.csr

3.3 查看证书信息

使用openssl x509命令,举例:

openssl x509 -text -noout -in mysite.crt

4 MinGW/MSYS/git-bash for Windows下生成证书时报错:Subject does not start with ‘/’

解决方案与原因详见:https://stackoverflow.com/questions/31506158/running-openssl-from-a-bash-script-on-windows-subject-does-not-start-with

git忽略文件的集中途径

  • 配置core.excludesfile,该文件中记录了需要忽略的文件列表
  • ~/.config/git/ignore
  • repo库中的info/exclude
  • commit树中的.gitignore

更多详情可参考官方文档:
https://git-scm.com/docs/gitignore

git远程追踪分支的建立方法

本地分支不存在,创建追踪分支

git branch <localbranch> [--track] <remote>/<branch>

起点为远程分支时--track可省略。

本地分支不存在,创建追踪分支,同时切换到该分支

git checkout <branch> # 只有一个remote时,自动追踪remote同名分支

git checkout --track <remote>/<branch> # 使用远程分支名称作为本地分支名

git checkout -b <localbranch> [--track] <remote>/<branch> # 指定本地分支名称,起点为远程分支时--track可省略

本地分支已存在,建立或更新上游追踪

git branch -u <remote>/<branch> [<localbranch>]

省略localbranch时使用当前branch。

本地分支已存在,在推送的同时建立或更新追踪关系

git push -u <remote> <localbranch>:<remotebranch>

git push -u <remote> <branch>    #本地branch名称与远程相同

《深入理解ES6》中文翻译及编排bug

第3章 函数

元属性(Metaproperty)new.target (55页)
通常是新创建对象实例,也就是函数体内this的构造函数
应为
通常是构造函数,它用于创建对象实例,作为函数体内的this
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/03-Functions.md#the-newtarget-metaproperty
oshotokill翻译版本
https://github.com/OshotOkill/understandinges6-simplified-chinese/blob/master/chapter_3.md#元属性-newtargetthe-newtarget-metaproperty

箭头函数语法 第二小节(60页)
即使没有显式的返回语句
根据原文,应保留return关键字
即使没有显式的return语句
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/03-Functions.md#arrow-function-syntax
oshotokill翻译版本
https://github.com/OshotOkill/understandinges6-simplified-chinese/blob/master/chapter_3.md#箭头函数语法arrow-function-syntax

第5章 解构

引言
解构是一种打破数据解构
应为
解构是一种打破数据结构

对象解构 第二小节(90页)
也是用来从options对象读取相应值的属性名称
应为
也是用来从node对象读取相应值的属性名称
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/05-Destructuring.md#object-destructuring

默认值 最后一小节 (93页)
此处没有node.value属性,因为value使用了预设的默认值
应为
此处没有node.value属性,因此value使用了预设的默认值

第6章 Symbol

Symbol.match, Symbol.replace, Symbol.search 和 Symbol.split Symbols 属性
示例代码 [Symbol.split] 函数
return value.length === 10 ? [, ] : [value];
应为
return value.length === 10 ? [””, “”] : [value];
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/06-Symbols.md#the-symbolmatch-symbolreplace-symbolsearch-and-symbolsplit-symbols

Symbol.match, Symbol.replace, Symbol.search 和 Symbol.split Symbols 属性
示例代码

let replace1 = message1.replace(hasLengthOf10),
    replace2 = message2.replace(hasLengthOf10);

console.log(replace1);          // "Hello world"
console.log(replace2);          // "Hello John"

应为

let replace1 = message1.replace(hasLengthOf10, "Howdy!"),
    replace2 = message2.replace(hasLengthOf10, "Howdy!");

console.log(replace1);          // "Hello world"
console.log(replace2);          // "Howdy!"

原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/06-Symbols.md#the-symbolmatch-symbolreplace-symbolsearch-and-symbolsplit-symbols

第7章 Set集合与Map集合

Set集合的forEach()方法
forEach的第一个参数(134页)
Set集合中下一次索引的位置
应为
Set集合中下一个位置的
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/07-Sets-And-Maps.md#the-foreach-method-for-sets
142页Map集合的forEach()方法也存在同样的问题。

第8章 迭代器(Iterator)和生成器(Generator)

异步任务执行器
示例代码3之后 第一小节(179页)
如果没有错误产生,data被传入task.run()
应为
如果没有错误产生,data被传入task.next()
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/08-Iterators-And-Generators.md#asynchronous-task-runner

第9章 JavaScript中的类

为何使用类语法(184页)
中修改类名会导致程序报错
应为
类方法中修改类名会导致程序报错
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/09-Classes.md#why-to-use-the-class-syntax

为何使用类语法 最后一小节(185页)
尽管可以在不使用new语法的前提下实现类的所有功能
应为
尽管可以在不使用新语法的前提下实现类的所有功能

在类的构造函数中使用new.target
第二小节 (208页)
等价于Rectangle的nwe.target
应为
等价于Rectangle的new.target

第12章 代理(Proxy)和反射(Reflection)

原型代理陷阱 末小节(281页)
paroxy
应为
proxy

第13章 用模块封装代码

导出的基本语法 示例代码(317页)
export multiply;
应为
export { multiply };
原文
https://github.com/nzakas/understandinges6/blob/master/manuscript/13-Modules.md#basic-exporting

我的Atom设置

"*":
  core:
    automaticallyUpdate: false
    restorePreviousWindowsOnStart: "no"
    telemetryConsent: "no"
    openEmptyEditorOnStart: false
  editor:
    atomicSoftTabs: false
    fontSize: 14
    showInvisibles: true
    showIndentGuide: true
    softTabs: false
    tabLength: 4
    tabType: "hard"
  "line-ending-selector":
    defaultLineEnding: "LF"
  welcome:
    showOnStartup: false