内容简介:CA(Certificate Authority或Certification Authority,数字证书认证机构),是负责发放和管理数字证书的权威机构,承担公钥体系中公钥的合法性检验的责任。 CA中心为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。 CA机构的数字签名使得攻击者不能伪造和篡改证书。 为保证用户之间在网上传递信息的安全性、真实性、可靠性、完整性和不可抵赖性,不仅需要对用户的身份真实性进行验证, 也需要有一个具有权威性、公正性、唯一性
CA简介
CA(Certificate Authority或Certification Authority,数字证书认证机构),是负责发放和管理数字证书的权威机构,承担公钥体系中公钥的合法性检验的责任。 CA中心为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。 CA机构的数字签名使得攻击者不能伪造和篡改证书。 为保证用户之间在网上传递信息的安全性、真实性、可靠性、完整性和不可抵赖性,不仅需要对用户的身份真实性进行验证, 也需要有一个具有权威性、公正性、唯一性的机构,负责签发(也称『颁发』)并管理安全证书。
其实,每个人都可以当CA。
以下基于Ubuntu来讲如何利用 openssl
来建立一个本地的CA,在命令行签发数字证书,并对证书进行认证。
(其它 Linux 和Mac,操作基本相同。)
默认配置
$ locate openssl.cnf /etc/ssl/openssl.cnf /usr/lib/ssl/openssl.cnf
/etc/ssl/openssl.cnf
就是 openssl
的默认配置文件。
其中,比较重要的配置如下:
[ CA_default ] dir = ./demoCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem# The private key RANDFILE = $dir/private/.rand # private random number file
所以,默认情况下,需要在执行 openssl
的目录下准备一个 demoCA
,这样会比较方便。
下面用更简洁的方法来展示从签发根证书、到签发服务端证书的过程。
根证书
CA的根证书准备,只需要三步:
- 生成CA私钥
- 生成根证书签发申请文件
- 生成根证书
生成CA私钥
$ export CA_PWD=******** $ openssl genrsa -aes256 -passout pass:${CA_PWD} -out ca.key Generating RSA private key, 2048 bit long modulus ..................+++ ..+++ e is 65537 (0x10001) $ cat ca.key -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,E2F90E5145E2F31E3D1C62805588F8EB rSnW0uRYmbT+xcXI73jMoy8QgCJxZjhKSaqpmuvCsWBGAN1XZohHpyn2uJfhKl+f 4fHMUD6oLHsxtBejd310WZo4jrmN1mxVraAjWjuZLocWc3fA6sHMz/KVvyjQCKZx 5OwCzgzW7qzhLPfrce4UeinuBoUimY7+nXNFMK3mUMFaiFNM1jhsa9AUqxSGrpxz +ESLCd+eq+jq7noUc7OYL/pUaY830XoFMNTfoIxrwPVz5cVhKzINnLBhgmBH2jGF y4lKQzbVh9c9P02hnBG6RN/FYhl73UVJgjOPOkTCjoX4SivxrMUCOuL5D4ZoH/K5 D8TvjwfOW08c0Esg+R7bKHWkY7I+A/xFuAfPZPDsQKY0lDvBQDCgGTeR3uCXrIXt ZCy9Wb5GSfgNQb0rWLL7NDjteoW1Yf7jXpYRbph5LEWFMbc0I5tk4C6YmABxzIbX WGWVoD7pwzog8E93w3UzIQ/38U+KK0k0YT9XftVk/OTHPDHRPTLP3YL/QYFNxgHA QACNup1Ql8RN8hHT+Pm0B4q1AHpbNc9N+gX5r2Ts6V/DyAnoc3yZeXv1pa4XY7So PTwm/lOM4VPikIzaayJ29eh5CLEv4+EpjhwImI8bcpfWhPLL+K0v9SmW/5j1D8AL KGtdctNtXw7D+87kVtZr7NUvLC8izMEU6PxMd8MkEu6Chc1cRtsyfbfLbmTUC9hE OTQRiOW3U4gFkZVOr7cUmuV/t87MLtIm/JP7NN+ivOVojfSf4a0fKWYD7z2eLOIQ 7XxsfhAMhwOPB/KpaEK5UtKI9leXY9t4C1JO68t20TSbY36SgkBMi7MgdiiMo2KF e3omI95zx4jS3Zc1Vb8BeBfNIKReuzoqlwyNVBzl5V8GIAMfSMk6WZ6KMRxc77yI xac0p6lE/9aPnJ3W6XOsNw3ium63lkP8/F9OayOJVeH4VNWZc2RzGumB9mb/RriC tGnXXuzKhfrEnsiz7OsCbWxRB6BSTHy/+enzkT8WCMSVYFqFSWtPbFoCo2XwwQY4 ACtn0RD7c3G+WTcyQtPVoaMGXIioJjfM8mb0efHRoC+bjcLlNpaNe36NtIEhiEEC x421AQhHKV8XAjRU0qO0dCbsGwd9hlBny+gamBzsjsX/IPpJ6/z2lGl6QhF+XZS3 4zTyIkP6uhsx8W/hBWcZ1vI5i8PY3ZwiewiTQCiv0CNF/DxJQvDn/xkZxAHqx7DY WinPJ6huwbk5XU147onSjxcyDjKaMQY27BgbS4WbkFXO/Ha+KXupMXBvmbfBHURJ +EHByhkKexZZDT2Rtaxelh/1diYdUmeovocyx9IKnajuqZnPtw8jY+q6lYd2T92d ksW1Kk97UduW6Gdg2FTzDRiAvZGvzHw6aT1pDP6lQMUGyvCmZHBrQzmETMw68kwr IzHZxEtp//3bL1v45XnPoavGftz25AOVywcsILzlkr6T6tOxH7hVKdmoFTY3Mbrw TWHCfY1PqzwpMC7lAh/mXs8ZhP/XcnOnuxcOHvWd1ER1HVZcCvp/C7bgA0W+g6QD OFD06HWNmC0vQFqAKNN3VCjMjVoAc/uoXmELHNlFJ7VEYxfLNWqd0uRHLpY+isWY -----END RSA PRIVATE KEY-----
OpenSSL通常使用PEM(Privacy Enbanced Mail)格式来保存私钥。
这里命名为 ca.key
,而非 ca.pem
,只求更直观。
(这个私钥,和ssh-keygen产生的RSA私钥,其实差不多。)
命令参数解释如下,详见《 genrsa - OpenSSL 》:
-
genrsa
:使用RSA算法产生私钥。 -
-aes256
:使用256位密钥的AES算法对私钥进行加密。 -
-out
:输出文件的路径。 -
-passout
:指定密码。如果不指定,则需要在运行过程中输入。这里用
pass:${CA_PWD}
的方法,来指定密码。 其实,如果是环境变量,env:CA_PWD
是更简洁的形式,详见《 PASS PHRASE ARGUMENTS 》。
生成根证书签发申请文件
CSR(Cerificate Signing Request,证书请求文件)是用来提交给证书签发机构的文件。 用CA私钥给CSR文件签名后,就得到了根证书文件,也即公钥。
$ export validity=1461 # 4 years $ openssl req -new -days ${validity} -subj "/C=CN/CN=example/DC=note/DC=qidong/DC=name" -key ca.key -passin pass:${CA_PWD} -out ca.csr $ cat ca.csr -----BEGIN CERTIFICATE REQUEST----- MIICqDCCAZACAQAwYzELMAkGA1UEBhMCQ04xEDAOBgNVBAMMB2V4YW1wbGUxFDAS BgoJkiaJk/IsZAEZFgRub3RlMRYwFAYKCZImiZPyLGQBGRYGcWlkb25nMRQwEgYK CZImiZPyLGQBGRYEbmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ALjKishvKWYLSkd9K//nP1gymDxC7TTg/nDNr9ZyaU38jfJ480vJEiSNK8Um5YGx 68CDkGJKgWrytJhZbF3djXM2vV4m8DuZ+czwvqaQ2qrXVbZyYWlA3jDMBLxdHG1g PhMnkJT8cnE/OlBhYoE4nyR6s+wCQfLs/WvMWAQkgS0EORFTgfXX66cSuz/d5isN NN9N/E2B5toS/ywMgzHcLi312Fsk+z8q/kNbtXXGgtFkLQz2rwDvLyVMjx/HjZHV 1knXJb8M6RXoJSF8DNlEOTql21EWFfYto/tsT7OE7/1H+BS9XuoOYHf4FgZiRkyN YVFnEiNdqR4XA1g8yjy0CUECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBOKL7t iXL89x8ur7ObRx6VGX3zZFqnAEiE7HAMJbn3cJS7cyCrNlu5QKymnpMUiT3yJ+AL WSBSrucCVAqW3bjXBy2EpRQLFz/qTaIQOZYFVIkKjDyBlkNdr/DBAl6u5NRkAUkd uSX3DgdD2pNSHJIZIyrXyrdKcOO67OP4G0luK2qsniG6lCletqnvdqWLUYzXlfud e4SET/TCHDBFo6IrCaShigyVWLGxxnSB937o/JgCMefhW349NAcLUevhwf5Mbt5k ZuZ9wK6gQ5BYSrOVYdIMInbQyflPRt1T3TAp9R02l7QPBe0VIJhPcUQT+MQ4vQji 48g1y1m2CSEAAu1q -----END CERTIFICATE REQUEST-----
命令参数解释如下,详见《 req - OpenSSL 》:
req -new -key -passout -out -subj
这个 -subj
看似杂乱无章,其实暗藏玄机。
它的本质就是一组键值对,一般形式为 /type0=value0/type1=value1/type2=...
。
但也并非毫无意义,一般采用 RFC2253
规范来指定一组信息。
String | X.500 AttributeType |
---|---|
CN | commonName |
L | localityName |
ST | stateOrProvinceName |
O | organizationName |
OU | organizationalUnitName |
C | countryName |
STREET | streetAddress |
DC | domainComponent |
UID | userid |
上面的 /C=CN/CN=example/DC=note/DC=qidong/DC=name
,涵义可以对照得出,就是中国 note.qidong.name
的一个叫 example
的组件。
其中,省市街道这些信息略过没写。
生成根证书
$ openssl x509 -req -days ${validity} -sha1 -extensions v3_ca -signkey ca.key -passin pass:${CA_PWD} -in ca.csr -out ca.cer Signature ok subject=/C=CN/CN=example/DC=note/DC=qidong/DC=name Getting Private key $ cat ca.cer -----BEGIN CERTIFICATE----- MIIDQjCCAioCCQCGY66stRpanDANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJD TjEQMA4GA1UEAwwHZXhhbXBsZTEUMBIGCgmSJomT8ixkARkWBG5vdGUxFjAUBgoJ kiaJk/IsZAEZFgZxaWRvbmcxFDASBgoJkiaJk/IsZAEZFgRuYW1lMB4XDTE5MDEw MzE0MTMwMFoXDTIzMDEwMzE0MTMwMFowYzELMAkGA1UEBhMCQ04xEDAOBgNVBAMM B2V4YW1wbGUxFDASBgoJkiaJk/IsZAEZFgRub3RlMRYwFAYKCZImiZPyLGQBGRYG cWlkb25nMRQwEgYKCZImiZPyLGQBGRYEbmFtZTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBALjKishvKWYLSkd9K//nP1gymDxC7TTg/nDNr9ZyaU38jfJ4 80vJEiSNK8Um5YGx68CDkGJKgWrytJhZbF3djXM2vV4m8DuZ+czwvqaQ2qrXVbZy YWlA3jDMBLxdHG1gPhMnkJT8cnE/OlBhYoE4nyR6s+wCQfLs/WvMWAQkgS0EORFT gfXX66cSuz/d5isNNN9N/E2B5toS/ywMgzHcLi312Fsk+z8q/kNbtXXGgtFkLQz2 rwDvLyVMjx/HjZHV1knXJb8M6RXoJSF8DNlEOTql21EWFfYto/tsT7OE7/1H+BS9 XuoOYHf4FgZiRkyNYVFnEiNdqR4XA1g8yjy0CUECAwEAATANBgkqhkiG9w0BAQUF AAOCAQEAes70Mi8r/hALHVb0y1R4zRG0y2KEUCk51wt0vjWkJ1CrxYFOlEFhk//n gGIlClJAn6nvXTvSMLoFzsXBjgkdqmKkqMUg32YFGhH+V1BsNBSjGGF89je/vaIw 3oGwnHsqkW8k0QNfgUM6ANrBfYkWAqJoMpok8JAmH9xslAFqEQtVs1PmOQAaGhg8 3dbw3U3iTSJ4TzB7swgCuFDJvn/bMLF2l8/vdIJrWIBFSOAJh5YupFqoLSYutjwp mTbcJdoa1kYDYqqs4obQr8dOBYtv0F7udroepP+PkKxOEm0ucJByn9MypQEYxS7H b5UjWd4rIFV+SBz9Yd5Apz8KYFpLpA== -----END CERTIFICATE-----
命令参数解释如下,详见《 x509 - OpenSSL 》:
x509 -req -days -sha1 -extensions -signkey -in -out
签发证书
生成服务端私钥
$ export USER_PWD=******** $ openssl genrsa -aes256 -passout pass:${USER_PWD} -out server.key Generating RSA private key, 2048 bit long modulus ...................................................................+++ .......................+++ e is 65537 (0x10001) $ cat server.key -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,FB9145090BCC8CA2FEAB0939E40CA8DC 0mXI/ggnQ49E01XJNwLRC6ZbvhWJ8A7RAiiwQAomBcjCyxprjj+e6NFJPZcpvtJD 80q+7ZqWaTNbHfy+7A4oaZbuIBDK0RjxIET6aFBnWgYDBZPTXZ2kXafeXPJ4gpCG ZXm0u1EqmmGTQcwHq6n/hdwwc9JFvOo1iPPn6/SIqKmZYgfFT9ueRW6EhhrsNpbt JjfSUdHrWnSbzgZah6Eus/LKGNDnIQ3T9FVvwXoAQwKgD+VkWQIXooFf1zz7QE+d jPeCSgYWkJOD9am/GrGxaKuZPlfDgKoz1hDChzMnsZvUZIDjHq4PN7NXBxd1m0Q9 tz952LE396zRZZM8FRH5/JmckJelGIsrwG/lXFk/i0KImpn/7Tb1BK76RIMthFdv Fr1BGPqYMoNXb85Kg0hxVCULNpckUPG3M5Qo7bSRILyCxX2KpFKQLTH7Z3u31P9P 3m4V+hvgOFScCDWuEGmBEpINnnll139ZatInevaVC49oGGMUK+1Zhxw0707UqvvQ XEWcdVlUZjbxVGhTCOLt7GCSSg1wumUOwSSbshu/WL1tmRCcwM/ZXUZjJTo2jnpy cDnyMBYsSx4vLLgh2K1w6qqC1kWB2QRmnMvZ1SGSJdtevzfjMTNyUa+8wVp9k2xF GbbauwMb4pQ2K53ux5Uzl+n1FYJvcImbLLzed5fUU2HOOSAPUIYH0a3VncN8H5Mf HF4JquD4l32XLmFHS1d05oQTHeVfb8K94o1V7Z1mfgDqBIHjfj1jR/aCn+lGNwkC a7rf1XJ2jCJk4gYktuGvlVL8jcD2srjUaXfEJh5Xhci5uL4Hp4CmqHi9r2BcnzTX 9b8gipjyAzebCqu7cQyVkDKTV/oezhfTMzzG7Ichd/iWYgtNrMPJI+CCvLE9Az2V /dJzXdRTZIBrApK/mUnCqwC2qVm73zNdjUR2Xce7IY7vfEWUmerlLKGz5+mlgTyI 2o3gl+5bUII74mYMVkaoOiNCpAssXNmmBetpCdbCU/ij0kEBNBfMbjgdOJ1w2f1M SI0R5+CeXtxeDtYDhRQJiufvDiAJl8HySgPLHrJWpAU2W7FY8yF8ix8HWnXRUFYw IJwEBRY6X7QNgGYt4POivZocmdTp2p2+4z41SnTvemamR3qU/nx1toTnHZ19dRo9 hDDge7jzMrfjPe0ZlMiOUk8PVtScIY+h7U8CFSnnO4+I5MA8MNBHtbwL+/u6ss+M sFr7N2KGBPVWXZtOkZkEZwWHxWIzZjZobXosaeQxgDfM7riObW8QiCfChqB5zigB /G0oLf6qpVrwXP9fszGWmpXvRq81VbL66LFU+aLCIyICkVsp9Z7mJZ5H1M8Ki0CH Z9NtZVI8fo2hyKe769wpf9hdWQeBSrm4iNn62RPR4UZpP/Kcm9kuiqV9DxKrNs1v 89CVL5DvH2JfziRk/OxlT2pzGttPNCTHkwAJkwpn3tVQZMR/uJMNgdEtvg4x4RRb s85+WZCiETYF7d5p7GF6E8jnRnYwPsYdPAo7yCv83uNxBleiaqDFPKXSaJks6Z/v id3n+bnWWJIq6HY4v9XAdXIle5UwU4NCvLZjwm0zxcc7d84f43l8kiGeEsH6R3LC -----END RSA PRIVATE KEY-----
这个 server.key
和前面的根证书私钥 ca.key
,没有本质上的不同,只是所属不同。
生成证书请求文件
$ openssl req -new -days ${validity} -subj "/C=CN/CN=example/DC=note/DC=qidong/DC=name" -key server.key -passin pass:${USER_PWD} -out server.csr $ cat server.csr -----BEGIN CERTIFICATE REQUEST----- MIICqDCCAZACAQAwYzELMAkGA1UEBhMCQ04xEDAOBgNVBAMMB2V4YW1wbGUxFDAS BgoJkiaJk/IsZAEZFgRub3RlMRYwFAYKCZImiZPyLGQBGRYGcWlkb25nMRQwEgYK CZImiZPyLGQBGRYEbmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANDdgvdDxcTRG/3CugKkBf55OJ/Znd/9OCDtHnTdtGv96dXybWg92lCvP2Pdm2ZV UAiTl41t2MyXeq1BvWOSHlpD7+DJhctCAdgqF1ovU3dBanTK5cB8S8uLgAn7FHTz Tjifuop3Cb5awPCI8PLDaiIi0tRzsFfXpmMizHrGBczhKODSYQpoHrWE3p20S+Rr zpjLk6LwtfnbIVp7zPiS11tgKpqc1H3X+/npeK/9LsrOfm7rXkyOGhIhUYUARdjC x6SYCJuf5QBad0WRV3wlFuDHsfMJGVXyVS2HxIEBPp+8fKSqePPyHG4AUsnyxjPL HC+J1RhyAEKRh/fZt5/r3XcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAxq5G1 0WB0XZf0cQv5JMuniV2W+wjma9mnHwCEAFgDY4dfuPWdsexSgg6AOKUcDZmu4hTG qAJGKE8A9/OGN4U2rtwn6anwiksAA5kaRQICoTKECGT+iQbrYFoTWxB8N6mRanQL 17b7Pam21IoHAmhk6y0lrZOFE2BD1AQ+mo5aH1Nw9J4DmAqGxivAI9uC0nhAjR4h kmlFDOkjYhRdrIYqbQwO3GYzjNYxqNlWivWbvEo7Zu0DuRCGho8mlhj5cOtYkQfb 0qoMzuKJg2n6BgSEiQbiEw7oej3x4+HoD/yNaB3jSW7fnl/Um3tFtd3fL/lH0SOr SIAe5n8tSVqY+pF+ -----END CERTIFICATE REQUEST-----
同样,这里也是用的 req
子命令。
需要注意的是,在 -subj
中会指定证书目标的用户名与IP(或域名)。
使用根证书签发服务端证书
$ echo 00 > ca.srl $ openssl x509 -req -days $validity -sha1 -extensions v3_ca -CAkey ca.key -passin pass:${CA_PWD} -CA ca.cer -CAserial ca.srl -in server.csr -out server.cer Signature ok subject=/C=CN/CN=example/DC=note/DC=qidong/DC=name Getting CA Private Key $ cat server.cer -----BEGIN CERTIFICATE----- MIIDOjCCAiICAQEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCQ04xEDAOBgNV BAMMB2V4YW1wbGUxFDASBgoJkiaJk/IsZAEZFgRub3RlMRYwFAYKCZImiZPyLGQB GRYGcWlkb25nMRQwEgYKCZImiZPyLGQBGRYEbmFtZTAeFw0xOTAxMDMxNDE3MDRa Fw0yMzAxMDMxNDE3MDRaMGMxCzAJBgNVBAYTAkNOMRAwDgYDVQQDDAdleGFtcGxl MRQwEgYKCZImiZPyLGQBGRYEbm90ZTEWMBQGCgmSJomT8ixkARkWBnFpZG9uZzEU MBIGCgmSJomT8ixkARkWBG5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQDQ3YL3Q8XE0Rv9wroCpAX+eTif2Z3f/Tgg7R503bRr/enV8m1oPdpQrz9j 3ZtmVVAIk5eNbdjMl3qtQb1jkh5aQ+/gyYXLQgHYKhdaL1N3QWp0yuXAfEvLi4AJ +xR08044n7qKdwm+WsDwiPDyw2oiItLUc7BX16ZjIsx6xgXM4Sjg0mEKaB61hN6d tEvka86Yy5Oi8LX52yFae8z4ktdbYCqanNR91/v56Xiv/S7Kzn5u615MjhoSIVGF AEXYwsekmAibn+UAWndFkVd8JRbgx7HzCRlV8lUth8SBAT6fvHykqnjz8hxuAFLJ 8sYzyxwvidUYcgBCkYf32bef6913AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAFNe DJQJb1KI9ncf/FlSuyFckyNHbqWoO8eVTCMm4X0n8EsaEojt6jw/s/ZjY2vJeq9N 3P9xWy/9TJp5N2yeaZeYeldJsyrlsAR+T2nXQSfsZy7hZ4ihH0DRgvdjNlnOzrnq ddlMu4zPI0EQoIe6gyVIFFmWx0L409XwfhErVeY5W7Do3idRR4d0bSi1NpyeAQkU 7JJuJrCIGLHIocuSknlfAuh24evWtVnYOU0RkV1XkaaloIM5HHmtfo+5kPP0cXWe 3eDhn9yCowxoe4UkVDRl2J4cu6Yrui/Y7tfvS5blua2yZ047VojHwIAG9rX15BhY g6ldcR4S6mPQ5y23Zcc= -----END CERTIFICATE-----
认证刚签发的证书
$ openssl verify -CAfile ca.cer server.cer server.cer: OK
参考《 verify - OpenSSL 》。
结语
以上展示了建立CA、签发证书并认证的完整过程。 虽然没有过多地介绍原理,但只要跟着手工执行一遍,比看完一篇原理详解的介绍文,还要更有实感。 不过,过程中省略了证书链、证书吊销等内容,并不完整。
刚才签发的证书,其实用途不大。
个人的CA,需要把根证书 ca.cer
交给用户,安装到系统或浏览器之类的客户端中,才能真正的起作用。
否则,在严格的证书验证策略下(这一般是默认策略),网络交互会失败。
参考
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML5 Programming
Eric Freeman、Elisabeth Robson / O'Reilly Media / 2011-10-18 / USD 49.99
What can HTML5 do for you? If you're a web developer looking to use this new version of HTML, you might be wondering how much has really changed. Head First HTML5 Programming introduces the key featur......一起来看看 《Head First HTML5 Programming》 这本书的介绍吧!