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

《JavaScript面向对象精要》中文翻译及编排bug

第1章

引言
开发者选择模仿……
应为
一些开发者选择模仿……

1.4.2节 定义数组的字面形式
用逗号区分的任意数量的值
应为
用逗号分隔的任意数量的值

第2章

2.4节 重载 第一小节
JavaScript语言根据实际传入的参数决定调用函数的哪个版本
应为
该类语言(指上文提到的其他面向对象语言)根据实际传入的参数决定调用函数的哪个版本

第3章

3.8节 总结 末小节
Object.prevent Extensions()
应为
Object.preventExtensions()

第4章

4.1节 第4、第8处示例代码
instanceofPerson
应为
instanceof Person

第6章

6.1.2节 代码示例2 getAge方法
returnage;
应为
return age;