我正在为嵌入式Linux设备添加HTTPS支持。我已尝试使用以下步骤生成自签名证书:

openssl req -new > cert.csr
openssl rsa -in privkey.pem -out key.pem
openssl x509 -in cert.csr -out cert.pem -req -signkey key.pem -days 1001
cat key.pem>>cert.pem

这是可行的,但我在Google Chrome上遇到了一些错误:

这可能不是你要找的网站!站点的安全证书不受信任!

我错过了什么吗?这是构建自签名证书的正确方法吗?


当前回答

这个非常简单的Python应用程序创建了一个自签名证书。代码:

from OpenSSL import crypto, SSL
from secrets import randbelow
print("Please know, if you make a mistake, you must restart the program.")
def cert_gen(
    emailAddress=input("Enter Email Address: "),
    commonName=input("Enter Common Name: "),
    countryName=input("Enter Country Name (2 characters): "),
    localityName=input("Enter Locality Name: "),
    stateOrProvinceName=input("Enter State of Province Name: "),
    organizationName=input("Enter Organization Name: "),
    organizationUnitName=input("Enter Organization Unit Name: "),
    serialNumber=randbelow(1000000),
    validityStartInSeconds=0,
    validityEndInSeconds=10*365*24*60*60,
    KEY_FILE = "private.key",
    CERT_FILE="selfsigned.crt"):
    #can look at generated file using openssl:
    #openssl x509 -inform pem -in selfsigned.crt -noout -text
    # create a key pair
    k = crypto.PKey()
    k.generate_key(crypto.TYPE_RSA, 4096)
    # create a self-signed cert
    cert = crypto.X509()
    cert.get_subject().C = countryName
    cert.get_subject().ST = stateOrProvinceName
    cert.get_subject().L = localityName
    cert.get_subject().O = organizationName
    cert.get_subject().OU = organizationUnitName
    cert.get_subject().CN = commonName
    cert.get_subject().emailAddress = emailAddress
    cert.set_serial_number(serialNumber)
    cert.gmtime_adj_notBefore(0)
    cert.gmtime_adj_notAfter(validityEndInSeconds)
    cert.set_issuer(cert.get_subject())
    cert.set_pubkey(k)
    cert.sign(k, 'sha512')
    with open(CERT_FILE, "wt") as f:
        f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8"))
    with open(KEY_FILE, "wt") as f:
        f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode("utf-8"))
    print("GENERATED")
    input("Press enter to close program.")
cert_gen()

但是,您仍然会收到“证书不受信任”错误。这是因为以下几个原因:

它是自签名/未验证的(验证的证书需要CA(证书颁发机构),如Let's Encrypt,以便在所有设备上都受信任)。您的计算机上不信任它。(此答案显示了如何使Windows信任您的证书)。

其他回答

我错过了什么吗?这是构建自签名证书的正确方法吗?

创建自签名证书很容易。您只需使用openssl-req命令。创建一个可供最多客户机使用的客户端(如浏览器和命令行工具)可能很棘手。

这很困难,因为浏览器有自己的一组要求,而且它们比IETF更具限制性。浏览器使用的要求记录在CA/浏览器论坛上(见下文参考资料)。限制出现在两个关键领域:(1)信任锚和(2)DNS名称。

现代浏览器(如我们在2014/2015年使用的warez)需要一个链接到信任锚的证书,他们希望DNS名称在证书中以特定方式显示。浏览器正在积极反对自签名服务器证书。

有些浏览器不太容易导入自签名服务器证书。事实上,你不能使用某些浏览器,比如Android的浏览器。因此,完整的解决方案是成为你自己的权威。

在没有成为自己的权威机构的情况下,您必须获得正确的DNS名称,以使证书获得最大的成功机会。但我鼓励你成为自己的权威。成为你自己的权威很容易,它会回避所有的信任问题(谁比你自己更值得信任?)。


这可能不是你要找的网站!站点的安全证书不受信任!

这是因为浏览器使用预定义的信任锚点列表来验证服务器证书。自签名证书不会链接回受信任的锚点。

避免这种情况的最佳方法是:

创建自己的权限(即成为CA)为服务器创建证书签名请求(CSR)使用CA密钥签署服务器的CSR在服务器上安装服务器证书在客户端上安装CA证书

第1步-创建您自己的授权仅意味着使用CA创建自签名证书:正确且正确的密钥使用。这意味着Subject和Issuer是同一实体,CA在基本约束中设置为true(也应标记为关键),密钥用法为keyCertSign和crlSign(如果您使用的是CRL),Subject key Identifier(SKI)与Authority key Identifier相同。

要成为自己的证书颁发机构,请参阅*如何与证书颁发机构签署证书签名请求?堆栈溢出。然后,将CA导入浏览器使用的信任存储。

步骤2-4大致上是您现在为面向公共的服务器注册像Startcom或CAcert这样的CA服务时所做的。第1步和第5步允许您避开第三方权威机构,充当自己的权威机构(谁比自己更值得信任?)。

避免浏览器警告的第二个最佳方法是信任服务器的证书。但有些浏览器,比如Android的默认浏览器,不允许你这样做,所以它永远不会在平台上运行。

浏览器(和其他类似的用户代理)不信任自签名证书的问题将是物联网(IoT)中的一个大问题。例如,当你连接到恒温器或冰箱来编程时,会发生什么?答案是,就用户体验而言,没有什么好东西。

W3C的WebAppSec工作组正在着手研究这个问题。例如,请参阅提案:将HTTP标记为非安全。


如何使用OpenSSL创建自签名证书

下面的命令和配置文件创建自签名证书(它还向您展示了如何创建签名请求)。它们在一个方面与其他答案不同:用于自签名证书的DNS名称是主题备用名称(SAN),而不是通用名称(CN)。

DNS名称通过配置文件以subjectAltName=@alternate_names行(无法通过命令行执行)放置在SAN中。然后在配置文件中有一个alternate_name部分(您应该根据自己的喜好进行调整):

[ alternate_names ]

DNS.1       = example.com
DNS.2       = www.example.com
DNS.3       = mail.example.com
DNS.4       = ftp.example.com

# Add these if you need them. But usually you don't want them or
#   need them in production. You may need them for development.
# DNS.5       = localhost
# DNS.6       = localhost.localdomain
# IP.1        = 127.0.0.1
# IP.2        = ::1

将DNS名称放在SAN中而不是CN中是很重要的,因为IETF和CA/浏览器论坛都指定了这种做法。它们还指定CN中的DNS名称已弃用(但并非禁止)。如果在CN中输入DNS名称,则必须根据CA/B策略将其包含在SAN中。因此,您不能避免使用主题备用名称。

如果您没有将DNS名称放在SAN中,则证书将无法在浏览器和其他用户代理下验证,这些用户代理遵循CA/浏览器论坛指南。

相关:浏览器遵循CA/浏览器论坛政策;而不是IETF策略。这也是使用OpenSSL创建的证书(通常遵循IETF)有时无法在浏览器下验证(浏览器遵循CA/B)的原因之一。它们是不同的标准,它们有不同的发布政策和不同的验证要求。


创建自签名证书(注意添加了-x509选项):

openssl req -config example-com.conf -new -x509 -sha256 -newkey rsa:2048 -nodes \
    -keyout example-com.key.pem -days 365 -out example-com.cert.pem

创建签名请求(请注意缺少-x509选项):

openssl req -config example-com.conf -new -sha256 -newkey rsa:2048 -nodes \
    -keyout example-com.key.pem -days 365 -out example-com.req.pem

打印自签名证书:

openssl x509 -in example-com.cert.pem -text -noout

打印签名请求:

openssl req -in example-com.req.pem -text -noout

配置文件(通过-config选项传递)

[ req ]
default_bits        = 2048
default_keyfile     = server-key.pem
distinguished_name  = subject
req_extensions      = req_ext
x509_extensions     = x509_ext
string_mask         = utf8only

# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description).
#   Its sort of a mashup. For example, RFC 4514 does not provide emailAddress.
[ subject ]
countryName         = Country Name (2 letter code)
countryName_default     = US

stateOrProvinceName     = State or Province Name (full name)
stateOrProvinceName_default = NY

localityName            = Locality Name (eg, city)
localityName_default        = New York

organizationName         = Organization Name (eg, company)
organizationName_default    = Example, LLC

# Use a friendly name here because it's presented to the user. The server's DNS
#   names are placed in Subject Alternate Names. Plus, DNS names here is deprecated
#   by both IETF and CA/Browser Forums. If you place a DNS name here, then you
#   must include the DNS name in the SAN too (otherwise, Chrome and others that
#   strictly follow the CA/Browser Baseline Requirements will fail).
commonName          = Common Name (e.g. server FQDN or YOUR name)
commonName_default      = Example Company

emailAddress            = Email Address
emailAddress_default        = test@example.com

# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ...
[ x509_ext ]

subjectKeyIdentifier        = hash
authorityKeyIdentifier    = keyid,issuer

# You only need digitalSignature below. *If* you don't allow
#   RSA Key transport (i.e., you use ephemeral cipher suites), then
#   omit keyEncipherment because that's key transport.
basicConstraints        = CA:FALSE
keyUsage            = digitalSignature, keyEncipherment
subjectAltName          = @alternate_names
nsComment           = "OpenSSL Generated Certificate"

# RFC 5280, Section 4.2.1.12 makes EKU optional
#   CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
#   In either case, you probably only need serverAuth.
# extendedKeyUsage    = serverAuth, clientAuth

# Section req_ext is used when generating a certificate signing request. I.e., openssl req ...
[ req_ext ]

subjectKeyIdentifier        = hash

basicConstraints        = CA:FALSE
keyUsage            = digitalSignature, keyEncipherment
subjectAltName          = @alternate_names
nsComment           = "OpenSSL Generated Certificate"

# RFC 5280, Section 4.2.1.12 makes EKU optional
#   CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused
#   In either case, you probably only need serverAuth.
# extendedKeyUsage    = serverAuth, clientAuth

[ alternate_names ]

DNS.1       = example.com
DNS.2       = www.example.com
DNS.3       = mail.example.com
DNS.4       = ftp.example.com

# Add these if you need them. But usually you don't want them or
#   need them in production. You may need them for development.
# DNS.5       = localhost
# DNS.6       = localhost.localdomain
# DNS.7       = 127.0.0.1

# IPv6 localhost
# DNS.8     = ::1

您可能需要为Chrome执行以下操作。否则,Chrome可能会投诉公用名无效(ERR_CERT_Common_Name_invalid)。我不确定SAN中的IP地址与本例中的CN之间的关系。

# IPv4 localhost
# IP.1       = 127.0.0.1

# IPv6 localhost
# IP.2     = ::1

关于X.509/PKIX证书中DNS名称的处理,还有其他规则。有关规则,请参阅以下文件:

RFC 5280,Internet X.509公钥基础结构证书和证书吊销列表(CRL)配置文件RFC 6125,在传输层安全性(TLS)环境中使用X.509(PKIX)证书在互联网公钥基础设施中表示和验证基于域的应用服务身份RFC 6797,附录A,HTTP严格传输安全(HSTS)RFC 7469,HTTP公钥锁定扩展CA/浏览器论坛基线要求CA/浏览器论坛扩展验证指南

列出了RFC 6797和RFC 7469,因为它们比其他RFC和CA/B文档更具限制性。RFC 6797和7469也不允许IP地址。

从2023年起,OpenSSL≥1.1.1,以下命令可满足您的所有需求,包括主题备用名称(SAN):

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -keyout example.key -out example.crt -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.net,IP:10.0.0.1"

在OpenSSL≤1.1.0的旧系统上,如Debian≤9或CentOS≤7,需要使用此命令的更长版本:

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -keyout example.key -out example.crt -extensions san -config \
  <(echo "[req]"; 
    echo distinguished_name=req; 
    echo "[san]"; 
    echo subjectAltName=DNS:example.com,DNS:www.example.net,IP:10.0.0.1
    ) \
  -subj "/CN=example.com"

任一命令都创建一个证书

对于(子)域example.com和www.example.net(SAN)有效,对于IP地址10.0.0.1(SAN)也是有效的,相对强劲(截至2023年)有效期为3650天(约10年)。

将生成以下文件:

私钥:example.key证书:example.crt

所有信息都在命令行中提供。没有让你烦恼的互动输入。没有你必须处理的配置文件。所有必要的步骤都由单个OpenSSL调用执行:从私钥生成到自签名证书。


备注#1:加密参数

由于证书是自签名的,需要用户手动接受,因此使用短到期或弱加密是没有意义的。

未来,您可能希望使用超过4096位的RSA密钥和比sha256更强的哈希算法,但截至2023年,这些都是正常的值。它们足够强大,同时受到所有现代浏览器的支持。

备注#2:参数“-nodes”

理论上,您可以省略-nodes参数(这意味着“没有DES加密”),在这种情况下,example.key将使用密码进行加密。然而,这对于服务器安装几乎从来都没有用处,因为您要么必须将密码也存储在服务器上,要么必须在每次重新启动时手动输入密码。

备注#3:另请参见

直接在命令行上向openssl提供subjectAltName如何通过命令行向SSL证书添加多个电子邮件地址?有关MSYS_NO_PATHCONV的更多信息

在经历了大量的尝试和各种解决方案之后,我仍然面临这样一个问题:为localhost颁发自签名证书会给我带来错误

错误证书无效

在扩展细节时,chrome表示:

您现在无法访问localhost,因为网站已发送加密凭据。。。

唯一难看的方式是键入(直接在这个屏幕上,没有看到文本的任何光标):

(在键盘上键入)这是安全的

这让我继续。

直到我找到extendedKeyUsage=serverAuth,clientAuth

TL;博士

openssl genrsa-out localhost.key 2048openssl req-key localhost.key-new-out localhost.csr(单击输入所有内容,然后用localhost或其他FQDN填写通用名称(CN)。将以下内容放入名为v3.ext的文件中(根据需要进行编辑):

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = CA:TRUE
keyUsage               = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
extendedKeyUsage       = serverAuth, clientAuth
subjectAltName         = DNS:localhost, DNS:localhost.localdomain
issuerAltName          = issuer:copy

openssl x509-req-in localhost.csr-signkey localhost.key-out localhost.pem-days 3650-sha256-extfile v3.ext

瞧!您可以访问网站,展开“高级”,然后单击“转到本地主机(不安全)”。

这个非常简单的Python应用程序创建了一个自签名证书。代码:

from OpenSSL import crypto, SSL
from secrets import randbelow
print("Please know, if you make a mistake, you must restart the program.")
def cert_gen(
    emailAddress=input("Enter Email Address: "),
    commonName=input("Enter Common Name: "),
    countryName=input("Enter Country Name (2 characters): "),
    localityName=input("Enter Locality Name: "),
    stateOrProvinceName=input("Enter State of Province Name: "),
    organizationName=input("Enter Organization Name: "),
    organizationUnitName=input("Enter Organization Unit Name: "),
    serialNumber=randbelow(1000000),
    validityStartInSeconds=0,
    validityEndInSeconds=10*365*24*60*60,
    KEY_FILE = "private.key",
    CERT_FILE="selfsigned.crt"):
    #can look at generated file using openssl:
    #openssl x509 -inform pem -in selfsigned.crt -noout -text
    # create a key pair
    k = crypto.PKey()
    k.generate_key(crypto.TYPE_RSA, 4096)
    # create a self-signed cert
    cert = crypto.X509()
    cert.get_subject().C = countryName
    cert.get_subject().ST = stateOrProvinceName
    cert.get_subject().L = localityName
    cert.get_subject().O = organizationName
    cert.get_subject().OU = organizationUnitName
    cert.get_subject().CN = commonName
    cert.get_subject().emailAddress = emailAddress
    cert.set_serial_number(serialNumber)
    cert.gmtime_adj_notBefore(0)
    cert.gmtime_adj_notAfter(validityEndInSeconds)
    cert.set_issuer(cert.get_subject())
    cert.set_pubkey(k)
    cert.sign(k, 'sha512')
    with open(CERT_FILE, "wt") as f:
        f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode("utf-8"))
    with open(KEY_FILE, "wt") as f:
        f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode("utf-8"))
    print("GENERATED")
    input("Press enter to close program.")
cert_gen()

但是,您仍然会收到“证书不受信任”错误。这是因为以下几个原因:

它是自签名/未验证的(验证的证书需要CA(证书颁发机构),如Let's Encrypt,以便在所有设备上都受信任)。您的计算机上不信任它。(此答案显示了如何使Windows信任您的证书)。

生成密钥我将/etc/mysql用于证书存储,因为/etc/appamor.d/usr.sbin.mysqld包含/etc/mysql/*.pem r。苏多苏-cd/etc/mysqlopenssl genrsa-out ca-key.pem 2048;openssl req-new-x509-nodes-days 1000-key ca-key.pem-out ca-cert.pem;openssl req-newkey rsa:2048-天1000-节点-keyout server-key.pem-out server-req.pem;openssl x509-req-in server-req.pem-天1000-CA CA-cert.pem-CA密钥CA-key.pem-set_serial 01-out server-cert.pum;openssl req-newkey rsa:2048-天1000-nodes-keyout client-key.pem-out client-req.pem;openssl x509-req-in client-req.pem-days 1000-CA CA-cert.pem-CA密钥CA-key.pem-set_serial 01-out client-cert.pum;添加配置/etc/mysql/my.cnf[客户]ssl-ca=/etc/mysql/ca-cert.pemssl证书=/etc/mysql/client-cert.pemssl密钥=/etc/mysql/client-key.pem[mysqld]ssl-ca=/etc/mysql/ca-cert.pemssl证书=/etc/mysql/server-cert.pemssl密钥=/etc/mysql/server-key.pem

在我的设置中,Ubuntu服务器登录到:/var/log/mysql/error.log

后续注意事项:

SSL错误:无法从“…”获取证书如果MySQL不在appmors配置中,则可能会拒绝对证书文件的读取访问。如前面步骤^中所述,将所有证书保存为/etc/mysql/目录中的.pem文件,appamor默认批准该目录(或修改appamor/SELinux以允许访问存储它们的任何位置)SSL错误:无法获取私钥您的MySQL服务器版本可能不支持默认的rsa:2048格式将生成的rsa:2048转换为纯rsa:openssl rsa-in server-key.pem-out server-key.phpopenssl rsa-in client-key.pem-out client-key.php检查本地服务器是否支持SSL:mysql-u根-pmysql>显示变量,如“%ssl%”;+---------------+----------------------------+|变量名称|值|+---------------+----------------------------+|have_openssl|是||have_ssl|是||ssl-ca|/etc/mysql/ca-cert.pem||ssl_容量|||ssl_cert |/etc/mysql/server-cert.pem||ssl_密码|||ssl-key |/etc/mysql/server-key.pem|+---------------+----------------------------+验证与数据库的连接是否经过SSL加密:正在验证连接登录到MySQL实例时,可以发出查询:显示状态,如“Ssl_cypher”;如果您的连接未加密,结果将为空:mysql>显示状态,如“Ssl_cypher”;+---------------+-------+|变量名称|值|+---------------+-------+|Ssl_密码||+---------------+-------+一组1行(0.00秒)否则,它将显示正在使用的密码的非零长度字符串:mysql>显示状态,如“Ssl_cypher”;+---------------+--------------------+|变量名称|值|+---------------+--------------------+|Ssl_密码|DHE-RSA-AES256-SHA|+---------------+--------------------+一组1行(0.00秒)特定用户的连接需要ssl(“需要ssl”):安全套接字层告诉服务器只允许帐户使用SSL加密连接。授予测试的所有特权。*至“root”@“本地主机”需要SSL;要进行连接,客户端必须指定--ssl ca选项来验证服务器证书,还可以指定--ssl密钥和--ssl证书选项。如果未指定--ssl ca选项或--ssl capath选项,则客户端不会对服务器证书进行身份验证。


备用链接:《用SSL保护PHP与MySQL的连接》中的冗长教程。