SSL系列二之快速签发免费ZeroSSL泛域名证书

引入

现如今,向像腾讯,阿里云等大厂的免费SSL证书,都由一年缩短为3个月。

https://cloud.tencent.com/document/product/400/104538

在如今的形式下,签发免费的3个月的单域名证书显然不如直接签发Let’s Encrypt、ZeroSSL等ACME提供的泛域名证书来的方便。

而acme.sh常用于Linux端上证书的自动化签发与续约,对于不使用服务器的人群,显然造成了许多麻烦,那么可以通过Web端直接签发这些ACME的泛域名证书吗?

Github开源项目ACME-Web-Browser-Client告诉了我们这并非是一件不可能实现的事情。

简介:

本网页客户端(仅一个静态HTML网页文件)用于:向 Let’s Encrypt、ZeroSSL、Google 等支持 ACME 协议的证书颁发机构,免费申请获得用于 HTTPS 的 SSL/TLS 域名证书(RSA、ECC/ECDSA),支持多域名和通配符泛域名;只需在现代浏览器上操作即可获得 PEM 格式纯文本的域名证书,不依赖操作系统环境(Windows、macOS都能用),无需下载和安装软件,无需注册登录,纯手动操作,只专注于申请获得证书这一件事,简单易用,非常适用于希望手动快捷申请获得证书的使用场景。

Github开源地址:https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client

Gitee开源地址:

https://gitee.com/xiangyuecn/ACME-HTML-Web-Browser-Client

使用

提示:可将其私有化部署到Vercel,Netlify等上,可弥补Github Pages速度的不足。

任意点击下列一个网页使用:

https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html (作者Github Pages)

https://ssl.dev.inkdust.top/ (本人Vercel)

签发ZeroSSL

本文为签发ZeroSSL篇教程,签发Let’s Encrypt或Google,请看Let’s Encrypt篇和Google篇。

步骤一:注册ZeroSSL

来到https://zerossl.com/

注册ZeroSSL,请使用魔法网络环境,如无法使用魔法网络环境,请使用Header Eidtor 来将 reCAPTCHA的地址重写到国内镜像。

Header Eidtor谷歌商店:https://chromewebstore.google.com/detail/header-editor/eningockdidmgiojffjmkdblpjocbhgh?hl=zh-CN&utm_source=ext_sidebar

Header Eidtor Edge商店:https://microsoftedge.microsoft.com/addons/detail/header-editor/djbcdihpmcbpkljpjibeiedjenilallo

Header Eidtor Crx搜搜:https://www.crxsoso.com/webstore/detail/eningockdidmgiojffjmkdblpjocbhgh
https://cdnn.boochi.cn/gh/inkdust-dev/ink-oss@main/qexo-upload/24/8/image_38fa68a496e6a63cd50b294b0776d192.png

重写规则:

1
2
3
4
5
6
7
8
9
10
11
名称:reCAPTCHA

匹配类型:正则表达式

规则类型:重定向规则

匹配规则:^http(s?)://(?:www\.|recaptcha\.|)google\.com/recaptcha/(.*)

执行类型:常规

重定向至:https://recaptcha.net/recaptcha/$2

导入TXT文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"request": [],
"sendHeader": [],
"receiveHeader": [
{
"enable": false,
"name": "Content Security Policy Header Modification",
"ruleType": "modifyReceiveHeader",
"matchType": "all",
"pattern": "",
"exclude": "",
"isFunction": true,
"code": "let rt = detail.type;\nif (rt === 'script' || rt === 'stylesheet' || rt === 'main_frame' || rt === 'sub_frame') {\n for (let i in val) {\n if (val[i].name.toLowerCase() === 'content-security-policy') {\n let s = val[i].value;\n s = s.replace(/googleapis\\.com/g, '$& https://gapis.geekzu.org');\n s = s.replace(/recaptcha\\.google\\.com/g, '$& https://recaptcha.net');\n s = s.replace(/google\\.com/g, '$& https://recaptcha.net');\n s = s.replace(/gstatic\\.com/g, '$& https://*.gstatic.cn');\n val[i].value = s;\n }\n }\n}",
"group": "未分组"
}
],
"receiveBody": []
}

https://cdnn.boochi.cn/gh/inkdust-dev/ink-oss@main/qexo-upload/24/8/image_18ab541cb20291a0419ffeb09a1c83bf.png

注册,并登录ZeroSSL。点击Developers,EAB Credentials for ACME Clients下方的Generate,保存好生成的EAB KIDEAB HMAC Key
https://cdnn.boochi.cn/gh/inkdust-dev/ink-oss@main/qexo-upload/24/8/image_d8176d5822392d6870b8b1f38becddd3.png

任意点击下列一个网页打开签发SSL:

https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html (作者Github Pages)

https://ssl.dev.inkdust.top/ (本人Vercel)

步骤二:选择证书颁发机构

在“证书颁发机构 ACME(v2, RFC 8555) 服务URL”下选择ZeroSSL,点击 读取服务目录并等待出现:

测试此ACME服务对浏览器的支持情况,发生错误:GetNonce:** 此ACME服务对浏览器访问支持太差,无法跨域获取Replay-Nonce响应头。

关闭改网页,直接打开https://acme.zerossl.com/v2/DV90/directory,按`F12`键打开开发者控制台,点击`控制台`Console,输入以下代码(代码安全开源):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
// 请复制本代码到打开的ACME服务URL页面的浏览器控制台中运行。

var Default_ACME_URL="https://acme.zerossl.com/v2/DV90/directory";
var IsReadDirGotoCORS=true;
var PageRawHTML=`<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="shortcut icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAe1BMVEUDuu3///8AtuwAue1c0/QAt+1a0vSZ4/gOve4Ju+4AtOzf9v31/P4iw/DS8/yf5fnE7/tNzvMsxvHw+/7t+v7k+P2U4veO4PeA3Pbp+f7h9/3Y9f3I8Pxt1/Vi1PRQz/NDzPIzyPEWv+76/v/3/f+87fqn5/mI3/Z82vb+fQDoAAAA10lEQVQ4y63T2c7CIBCGYWamUwFZurfuy7/e/xVaeySlYmJ8T78nISQgPh0xU2pGZa1EfrazHJz5an2FT3brYKoul4UqAHx5Hhqoq6VTMAc4aER9acDjEviFPd2voP9gKykGVECHkzyBsRzvI+g1jWG5BFDIPXRKjqkcTKUwPISPbvsN9XpqA9DuehXsF4g64APALAY+AHkMVm+CzToNVld1NAmwE0zaJ0DBQughAcxZ66tLAGj6f/f2NdMgewXKGHSPgOTPfG/Dh0vqlAXlluefahaLT3UDvrAKrj7jLKkAAAAASUVORK5CYII=">

<title>ACME Web Browser Client | ACME客户端H5网页单文件版,在线免费申请签发SSL/TLS通配符泛域名HTTPS证书,支持Let's Encrypt、ZeroSSL,无需账号免登录注册 | Windows macOS Get Wildcard Certificates Online For Free - Single HTML File</title>
</head>

<body>
<script>
var Version="1.0.230820";
console.log("LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client/blob/main/LICENSE");
/***********************************
中英对照翻译主要来自:Chrome自带翻译+百度翻译,由中文翻译成English(作者英文很菜)。
The Chinese-English translation is mainly from: Chrome comes with translation + Baidu translation, which is translated from Chinese to English (the author's English level is very low)

感谢围观本客户端源码,所有功能的代码都在本页面一个文件内,逻辑上会比较长和比较丑(关键地方或多或少有写注释),如感不适,请转头看看旁边的大长腿,再深呼吸一下继续看下一行~
Thank you for watching the source code of this client. The code of all functions is in one file on this page, which is logically long and ugly (more or less comments are written in key places). If you feel uncomfortable, please turn your head and look at the Big Long Legs next to you, take another deep breath and continue to look at the next line~
************************************/
</script>


<div class="main-load" style="padding-top:40vh;text-align:center;font-size:28px">Loading...</div>
<div class="main" style="display:none">
<div class="mainBox acmeReadDirGotoCORSState" style="display:none"></div>

<div class="mainBox">
<span style="font-size:32px;font-weight:bold;color:#03baed;">
<span class="langCN clientNameCN">HTML5网页版ACME客户端</span>
<span class="langEN clientNameEN">ACME Web Browser Client</span>
</span>
<span class="versionBox" style="font-size:14px;color:#03baed;margin-right:80px"></span>
<span style="display:inline-block">
<span class="langCN">开源代码:</span>
<span class="langEN">Open source: </span>
<a href="https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client" target="_blank">GitHub &gt;&gt;</a>
<span class="langCN">
| <a href="https://gitee.com/xiangyuecn/ACME-HTML-Web-Browser-Client" target="_blank">Gitee &gt;&gt;</a>
</span>
</span>

<ul class="itemBox feature_ul" style="list-style-type: none;margin:8px 0 0 0;padding:0 8px 0 8px;color:#666">
<li class="langCN"><i>功能用途</i>本网页客户端用于:向 <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a>、<a href="https://zerossl.com/" target="_blank">ZeroSSL</a>、<a href="https://pki.goog/" target="_blank">Google</a> 等支持 ACME 协议的证书颁发机构,免费申请获得用于 HTTPS 的 SSL/TLS 域名证书(RSA、ECC/ECDSA),支持多域名和通配符泛域名;只需在现代浏览器上操作即可获得 PEM 格式纯文本的域名证书,不依赖操作系统环境,无需下载和安装软件,纯手动操作,<span class="Bold">只专注于申请获得证书这一件事。</span></li>
<li class="langEN"><i>Functional use</i>This web client is used to: apply for free SSL/TLS domain name certificates (RSA, ECC/ECDSA) for HTTPS from <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> , <a href="https://zerossl.com/" target="_blank">ZeroSSL</a> , <a href="https://pki.goog/" target="_blank">Google</a> and other certificate authorities that support the ACME protocol, and support multiple domain names and wildcard pan-domain names; Simply operate on a modern browser to obtain a domain name certificate in plain text in PEM format, does not depend on the operating system environment, does not need to download and install software, and is purely manual, <span class="Bold">only focus on the only thing that is to apply for and obtain a certificate.</span></li>

<li class="langCN"><i>简单易用</i>点点鼠标 Ctrl+C Ctrl+V 就能完成证书的申请,全程需要的操作少,每一步都有保姆级操作提示,UI友好大气美观;<span class="Bold">本客户端不需要注册账号、更不需要登录。</span></li>
<li class="langEN"><i>Easy to use</i>Click the mouse and Ctrl+C Ctrl+V to complete the certificate application. The whole process requires less operations, and there are nanny level operation prompts at each step; UI friendly, atmospheric and beautiful; <span class="Bold">This client does not need to register an account, and does not need to log in.</span></li>

<li class="langCN"><i>开源项目</i>本网页客户端源码已开源,访问网址由托管仓库提供,源码透明可追溯。</li>
<li class="langEN"><i>Open source project</i>The source code of the client side of this webpage has been open sourced, and the access URL is provided by the hosting warehouse, and the source code is transparent and traceable.</li>

<li class="langCN"><i>单一文件</i>本网页客户端仅一个静态 HTML 文件,不依赖其他任何文件;因此可以直接保存到你本地(右键-另存为),即可通过浏览器打开。</li>
<li class="langEN"><i>A single file</i>This web client is only a single static HTML web page file and does not depend on any other files; therefore, it can be directly saved to your local (right-click - save as), and you can open it through a browser.</li>

<li class="langCN"><i>数据安全</i>除了你指定证书颁发机构的 ACME 接口地址外,本网页客户端不会向其他任何地址发送数据,通过浏览器控制台很容易做到网络数据审查。</li>
<li class="langEN"><i>Data security</i>Except for the ACME interface address of the certificate authority you specify, this web client will not send data to any other address, and it is easy to check the network data through the browser console.</li>

<li class="langCN"><i>系统安全</i>纯网页应用,不会也无法对你的电脑系统做出任何修改。</li>
<li class="langEN"><i>System security</i>Pure web application, will not and cannot make any modification to your computer system.</li>

<li class="langCN" style="color:#cb1d1d"><i style="background:#cb1d1d">证书过期风险提醒</i>由于本网页客户端只能纯手动操作,不支持自动续期,需注意在证书过期前重新生申请新证书(免费证书普遍90天有效期,届时只需重复操作一遍即可),或使用 acme.sh 等客户端自动化续期。</li>
<li class="langEN" style="color:#cb1d1d"><i style="background:#cb1d1d">Certificate Expiration Risk Alert</i>Since this web client can only be operated manually and does not support automatic renewal, you should pay attention to apply for a new certificate before the certificate expires (free certificates are generally valid for 90 days, you only need to repeat the operation at that time), or use acme.sh and other client automatic renewal.</li>
</ul>
<style>
.feature_ul li{margin: 8px 0;}
.feature_ul i{
font-style: normal;
margin-right:8px;
display: inline-block;
vertical-align: middle;
background: #03baed;
color: #fff;
font-size: 14px;
padding: 2px 8px;
border-radius: 99px;
}
</style>
</div>





<div class="mainBox">
<div class="pd itemTitle">
<span class="langCN">步骤一:选择证书颁发机构</span>
<span class="langEN">Step 1: Select a Certificate Authority</span>
</div>
<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">证书颁发机构 ACME(v2, <a href="https://www.rfc-editor.org/rfc/rfc8555.html" target="_blank">RFC 8555</a>) 服务URL:</span>
<span class="langEN">Certificate Authority ACME (v2, <a href="https://www.rfc-editor.org/rfc/rfc8555.html" target="_blank">RFC 8555</a>) Service URL:</span>
</div>
<div class="pd">
<label><input type="radio" name="choice_acmeURL" value="https://acme-v02.api.letsencrypt.org/directory" desckey="descLetsEncrypt">Let's Encrypt</label>
<label><input type="radio" name="choice_acmeURL" value="https://acme.zerossl.com/v2/DV90/directory" desckey="descZeroSSL">ZeroSSL</label>
<label><input type="radio" name="choice_acmeURL" value="https://dv.acme-v02.api.pki.goog/directory" desckey="descGoogle">Google</label>
<label>
<input type="radio" name="choice_acmeURL" value="manual">
<span class="langCN">手动填写URL</span>
<span class="langEN">Fill in the URL manually</span>
</label>
<label>
<input type="radio" name="choice_acmeURL" value="https://acme-staging-v02.api.letsencrypt.org/directory">
<span class="langCN">测试用[不要选]</span>
<span class="langEN">For testing, don't choose</span>
</label>
</div>

<div style="font-size:13px;color:#aaa">
<div class="pd descAcmeURL descLetsEncrypt" style="display:none">
<a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a>:
<span class="langCN">请按照下面的操作步骤提示进行申请即可得到证书,证书有效期90天。</span>
<span class="langEN">Please follow the operation steps prompts below to apply, and you can get the certificate, which is valid for 90 days.</span>
</div>
<div class="pd descAcmeURL descZeroSSL" style="display:none">
<a href="https://zerossl.com/" target="_blank">ZeroSSL</a>:
<span style="color:#f80">
<span class="langCN">此URL可能需要先根据下面的提示进行操作来消除跨域不能访问的问题。</span>
<span class="langEN">This URL may need to be operated according to the prompts below to eliminate the problem of cross-domain inaccessibility.</span>
</span>
<span class="langCN">申请证书前,你需要根据ZeroSSL的<a href="https://zerossl.com/documentation/acme/" target="_blank">官方文档</a>,先注册ZeroSSL账号并生成一个EAB凭据,每次申请证书时使用此EAB凭据,按照下面的操作步骤提示进行申请即可得到证书,证书有效期90天。</span>
<span class="langEN">Before applying for a certificate, you need to follow ZeroSSL's <a href="https://zerossl.com/documentation/acme/" target="_blank">official documents</a>, register a ZeroSSL account and generate an EAB credential, and use this EAB credential every time you apply for a certificate, follow the operation steps prompts below to apply, and you can get the certificate, which is valid for 90 days.</span>
</div>
<div class="pd descAcmeURL descGoogle" style="display:none">
<a href="https://pki.goog/" target="_blank">Google Trust Services</a>:
<span style="color:#f80">
<span class="langCN">此URL可能需要先根据下面的提示进行操作来消除跨域不能访问的问题。</span>
<span class="langEN">This URL may need to be operated according to the prompts below to eliminate the problem of cross-domain inaccessibility.</span>
</span>
<span class="langCN">申请证书前,你需要根据Google的<a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank">官方文档</a>,在Google Cloud中生成一个EAB凭据,每次申请证书时使用此EAB凭据,按照下面的操作步骤提示进行申请即可得到证书,证书有效期90天。</span>
<span class="langEN">Before applying for a certificate, you need to follow Google's <a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank">official documents</a>, generate an EAB credential in Google Cloud, and use this EAB credential every time you apply for a certificate, follow the operation steps prompts below to apply, and you can get the certificate, which is valid for 90 days.</span>
<span style="color:#f80">
<span class="langCN">注意:因为同一个Google EAB凭据只能绑定到一个ACME账户(私钥),因此你在首次申请证书时,<span style="font-weight:bold;font-size:20px">必须同时保存好在第二步操作中新创建的或手动填写的ACME账户私钥</span>,下次申请证书时使用此EAB凭据必须和已保存的ACME账户私钥一起使用。</span>
<span class="langEN">Note: Because the same Google EAB credential can only be bound to one ACME account (Private key), when you apply for a certificate for the first time, <span style="font-weight:bold;font-size:20px">you must also save the newly generated or manually filled ACME account private key in the second step</span>, this EAB credential must be used together with the saved ACME account private key when applying for a certificate next time.</span>
</span>
</div>
</div>

<div class="pd FlexBox">
<div class="FlexItem">
<input class="in_acmeURL inputLang" style="width:100%" placeholder-cn="请填写证书颁发机构ACME服务URL" placeholder-en="Please fill in the Certificate Authority ACME Service URL">
</div>
<div style="padding-left:12px">
<span class="mainBtn mainBtnMin" onclick="acmeReadDirClick()" style="padding:0 50px">
<span class="langCN">读取服务目录</span>
<span class="langEN">Read service directory</span>
</span>
</div>
</div>
<div class="acmeReadDirState"></div>
<script>
//跨域支持的不好的ACME服务,直接复制源码到他们网站里面运行
var acmeReadDirGotoCORSInit=function(){
if(!window.IsReadDirGotoCORS)return;
var stateEl=$(".acmeReadDirGotoCORSState").show().html(\`
<div style="color:#cb1d1d">
<span class="langCN">本客户端正在以跨域兼容模式运行,请按正常流程操作即可,目标ACME服务URL=$\{window.Default_ACME_URL}</span>
<span class="langEN">This client is running in cross-domain compatibility mode, please follow the normal process, the target ACME service URL=$\{window.Default_ACME_URL}</span>
</div>
\`);
LangReview(stateEl);
};
var acmeReadDirGotoCORS=function(title){
"use strict";
var codes="// "+Lang("请复制本代码到打开的ACME服务URL页面的浏览器控制台中运行。","Please copy this code to the browser console of the opened ACME service URL page to run.",true)
+"\\n\\nvar Default_ACME_URL="+JSON.stringify(ACME.URL)+";"
+"\\nvar IsReadDirGotoCORS=true;"
+"\\nvar PageRawHTML=\`"
+PageRawHTML.replace(/\\\\/g,"\\\\\\\\").replace(/\`/g,"\\\\\`").replace(/\\$\\{/g,"$\\\\{")
+"\`;";
codes+="\\n("+(function(){
console.clear();
document.head.innerHTML=/<head[^>]*>([\\S\\s]+?)<\\/head>/i.exec(PageRawHTML)[1];
document.body.innerHTML=/<body[^>]*>([\\S\\s]+)<\\/body>/i.exec(PageRawHTML)[1];
var js=/<script[^>]*>([\\S\\s]+?)<\\/script>/ig,m;
while(m=js.exec(PageRawHTML)) eval.call(window, m[1]);
}).toString()+")()";
$(".gotoCORSBox").hide();
var stateEl=$(".acmeReadDirState").append(\`
<div class="gotoCORSBox" style="padding-top:15px">
<div class="pd Bold" style="color:red">
<i class="must">*</i>
\`+(title||\`
<span class="langCN">由于此ACME服务对跨域访问支持不良,</span>
<span class="langEN">Because this ACME service has poor support for cross-domain access, </span>
<span style="font-size:24px">
<span class="langCN">请按下面步骤操作:</span>
<span class="langEN">please follow the steps below:</span>
</span>\`)+\`
</div>
<div style="padding-left:40px">
<div class="pd">
<span class="langCN">1. 请在浏览器中直接打开此ACME服务URL,<a href="$\{ACME.URL}" target="_blank">点此打开</a>;</span>
<span class="langEN">1. Please open the ACME service URL directly in the browser, <a href="$\{ACME.URL}" target="_blank">click here to open</a>;</span>
</div>
<div class="pd">
<span class="langCN">2. 在上一步打开的页面中打开浏览器控制台(需等页面加载完成后,再按F12键);</span>
<span class="langEN">2. Open the browser console in the page opened in the previous step (after the page is loaded, press the F12 key);</span>
</div>
<div class="pd">
<span class="langCN">3. 复制以下代码,在第2步打开的浏览器控制台中运行,然后就可以正常申请证书了。</span>
<span class="langEN">3. Copy the following code, run it in the browser console opened in step 2, and then you can apply for the certificate normally.</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">工作原理:代码内包含了本页面源码,在目标页面内运行后将原样的显示出本客户端,然后按正常流程操作即可,此时已没有跨域问题了(既然打不过,那就加入他们)。</span>
<span class="langEN">Working principle: The code contains the source code of this page. After running in the target page, the client will be displayed as it is, and then operate according to the normal process. At this time, there is no cross-domain problem (if we can't beat them, we'd better join them).</span>
</div>
</div>
<div style="padding-top:20px">
<textarea class="gotoCORSText" style="width:100%;height:200px" readonly></textarea>
</div>
</div>
\`);
$(".gotoCORSText").val(codes);
LangReview(stateEl);
};
</script>
</div>
</div>





<div class="mainBox">
<div class="pd itemTitle">
<span class="langCN">步骤二:证书配置</span>
<span class="langEN">Step 2: Certificate Configuration</span>
</div>
<div class="step2Hide step1Show">
<div class="itemBox" style="color:#999">
<span class="langCN">等待中,请先完成第一步...</span>
<span class="langEN">Waiting, please complete step 1 first...</span>
</div>
</div>
<div class="step1Hide step2Show">


<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">温馨提示:如果上次申请过证书,可以拖拽已下载保存的记录LOG文件到本页面,将自动填充上次的配置信息。</span>
<span class="langEN">Reminder: If you have applied for a certificate last time, you can drag and drop the downloaded and saved record LOG file to this page, and the last configuration information will be automatically filled in.</span>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">证书中要包含的域名:</span>
<span class="langEN">Domain name to be included in the certificate:</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">一个证书可以包含多个域名(支持通配符),比如填写:<i class="i">a.com, *.a.com, b.com, *.b.com</i>;第一个域名将作为证书的通用名称(Common Name);带通配符的域名只支持DNS验证,其他域名支持上传文件验证;注意:填了<i class="i">www.a.com</i>时,一般需要额外填上<i class="i">a.com</i>。</span>
<span class="langEN">A certificate can contain multiple domain names (wildcard are supported), for example, fill in: <i class="i">a.com, *.a.com, b.com, *.b.com</i>; the first domain name will be used as the Common Name of the certificate; Domain names with wildcard only support DNS verification, and other domain names support upload file verification ; Note: When <i class="i">www.a.com</i> is filled in, it is generally necessary to fill in <i class="i">a.com</i> additionally.</span>
</div>
<div class="FlexBox">
<div class="FlexItem">
<input class="in_domains inputLang" style="width:100%" placeholder-cn="请填写你的域名,多个用逗号隔开" placeholder-en="Please fill in your domain name, multiple separated by commas">
</div>
<div style="padding-left:15px;line-height:30px;font-size:13px;color:#aaa">
<label>
<input type="checkbox" class="choice_domains_store">
<span class="langCN">记住</span>
<span class="langEN">Remember</span>
</label>
</div>
</div>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">证书的私钥:</span>
<span class="langEN">Private key of certificate:</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">生成或填写的私钥仅用于ACME接口签名,支持<i class="i">RSA(2048位+)</i>、<i class="i">ECC(<span class="eccCurveNames"></span>曲线)</i>私钥;<span style="color:#f80">注意:证书私钥的类型决定了申请到的证书是RSA证书还是ECC(ECDSA)证书,RSA类型适用性更广也更常见</span>;本客户端不会对此私钥进行保存或发送给其他任何人;证书签发后在部署到服务器时,需使用到此私钥;建议每次申请证书时均生成新的证书私钥。</span>
<span class="langEN">The generated or filled private key is only used for ACME interface signature, and supports <i class="i">RSA (2048-bit+)</i> and <i class="i">ECC (<span class="eccCurveNames"></span> curve)</i> private keys; <span style="color:#f80">Note: The type of certificate private key determines whether the applied certificate is an RSA certificate or a ECC(ECDSA) certificate, RSA type is more widely applicable and more common;</span> this client will not save or send this private key to anyone else; this private key needs to be used when deploying to the server after the certificate is issued; it is recommended to generate a new certificate private key every time you apply for a certificate.</span>
</div>
<div class="pd">
<label>
<input type="radio" name="choice_privateKey" value="generateRSA">
<span class="langCN">创建新RSA私钥</span>
<span class="langEN">Generate RSA private key</span>
</label>
<label>
<input type="radio" name="choice_privateKey" value="generateECC">
<span class="langCN">创建新ECC私钥</span>
<span class="langEN">Generate ECC private key</span>
</label>
<label>
<input type="radio" name="choice_privateKey" value="manual">
<span class="langCN">手动填写私钥</span>
<span class="langEN">Manually fill in the private key</span>
</label>
</div>
<div class="privateKeyBox">
<textarea class="in_privateKey inputLang" style="width:100%;height:60px" placeholder-cn="请填写pem私钥,私钥文本以 -----BEGIN PRIVATE KEY----- 开头(这是PKCS#8格式,里面带有 RSA|EC 字符的PKCS#1格式也是支持的)" placeholder-en="Please fill in the pem private key. The private key text starts with -----BEGIN PRIVATE KEY----- (this is in PKCS#8 format, and PKCS#1 format with RSA|EC characters in it is also supported)"></textarea>
</div>
<div class="privateKeyState"></div>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">ACME账户的私钥:</span>
<span class="langEN">Private key of ACME account:</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">生成或填写的私钥仅用于ACME接口签名,支持<i class="i">RSA(2048位+)</i>、<i class="i">ECC(<span class="eccCurveNames"></span>曲线)</i>私钥;账户私钥类型对证书无影响;本客户端不会对此私钥进行保存或发送给其他任何人;一个私钥相当于一个账户,可用于吊销已签发的证书;建议每次申请证书时使用相同的一个私钥(这样短期内多次申请证书时,验证域名所有权的参数极有可能会保持相同),不过每次都生成一个新的私钥大部分情况下也不会有问题。</span>
<span class="langEN">The generated or filled private key is only used for ACME interface signature, and supports <i class="i">RSA (2048-bit+)</i> and <i class="i">ECC (<span class="eccCurveNames"></span> curve)</i> private keys; the account private key type has no effect on the certificate; this client will not save or send this private key to anyone else; A private key is equivalent to an account and can be used to revoke an issued certificate; it is recommended to use the same private key every time you apply for a certificate (in this way, the parameters used to verify the domain name ownership are likely to remain identical when multiple certificate applications are made in a short period of time); However, generating a new private key every time will not be a problem in most cases.</span>
<span class="eabShow" style="color:#f80">
<span class="langCN">注意:如果你选择的ACME服务(比如Google)要求提供EAB凭据并且限制了同一个EAB凭据只能绑定到一个ACME账户(私钥),那每次使用此EAB凭据时必须使用相同的一个私钥(首次时如果新创建了私钥,此新私钥需立即保存起来下次和此EAB凭据一起使用)。</span>
<span class="langEN">Note: If the ACME service you choose (such as Google) requires EAB credentials and limits the same EAB credentials to only one ACME account (private key), then you must use the same private key every time you use this EAB credential (if you generate a new private key for the first time, this new private key needs to be saved immediately and used with this EAB credential next time).</span>
</span>
</div>
<div class="pd">
<label>
<input type="radio" name="choice_accountKey" value="generateRSA">
<span class="langCN">创建新RSA私钥</span>
<span class="langEN">Generate RSA private key</span>
</label>
<label>
<input type="radio" name="choice_accountKey" value="generateECC">
<span class="langCN">创建新ECC私钥</span>
<span class="langEN">Generate ECC private key</span>
</label>
<label>
<input type="radio" name="choice_accountKey" value="manual">
<span class="langCN">手动填写私钥</span>
<span class="langEN">Manually fill in the private key</span>
</label>
</div>
<div class="accountKeyBox">
<textarea class="in_accountKey inputLang" style="width:100%;height:60px" placeholder-cn="请填写pem私钥,私钥文本以 -----BEGIN PRIVATE KEY----- 开头(这是PKCS#8格式,里面带有 RSA|EC 字符的PKCS#1格式也是支持的)" placeholder-en="Please fill in the pem private key. The private key text starts with -----BEGIN PRIVATE KEY----- (this is in PKCS#8 format, and PKCS#1 format with RSA|EC characters in it is also supported)"></textarea>
</div>
<div class="accountKeyState"></div>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">ACME账户的联系邮箱:</span>
<span class="langEN">Contact email of ACME account:</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">此邮箱地址用于证书颁发机构给你发送邮件,比如:证书过期前的续期通知提醒。</span>
<span class="langEN">This email address is used by the certificate authority to send you emails, such as a reminder of renewal notice before the certificate expires.</span>
</div>
<div class="FlexBox">
<div class="FlexItem">
<input class="in_email inputLang" style="width:100%" placeholder-cn="请填写一个你的邮箱" placeholder-en="Please fill in one of your email addresses">
</div>
<div style="padding-left:15px;line-height:30px;font-size:13px;color:#aaa">
<label>
<input type="checkbox" class="choice_email_store">
<span class="langCN">记住</span>
<span class="langEN">Remember</span>
</label>
</div>
</div>
</div>


<div class="itemBox eabShow">
<div class="pd Bold">
<span class="langCN">EAB凭据:</span>
<span class="langEN">EAB Credentials:</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">当前ACME服务要求提供外部账号绑定凭据(External Account Binding),比如ZeroSSL:你可以在ZeroSSL的管理控制台的 Developer 中获得此凭据,所以你需要先注册一个ZeroSSL的账号。</span>
<span class="langEN">The current ACME service requires external account binding credentials, such as ZeroSSL: You can obtain this credentials in the Developer of the ZeroSSL management console, so you need to register a ZeroSSL account first.</span>
</div>
<div class="FlexBox" style="line-height:30px">
<div><i class="must">*</i>EAB KID:</div>
<div class="FlexItem" style="padding:0 50px 0 6px">
<input class="in_eab_kid inputLang" style="width:100%" placeholder-cn="请填写EAB KID" placeholder-en="Please fill in EAB KID">
</div>
<div style="padding:0 6px 0 0"><i class="must">*</i>HMAC KEY:</div>
<div class="FlexItem">
<input class="in_eab_key inputLang" style="width:100%" placeholder-cn="请填写EAB HMAC KEY" placeholder-en="Please fill in EAB HMAC KEY">
</div>
</div>
</div>

<div class="pd termsAgreeBox">
<label>
<input type="checkbox" class="choice_termsAgree">
<span class="termsAgreeTips"></span>
</label>
</div>

<div class="Center" style="padding:15px 0 10px">
<span class="mainBtn" onclick="configStepClick()" style="width:300px">
<span class="langCN">确定</span>
<span class="langEN">OK</span>
</span>
</div>
<div class="configStepState"></div>

</div>
</div>






<div class="mainBox">
<div class="pd itemTitle">
<span class="langCN">步骤三:验证域名所有权</span>
<span class="langEN">Step 3: Verify Domain Ownership</span>
</div>
<div class="step3Hide step2Show step1Show">
<div class="itemBox" style="color:#999">
<span class="langCN">等待中,请先完成第二步...</span>
<span class="langEN">Waiting, please complete step 2 first...</span>
</div>
</div>
<div class="step1Hide step2Hide step3Show">

<div class="pd" style="font-size:13px;color:#f80">
<span class="langCN">请给每个域名选择一个你合适的验证方式(推荐采用DNS验证,比较简单和通用),然后根据显示的提示完成对应的配置操作。</span>
<span class="langEN">Please select a suitable verification method for each domain name (DNS Verify is recommended, which is relatively simple and common), and then complete the corresponding configuration operations according to the displayed prompts.</span>
</div>

<div class="verifyBox"></div>
<script>
//显示所有域名的验证界面,html太多
var verifyBoxShow=function(){
"use strict";
var boxEl=$(".verifyBox").html("");
var auths=JSON.parse(JSON.stringify(ACME.StepData.auths));//避免改动原始数据
var domains=ACME.StepData.config.domains;
for(var i0=0;i0<domains.length;i0++){
var domain=domains[i0],auth=auths[domain]

var challs=auth.challenges;
for(var i=0;i<challs.length;i++){//排序,dns排前面
var o=challs[i];
o.challIdx=i;
o.name=ACME.ChallName(o);
o._sort=ACME.ChallSort(o);
}
challs.sort(function(a,b){return a._sort.localeCompare(b._sort)});
var choiceHtml="";
for(var i=0;i<challs.length;i++){
var chall=challs[i];
choiceHtml+=\`
<label>
<input type="radio" name="choice_authItem_$\{i0}"
class="choice_authChall choice_authChall_$\{i0} choice_authChall_$\{i0}_$\{i}"
value="$\{i0}_$\{i}" challidx="$\{chall.challIdx}">$\{chall.name}
</label>
\`;
}

boxEl.append(\`
<div class="itemBox">
<div class="pd FlexBox" style="line-height:26px">
<div><i class="must">*</i></div>
<div style="width:180px;padding-right:5px;text-align:right;background:#03baed;color:#fff;border-radius: 4px;">$\{domain}</div>
<div class="FlexItem" style="padding-left:10px;">$\{choiceHtml}</div>
</div>
<div class="verifyItemBox_$\{i0}"></div>
<div class="verifyItemState_$\{i0}"></div>
</div>
\`);
};
LangReview(boxEl);

$(".choice_authChall").bind("click",function(e){
var el=e.target,vals=el.value.split("_"),i0=+vals[0],i2=+vals[1];
var domain=domains[i0],auth=auths[domain],chall=auth.challenges[i2];
var html=['<div class="pd" style="padding-left:10px;font-size:13px;color:#aaa">'];
var nameCss='width:195px;text-align:right;padding-right:10px';
if(chall.type=="dns-01"){
html.push(Lang('请到你的域名DNS解析管理中,给下面这个子域名增加一条<i class="i">TXT记录</i>(一个子域名可以同时存在多条TXT记录,可以修改或删除老的记录)。','Please go to the DNS resolution management of your domain name and add a <i class="i">TXT record</i> for the following subdomain name (a subdomain name can have multiple TXT records at the same time, and the old records can be modified or deleted).')+'</div>');
html.push(\`<div class="pd FlexBox">
<div style="$\{nameCss}">$\{Lang('子域名','Sub Domain')}:</div>
<div class="FlexItem">
<input style="width:100%" readonly value="_acme-challenge.$\{auth.identifier.value}" />
</div>
</div>
<div class="pd FlexBox">
<div style="$\{nameCss}">$\{Lang('TXT记录','TXT Record')}:</div>
<div class="FlexItem">
<input style="width:100%" readonly value="$\{chall.authTxtSHA256}" />
</div>
</div>\`);
}else if(chall.type=="http-01"){
html.push(Lang('请在你的网站根目录中创建<i class="i">/.well-known/acme-challenge/</i>目录,目录内创建<i class="i">'+FormatText(chall.token)+'</i>文件,文件内保存下面的文件内容,保存好后<a href="http://'+auth.identifier.value+'/.well-known/acme-challenge/'+FormatText(chall.token)+'" target="_blank">请点此打开此文件URL</a>测试能否正常访问;注意:这个文件URL必须是80端口,并且公网可以访问,否则ACME无法访问到此地址将会验证失败。Windows操作提示:Windows中用<i class="i">.well-known.</i>作为文件夹名称就能创建<i class="i">.well-known</i>文件夹;IIS可能需在此文件夹下的MIME类型中给 <i class="i">.</i> (扩展名就是一个字".")添加 <i class="i">text/plain</i> 才能正常访问。','Please create the <i class="i">/.well-known/acme-challenge/</i> directory in the root directory of your website, create the <i class="i">'+FormatText(chall.token)+'</i> file in the directory, and save the following file content in the file; After saving, <a href="http://'+auth.identifier.value+'/.well-known/acme-challenge/'+FormatText(chall.token)+'" target="_blank">please click here to open the URL</a> of this file to test whether Normal access; note: the URL of this file must be 80 The port and the public network can be accessed, otherwise ACME cannot access this address and the verification will fail. Windows operation tips: In Windows, you can create a <i class="i">.well-known</i> folder by using <i class="i">.well-known.</i> as the folder name; IIS may need to give <i class="i">.</i> in the MIME type under this folder (the extension is a word ".") Add <i class="i">text/plain</i> for normal access.')+'</div>');
html.push(\`<div class="pd FlexBox">
<div style="$\{nameCss}">$\{Lang('文件URL','File URL')}:</div>
<div class="FlexItem">
<input style="width:100%" readonly value="http://$\{auth.identifier.value}/.well-known/acme-challenge/$\{FormatText(chall.token)}" />
</div>
</div>
<div class="pd FlexBox">
<div style="$\{nameCss}">$\{Lang('文件内容','File Content')}:</div>
<div class="FlexItem">
<input style="width:100%" readonly value="$\{chall.authTxt}" />
</div>
</div>\`);
}else{
html.push(Lang('非预定义验证类型,请使用<i class="i">Key Authorizations (Token+.+指纹)</i>自行处理,<i class="i">Digest</i>为Key Authorizations的SHA-256 Base64值。','For non-predefined authentication types, please use <i class="i">Key Authorizations (Token+.+Thumbprint)</i> to handle it yourself. <i class="i">Digest</i> is the SHA-256 Base64 value of Key Authorizations.')+'</div>');
html.push(\`<div class="pd FlexBox">
<div style="$\{nameCss}">Key Authorizations:</div>
<div class="FlexItem">
<input style="width:100%" readonly value="$\{chall.authTxt}" />
</div>
</div>
<div class="pd FlexBox">
<div style="$\{nameCss}">Digest(SHA-256 Base64):</div>
<div class="FlexItem">
<input style="width:100%" readonly value="$\{chall.authTxtSHA256Base64}" />
</div>
</div>\`);
}
$(".verifyItemBox_"+i0).html(html.join('\\n'));
});
for(var i0=0;i0<domains.length;i0++){
var el=$(".choice_authChall_"+i0+"_0");
el[0]&&el[0].click(); //默认选中每个域名的第一个
}
};
</script>

<div class="itemBox">
<div class="pd" style="font-size:15px">
<span class="langCN">请每个域名选择好对应的验证方式,根据显示的提示进行对应的配置操作;<span style="color:#cb1d1d">必须所有域名配置完成后,再来点击下面的“开始验证”按钮进行验证,</span>如果验证失败,需要返回第二步重新开始操作。</span>
<span class="langEN">Please select the corresponding verify method for each domain name, and perform the corresponding configuration operation according to the displayed prompts; <span style="color:#cb1d1d">after all domain names are configured, click the "Start Verify" button below to verify,</span> if the verify fails, you need to go back to the step 2 Start the operation.</span>
</div>

<div class="Center" style="padding:15px 0 10px">
<span class="mainBtn verifyStepBtn" onclick="verifyStepClick()" style="width:300px">
<span class="langCN">开始验证</span>
<span class="langEN">Start Verify</span>
</span>
<span class="mainBtn verifyRunStopBtn" onclick="verifyRunStopClick()" style="width:300px;background:#aaa">
<span class="langCN">取消</span>
<span class="langEN">Cancel</span>
</span>
<span class="mainBtn finalizeOrderBtn" onclick="finalizeOrderClick()" style="width:300px">
<span class="langCN">重试</span>
<span class="langEN">Retry</span>
</span>
</div>
<div class="verifyStepState"></div>
</div>

</div>
</div>






<div class="mainBox">
<div class="pd itemTitle">
<span class="langCN">步骤四:下载保存证书PEM文件</span>
<span class="langEN">Step 4: Download and save the certificate PEM file</span>
</div>
<div class="step4Hide step3Show step2Show step1Show">
<div class="itemBox" style="color:#999">
<span class="langCN">等待中,请先完成第三步...</span>
<span class="langEN">Waiting, please complete step 3 first...</span>
</div>
</div>
<div class="step1Hide step2Hide step3Hide step4Show">


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">保存证书PEM文件:</span>
<span class="langEN">Save certificate PEM file: </span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN"><span style="color:#f80">必须保存此文件,</span>请点击下载按钮下载,或者将证书文本内容复制保存为<i class="i downloadCertFileName"></i>文件(PEM纯文本格式);文件名后缀可改成 <i class="i">.crt</i> 或 <i class="i">.cer</i>,这样在Windows中能直接双击打开查看。本PEM格式文件已包含你的域名证书、和完整证书链,文本中第一个CERTIFICATE为你的域名证书,后面的为证书颁发机构的中间证书和根证书,如过有需要你可以自行拆分成多个.pem文件。</span>
<span class="langEN"><span style="color:#f80">This file must be saved,</span> please click the download button to download, or copy the text content of the certificate and save it as <i class="i downloadCertFileName"></i> file (PEM plain text format); the file name suffix can be changed to <i class="i">.crt </i> or <i class="i">.cer </i>, so that it can be directly double-clicked to open and view in Windows. This PEM format file already contains your domain name certificate and complete certificate chain. The first CERTIFICATE in the text is your domain name certificate, followed by the intermediate certificate and root certificate of the certificate authority, if necessary, you can split it into multiple .pem files.</span>
</div>
<div class="FlexBox">
<div class="FlexItem">
<textarea class="txt_downloadCert" style="width:100%;height:160px" readonly=""></textarea>
</div>
<div style="padding-left:10px;line-height:30px;font-size:13px;color:#aaa">
<div class="mainBtn" onclick="downloadBtnClick('Cert')">
<span class="langCN">下载保存</span>
<span class="langEN">Download</span>
</div>
</div>
</div>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">保存证书私钥KEY文件:</span>
<span class="langEN">Save the certificate private key KEY file: </span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">请点击下载按钮下载,或者将私钥文本内容复制保存为<i class="i downloadKeyFileName"></i>文件(PEM纯文本格式,.key后缀可自行修改成.pem)。如果第二步操作中你手动填写了证书私钥,此处的证书私钥和你填写的是完全一样的,可以不需要重复保存;<span style="color:#f80">如果你是新创建的证书私钥,则你必须下载保存此证书私钥文件。</span></span>
<span class="langEN">Please click the download button to download, or copy and save the text content of the private key as <i class="i downloadKeyFileName"></i> file (PEM plain text format, the .key suffix can be modified to .pem by yourself). If you manually filled in the certificate private key in the step 2, the certificate private key here is exactly the same as what you filled in, and you don’t need to save it repeatedly; <span style="color:#f80">if you are a newly created certificate private key, you must download and save it This certificate private key file.</span></span>
</div>
<div class="FlexBox">
<div class="FlexItem">
<textarea class="txt_downloadKey" style="width:100%;height:80px" readonly=""></textarea>
</div>
<div style="padding-left:10px;line-height:30px;font-size:13px;color:#aaa">
<div class="mainBtn" onclick="downloadBtnClick('Key')">
<span class="langCN">下载保存</span>
<span class="langEN">Download</span>
</div>
</div>
</div>
</div>


<div class="itemBox">
<div class="pd Bold">
<i class="must">*</i>
<span class="langCN">保存记录LOG文件:</span>
<span class="langEN">Save the record LOG file: </span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">建议下载保存此文件,本记录文件包含了所有数据,包括:证书PEM文本、证书私钥PEM文本、账户私钥PEM文本、所有配置参数。下次你需要续签新证书时,可以将本记录文件直接拖拽进本页面,会自动填写所有参数。</span>
<span class="langEN">It is recommended to download and save this file. This record file contains all data, including: certificate PEM text, certificate private key PEM text, account private key PEM text, and all configuration parameters. Next time you need to renew a new certificate, you can drag and drop the record file directly into this page, and all parameters will be filled in automatically.</span>
</div>
<div class="FlexBox">
<div class="FlexItem">
<textarea class="txt_downloadLog" style="width:100%;height:80px" readonly=""></textarea>
</div>
<div style="padding-left:10px;line-height:30px;font-size:13px;color:#aaa">
<div class="mainBtn" onclick="downloadBtnClick('Log')">
<span class="langCN">下载保存</span>
<span class="langEN">Download</span>
</div>
</div>
</div>
</div>


</div>
</div>






<div class="mainBox">

<div class="itemBox">
<div class="pd Bold">
<span class="langCN">你需要其他格式的证书文件?</span>
<span class="langEN">Do you need certificate files in other formats?</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">大部分服务器程序支持直接使用 <i class="i downloadCertFileName"></i>+<i class="i downloadKeyFileName"></i> 来配置开启HTTPS(比如Nginx),如果你需要 <i class="i">*.pfx</i>、<i class="i">*.p12</i> 格式的证书(比如用于IIS),请用下面命令将PEM证书转换成 <i class="i">pfx/p12</i> 格式:</span>
<span class="langEN">Most server programs support directly using <i class="i downloadCertFileName"></i>+<i class="i downloadKeyFileName"></i> to configure and enable HTTPS (such as Nginx). If you need a certificate in <i class="i">*.pfx</i> or <i class="i">*.p12</i> format (such as for IIS), please use the following command to convert the PEM certificate Convert to <i class="i">pfx/p12</i> format:</span>
</div>
<div class="code">openssl pkcs12 -export -out <span class="downloadFileName"></span>.pfx -inkey <span class="downloadKeyFileName"></span> -in <span class="downloadCertFileName"></span></div>
</div>

<div class="itemBox">
<div class="pd Bold">
<span class="langCN">IIS证书链缺失?</span>
<span class="langEN">IIS certificate chain missing?</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">对于Windows IIS服务器,你需要将证书链安装到“本地计算机”的“中间证书颁发机构”中;请将PEM证书中的所有证书拆分成单个PEM文件(后缀改成<i class="i">.crt</i>或<i class="i">.cer</i>),然后将系统中缺失的中间证书双击打开然后安装进去;详细参考:</span>
<span class="langEN">For Windows IIS server, you need to install the certificate chain into "Intermediate Certification Authorities" in "Local Computer"; please split all certificates in PEM certificate into a single PEM file (change the suffix to <i class="i">.crt</i> or <i class="i">.cer</i>), then double-click to open the missing intermediate certificate in the system Then install it; detailed reference:</span>
<a href="http://support.microsoft.com/kb/954755" target="_blank">http://support.microsoft.com/kb/954755</a>
</div>
</div>

<div class="itemBox">
<div class="pd Bold">
<span class="langCN">本客户端部分原理简介</span>
<span class="langEN">Introduction to the principle of this client</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">得益于现代浏览器的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto" target="_blank">crypto.subtle</a> 对加密功能标准化,不依赖其他任何js库就能在网页上实现 <i class="i">RSA</i>、<i class="i">ECC</i> 的加密、解密、签名、验证、和密钥对生成。在本客户端内的 <i class="i">X509</i> 对象中:用 X509.CreateCSR 来生成CSR,用 X509.KeyGenerate 来创建PEM格式密钥,用 X509.KeyParse 来解析PEM格式密钥,用 X509.KeyExport 来导出PEM格式密钥;这些功能都是根据相应的标准用js代码在二进制层面上实现的,二进制数据操作封装在了 <i class="i">ASN1</i> 对象中:实现了 ASN.1 标准的二进制解析和封包,使用 ASN1.ParsePEM 方法可以解析任意的PEM格式密钥或证书。以上这些都是实现ACME网页客户端的核心基础。</span>
<span class="langEN">Thanks to the standardization of encryption functions by <a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto" target="_blank">crypto.subtle</a> of modern browsers, <i class="i">RSA</i> and <i class="i">ECC</i> encryption, decryption, signature, verification, and key pair generation can be implemented on web pages without relying on any other js library. In the <i class="i">X509</i> object in this client: use X509.CreateCSR to generate CSR, use X509.KeyGenerate to create PEM format key, use X509.KeyParse to parse PEM format key, use X509.KeyExport to export PEM format key; These functions are implemented at the binary level with js code according to the corresponding standards, and binary data operations are encapsulated in <i class="i">ASN1</i> objects: ASN.1 standard binary parsing and encapsulation are implemented, Arbitrary PEM format keys or certificates can be parsed using the ASN1.ParsePEM method. These are the core foundations for implementing the ACME web client.</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">然后就是对接ACME实现证书的签发,和实现交互UI;对接ACME可以直接参考 RFC 8555 标准。有些证书颁发机构的ACME服务对浏览器支持不良,未提供齐全的 <i class="i">Access-Control-*</i> 响应头,导致网页内无法直接调用服务接口;目前采用的解决办法非常简单粗暴,比如ZeroSSL:检测到此ACME服务存在跨域问题时,会调用 <i class="i">acmeReadDirGotoCORS()</i> 方法告诉用户操作步骤(你可以<a onclick="acmeReadDirGotoCORS();alert('调用成功,请到第一步操作')">点此</a>手动调用此方法),通过在他们的页面内运行本客户端来消除跨域问题(既然打不过,那就加入他们)。</span>
<span class="langEN">Then it is to connect with ACME to realize certificate issuance and realize interactive UI; for connecting with ACME, you can directly refer to the RFC 8555 standard. The ACME services of some certificate authorities do not support browsers well, and do not provide complete <i class="i">Access-Control-*</i> response headers, so that the service interface cannot be called directly in the web page; the current solution is very simple and rude, such as ZeroSSL: detect this ACME When there is a cross-domain problem with the service, the <i class="i">acmeReadDirGotoCORS()</i> method will be called to tell the user the operation steps (you can call this method manually by <a onclick="acmeReadDirGotoCORS();alert('Call succeeded, please go to step 1')">clicking here</a>), and the cross-domain problem will be eliminated by running this client in their page (if we can't beat them, we'd better join them).</span>
</div>
</div>

<div class="itemBox">
<div class="pd Bold">
<span class="langCN">QQ群:交流与支持</span>
<span class="langEN">QQ group: communication and support</span>
</div>
<div class="pd" style="font-size:13px;color:#aaa">
<span class="langCN">欢迎加QQ群:<i class="i">421882406</i>,纯小写口令:<i class="i">xiangyuecn</i>。如需功能定制,网站、App、小程序、前端和后端等开发需求,请加此QQ群,联系群主(即作者),谢谢~</span>
<span class="langEN">Welcome to join the QQ group: <i class="i">421882406</i> , code: <i class="i">xiangyuecn</i> . If you need function customization, website, app, applet, front-end and back-end development needs, please join this QQ group and contact the group owner (ie the author), thank you~</span>
</div>
</div>

</div>




<div style="padding-top:20px;font-size:13px;color:#aaa">
<div class="pd langEN">The Chinese-English translation is mainly from: Chrome comes with translation + Baidu translation, which is translated from Chinese to English.</div>

<div class="pd">
<span class="versionBox"></span>
<a style="margin-left:15px" href="https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client/blob/main/LICENSE" target="_blank">License: GPL-3.0</a>
</div>
</div>



<div class="toastState" style="display:none;position:fixed;padding:10px;bottom:10px;right:10px;width:360px;max-height:120px;overflow-y:auto;background:#fff;box-shadow: 0px 0px 3px #aaa;border-radius: 10px;"></div>

<div class="donateWidget" style="position:fixed;top:30%;right:5px;width:160px">
<div style="border-radius:12px;background:linear-gradient(160deg, rgba(0,179,255,.7) 20%, rgba(177,0,255,.7) 80%);max-width:300px;padding:20px 10px;text-align: center;">
<div style="font-size:18px;color:#fff;">
<span class="langCN">赏包辣条?</span>
<span class="langEN">Donate a Coke?</span>
</div>
<div style="font-size:14px;color:#fff;">
<div class="langCN" style="padding:10px 0">客户端工具开发维护不易,期望本项目对你能有所帮助,欢迎通过下面按钮打赏作者~</div>
<span class="langEN">It is not easy to develop and maintain client tools. I hope this project can help you. Welcome to reward the author through the following buttons~</span>
</div>
<div>
<span class="langCN">
<a id="id_ek8v" target="_blank" class="mainBtn mainBtnMin" style="color:#fff" href="https://xiangyuecn.github.io/Docs/about.html">打赏 <span class="donateBtnIco"></span></a>
<script> id_ek8v.href=/gitee\\.io/.test(location.host)?"https://xiangyuecn.gitee.io/docs/about.html":"https://xiangyuecn.github.io/Docs/about.html" </script>
</span>
<span class="langEN">
<a href="https://xiangyuecn.github.io/Docs/about.html" target="_blank" class="mainBtn mainBtnMin" style="color:#fff">Donate <span class="donateBtnIco"></span></a>
</span>
</div>
</div>
</div>

<div class="langBtnBox" style="position:fixed;top:0;right:0;padding:5px 10px;font-size:14px;border-radius:0 0 0 10px;background:#f5f5f5;">
Language:
<a class="langBtn langBtn_cn" onclick="LangClick('cn')">中文</a>
| <a class="langBtn langBtn_en" onclick="LangClick('en')">EN</a>
</div>

</div>



<style>
body{
word-wrap: break-word;
--word-break: break-all;
background:#f5f5f5 center top no-repeat;
background-size: auto 680px;
}
pre{
white-space:pre-wrap;
}
label,label *{
cursor: pointer;
}
label:hover{
color:#06c;
}
a{
text-decoration: none;
color:#06c;
cursor: pointer;
}
a:hover{
color:#f00;
}
input, textarea {
--outline: 0;
border: 1px solid #999;
padding: 2px;
box-sizing: border-box;
font-size: 15px;
line-height:24px;
}

.main{
max-width:900px;
margin:0 auto;
padding-bottom:80px
}

.mainBox{
margin-top:12px;
padding: 12px;
border-radius: 6px;
background: #fff;
box-shadow: 2px 2px 3px #aaa;
}


.mainBtn{
display: inline-block;
cursor: pointer;
border: none;
border-radius: 3px;
background: #f80;
color:#fff;
padding: 0 15px;
line-height: 36px;
height: 36px;
overflow: hidden;
vertical-align: middle;
}
.mainBtnMin{
height: 30px;
line-height: 30px;
font-size: 14px;
padding: 0 12px;
}
.mainBtn:hover{
opacity:0.8;
}
.mainBtn:active{
opacity:1;
background: #f00;
}

.pd{
padding:0 0 8px 0;
}
.pdT{
padding:8px 0 0 0;
}
.lb{
display:inline-block;
vertical-align: middle;
background:#00940e;
color:#fff;
font-size:14px;
padding:2px 8px;
border-radius: 99px;
}

.Fill {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.Center{
text-align: center;
}
.CenterV {
display: -webkit-flex;
display: -ms-flex;
display: -moz-flex;
display: flex;
-webkit-align-items: center;
-ms-align-items: center;
-moz-align-items: center;
align-items: center;
}
.FlexBox {
display: -webkit-flex;
display: -ms-flex;
display: -moz-flex;
display: flex;
}
.FlexBoxV{
-webkit-flex-direction:column;
-ms-flex-direction:column;
-moz-flex-direction:column;
flex-direction:column;
}
.FlexCenter{
-webkit-box-pack:center;
-ms-box-pack:center;
-moz-box-pack:center;
box-pack:center;
-webkit-justify-content:center;
-ms-justify-content:center;
-moz-justify-content:center;
justify-content:center;
}
.FlexCenterV{
-webkit-box-align:center;
-ms-box-align:center;
-moz-box-align:center;
box-align:center;
-webkit-align-items:center;
-ms-align-items:center;
-moz-align-items:center;
align-items:center;
}
.FlexItem {
-webkit-flex: 1;
-ms-flex: 1;
-moz-flex: 1;
flex: 1;
}


.Bold{
font-weight: bold;
}
.code{
padding: 15px;
background-color: #000;
vertical-align: middle;
color: #fff;
font-size: 14px;
white-space: pre-wrap;
border-radius: 6px;
font-style: normal;
}
.i{
padding: 2px 4px;
background-color: #f6f6f6;
vertical-align: top;
color: #c7254e;
font-size: 12px;
white-space: pre-wrap;
border-radius: 4px;
font-style: normal;
}
i.must{
color:red;
font-style:normal;
}
.itemTitle{
font-size: 24px;
font-weight: bold;
color: #03baed;
}
.itemBox{
padding:8px;
margin-bottom:6px;
border:1px #ccc dashed;
border-radius: 6px;
}
</style>




<script>
//=================================================
//================= UI functions ==================
//=================================================
//LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
(function(){
"use strict";
var ChoiceAcmeURLStoreKey="ACME_HTML_choice_acmeURL";
var InputDomainsStoreKey="ACME_HTML_input_domains";
var InputEmailStoreKey="ACME_HTML_input_email";
var DropConfigFile={}; //拖拽进来的上次配置文件

/************** UI: Initialize on Launch **************/
window.initMainUI=function(){
$(".eccCurveNames").html(X509.SupportECCType2Names().join(Lang("、",", ")));
$(".donateBtnIco").html(unescape("%uD83D%uDE18"));
$(".versionBox").html(Lang("版本: "+Version,"Ver: "+Version));

if(/mobile/i.test(navigator.userAgent)){
$(".main").prepend($(".langBtnBox").css("position",null));
$(".donateWidget").css("position",null);
}

CLog("initMainUI",0,Lang(
\`一些高级配置:
- 设置 X509.DefaultType2_RSA="4096" 可以调整新生成的RSA密钥位数。
- 设置 X509.DefaultType2_ECC="P-384" 可以调整新生成的ECC密钥曲线,X509.SupportECCType2内为支持的曲线。
- 设置 DefaultDownloadFileNames 内的属性可以修改对应下载的文件默认名称。
- UI调试:完成第二步后允许进行UI调试,手动调用 Test_AllStepData_Save() 保存数据,刷新页面可恢复界面。\`,
\`Some advanced configurations:
- Set X509.DefaultType2_RSA="4096" The number of newly generated RSA keys can be adjusted.
- Set X509.DefaultType2_ECC="P-384" The newly generated ECC key curve can be adjusted. The supported curve is in X509.SupportECCType2.
- Setting the property in DefaultDownloadFileNames can modify the default name of the corresponding downloaded file.
- UI debugging: Allow UI debugging after completing the step 2 . Manually call Test_AllStepData_Save() to save the data, and refresh the page to restore the interface.\`));

initTest_Restore();
acmeReadDirGotoCORSInit();
downloadFileNameShow();
initStep1();
initStep2();
initStep4();
};
var initStep1=function(){
$("input[name=choice_acmeURL]").bind("click",function(e){
var el=e.target;
var isManual=el.value=="manual";
$(".in_acmeURL").css("opacity",isManual?1:0.4)
.val(isManual?step1ChoiceStoreVal:el.value)
.attr("readonly",isManual?null:"");

var descKey=$(el).attr("desckey");
$(".descAcmeURL").hide();
if(descKey)$("."+descKey).show();

step1ChoiceStoreVal="";
choiceAcmeURLChangeAfter();
});
resetStep1();
};
var step1ChoiceStoreVal;
var resetStep1=function(){
//选中上次选择的证书颁发机构
step1ChoiceStoreVal=DropConfigFile.acmeURL||window.Default_ACME_URL||localStorage[ChoiceAcmeURLStoreKey]||"";
var choices=$("input[name=choice_acmeURL]")
var idx=0;
if(step1ChoiceStoreVal){
var manualIdx=0;
for(var i=0;i<choices.length;i++){
if(choices[i].value==step1ChoiceStoreVal) idx=i+1;
if(choices[i].value=="manual") manualIdx=i+1;
}
if(!idx)idx=manualIdx //手动填写
idx--;
}
choices[idx].click();
};
var initStep2=function(){
//证书私钥UI
$(".privateKeyBox").hide();
$("input[name=choice_privateKey]").bind("click",function(e){
var el=e.target;
var isManual=el.value=="manual";
$(".in_privateKey").css("opacity",isManual?1:0.4)
.val("")
.attr("readonly",isManual?null:"");
$(".privateKeyBox").show();

configPrivateKeyGenerate(el.value);//生成密钥
configStepShow();//重新显示界面
});

//ACME账户私钥UI
$(".accountKeyBox").hide();
$("input[name=choice_accountKey]").bind("click",function(e){
var el=e.target;
var isManual=el.value=="manual";
$(".in_accountKey").css("opacity",isManual?1:0.4)
.val("")
.attr("readonly",isManual?null:"");
$(".accountKeyBox").show();

configAccountKeyGenerate(el.value);//生成密钥
configStepShow();//重新显示界面
});
};


//下一步操作提示
var NextStepTips=function(){
return '<span style="font-size:24px;font-weight:bold;">'+Lang("请进行下一步操作。"," Please proceed to the next step.")+'</span>';
};
//请稍候提示
var PleaseWaitTips=function(){
return Lang(" 请稍候... "," Please wait... ");
};
//请重试提示
var TryAgainTips=function(){
return Lang(" 请重试!"," Please try again! ");
};
//每一步的状态更新显示
var ShowState=function(elem,msg,color,tag){
var now=new Date();
var t=("0"+now.getHours()).substr(-2)
+":"+("0"+now.getMinutes()).substr(-2)
+":"+("0"+now.getSeconds()).substr(-2);
$(elem).html(msg===false?'':'<div style="color:'+(!color?"":color==1?"red":color==2?"#0b1":color==3?"#fa0":color)+'">'+(tag==null?'['+t+'] ':tag)+msg+'</div>');
return msg;
};
window.Toast=function(msg,color,time){
ShowState(".toastState",msg,color,"");

$(".toastState").show();
clearTimeout(Toast._int);
Toast._int=setTimeout(function(){ $(".toastState").hide(); }, time||5000);
};
window.onerror=function(message, url, lineNo, columnNo, error){
//https://www.cnblogs.com/xianyulaodi/p/6201829.html
Toast('【Uncaught Error】'+message+'<pre>'+"at:"+lineNo+":"+columnNo+" url:"+url+"\\n"+(error&&error.stack||"-")+'</pre>',1,15000);
};



//用户点击操作同步控制,新点击操作要终止之前未完成的操作
var UserClickSyncID=0;
var UserClickSyncKill=function(id,tag,msg){
if(id!=UserClickSyncID){
var abort=Lang("被终止","Abort",1);
CLog(tag+" "+abort,3,"From: "+msg+" ["+abort+"]");
return true;
}
};





/************** UI Step1: Read ACME service directory **************/
//证书颁发机构单选按钮点击后处理
var choiceAcmeURLChangeAfter=function(){
UserClickSyncID++;

$(".step1Hide").hide();
$(".step1Show").show();
ShowState(".acmeReadDirState",false);

if($(".in_acmeURL").val())acmeReadDirClick();
};
//点击读取服务目录按钮
window.acmeReadDirClick=function(){
var id=++UserClickSyncID;

$(".step1Hide").hide();
$(".step1Show").show();
var tag="Step-1",sEl=".acmeReadDirState";

var url=$(".in_acmeURL").val().trim();
if(!url){
ShowState(sEl,Lang("请填写服务URL!","Please fill in the service URL!"),1);
return;
}
localStorage[ChoiceAcmeURLStoreKey]=url;
url=ACME.URL=url.replace(/\\/$/,"");

var msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在读取服务目录,","Reading service directory, ")+" URL="+ACME.URL, 2));
var reqDir=function(){
ACME.Directory(function(cache,saveCache){
saveCacheCors=function(corsOK,err){
cache.corsOK=corsOK?1:-1;
cache.corsError=err||"";
saveCache();
};
if(cache.corsOK==1) dirOK();//已缓存的,此ACME服务正常
else if(cache.corsOK==-1) testCORSFail(cache.corsError, true);//不正常已缓存
else testCORS();//检测是否能正常调用接口,是否支持跨域
},function(err,status){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
if(status===0){ //可能是跨域无法读取到任何数据
CLog(tag,1, ShowState(sEl,Lang("读取服务目录出错:无法访问此URL。","Read service directory error: This URL cannot be accessed.")+TryAgainTips(), 1));

acmeReadDirGotoCORS(Lang("如果你可以在浏览器中直接打开并访问此ACME服务URL,代表此ACME服务对跨域访问支持不良,则请按下面步骤操作:","If you can open and access this ACME service URL directly in your browser, it represents that this ACME service has poor support for cross-domain access, please follow the steps below:"));
}else{
CLog(tag,1, ShowState(sEl,Lang("读取服务目录出错:"+err,"Read service directory error: "+err)+TryAgainTips(), 1));
};
});
};
var saveCacheCors;
var dirOK=function(){
if(UserClickSyncKill(id,tag,msg0))return;
configStepShow();
CLog(tag,0, ShowState(sEl,Lang("读取服务目录成功,","Read service directory OK,")
+NextStepTips()+" URL="+ACME.URL, 2), ACME.DirData);
};

//ZeroSSL接口跨域支持太差,发现这种就直接在他们网站里面跑
var testCORS=function(){
if(UserClickSyncKill(id,tag,msg0))return;
msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在测试此ACME服务对浏览器的支持情况,","Testing browser support for this ACME service, ")+" URL="+ACME.URL, 2));
ACME.GetNonce(true,function(){
ACME.TestAccountCORS(function(){
CLog(tag,0, Lang("此ACME服务对浏览器的支持良好。","This ACME service has good browser support."));
saveCacheCors(true);
dirOK();
},testCORSFail);
},function(err,corsFail){ //GetNonce 能明确检测到是否支持跨域可以缓存起来,账户地址可能是网络错误不缓存
if(corsFail) saveCacheCors(false, err);
testCORSFail(err,corsFail);
});
};
var testCORSFail=function(err,corsFail){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
CLog(tag,1, ShowState(sEl,Lang(
"测试此ACME服务对浏览器的支持情况,发生错误:"+err
,"Test browser support for this ACME service, An error occurred: "+err)
+(corsFail?"":TryAgainTips()), 1));
LangReview(sEl);//err from cache
if(corsFail) acmeReadDirGotoCORS();
};

reqDir();
};






/************** UI Step2: Certificate Configuration **************/
//显示第二步界面
var configStepShow=function(){
$(".step2Hide").hide();
$(".step2Show").show();
ShowState(".configStepState",false);

$(".eabShow")[ACME.StepData.needEAB?'show':'hide']();
if(DropConfigFile.eabKid)$(".in_eab_kid").val(DropConfigFile.eabKid);
if(DropConfigFile.eabKey)$(".in_eab_key").val(DropConfigFile.eabKey);

$(".termsAgreeBox")[ACME.StepData.termsURL?'show':'hide']();
$(".termsAgreeTips").html(Lang('我同意此证书颁发机构ACME服务的<a href="'+ACME.StepData.termsURL+'" target="_blank">服务条款</a>。', 'I agree to the <a href="'+ACME.StepData.termsURL+'" target="_blank">terms of service</a> for this Certificate Authority ACME Service.'));
$(".choice_termsAgree").prop("checked",true);

var el=$(".in_domains");//填充上次填写的域名列表
var valS=localStorage[InputDomainsStoreKey];
var val=DropConfigFile.domains&&DropConfigFile.domains.join(", ")||valS;
if(!el.val()){
el.val(val||"");
}
$(".choice_domains_store").prop("checked", !!valS);

var el=$(".in_email");//填充上次填写的联系邮箱
var valS=localStorage[InputEmailStoreKey];
var val=DropConfigFile.email||valS;
if(!el.val()){
el.val(val||"");
}
$(".choice_email_store").prop("checked", !!valS);

var setKey=function(k){
if(DropConfigFile[k]){
$("input[name=choice_"+k+"][value=manual]")[0].click();
$(".in_"+k).val(DropConfigFile[k]);
}
};
setKey("privateKey");setKey("accountKey");

DropConfigFile={};//配置完成,丢弃拖拽进来的配置信息
};
//生成证书的密钥对
var configPrivateKeyGenerate=function(type){
var id=++UserClickSyncID;
var tag="Step-2",sEl=".privateKeyState";

var keyTag="",type2;
if(type=="generateRSA"){
type="RSA";type2=X509.DefaultType2_RSA;
keyTag=Lang("证书RSA私钥("+type2+"位)","Certificate RSA private key ("+type2+" bits)");
}else if(type=="generateECC"){
type="ECC";type2=X509.DefaultType2_ECC; var type2N=X509.SupportECCType2[type2]||type2;
keyTag=Lang("证书ECC私钥("+type2N+"曲线)","Certificate ECC Private Key ("+type2N+" curve)");
}else{
ShowState(sEl,false);
return;
};

var msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在创建","Generating ")+keyTag, 2));
X509.KeyGenerate(type,type2,function(pem){
if(UserClickSyncKill(id,tag,msg0))return;
$(".in_privateKey").val(pem);
CLog(tag,0, ShowState(sEl,keyTag+Lang(",创建成功。",", generated successfully."), 2), '\\n'+pem);
},function(err){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
CLog(tag,1, ShowState(sEl,keyTag+Lang(",发生错误:"+err,", An error occurred: "+err), 1));
});
};
//生成ACME账户的密钥对
var configAccountKeyGenerate=function(type){
var id=++UserClickSyncID;
var tag="Step-2",sEl=".accountKeyState";

var keyTag="",type2;
if(type=="generateRSA"){
type="RSA";type2=X509.DefaultType2_RSA;
keyTag=Lang("ACME账户RSA私钥("+type2+"位)","ACME account RSA private key ("+type2+" bits)");
}else if(type=="generateECC"){
type="ECC";type2=X509.DefaultType2_ECC; var type2N=X509.SupportECCType2[type2]||type2;
keyTag=Lang("ACME账户ECC私钥("+type2N+"曲线)","ACME account ECC Private Key ("+type2N+" curve)");
}else{
ShowState(sEl,false);
return;
};

var msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在创建","Generating ")+keyTag, 2));
X509.KeyGenerate(type,type2,function(pem){
if(UserClickSyncKill(id,tag,msg0))return;
$(".in_accountKey").val(pem);
CLog(tag,0, ShowState(sEl,keyTag+Lang(",创建成功。",", generated successfully."), 2), '\\n'+pem);
},function(err){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
CLog(tag,1, ShowState(sEl,keyTag+Lang(",发生错误:"+err,", An error occurred: "+err), 1));
});
};
//点击确定按钮,完成配置域名和私钥的配置
window.configStepClick=function(){
var id=++UserClickSyncID;
var tag="Step-2",sEl=".configStepState";

$(".step2Hide").hide();
$(".step2Show").show();
ShowState(sEl,false);

var domains=$(".in_domains").val().trim();
var domainsStore=$(".choice_domains_store").prop("checked");
var privateKey=$(".in_privateKey").val().trim();
var accountKey=$(".in_accountKey").val().trim();
var email=$(".in_email").val().trim();
var emailStore=$(".choice_email_store").prop("checked");
var eabKid=$(".in_eab_kid").val().trim();
var eabKey=$(".in_eab_key").val().trim();
var termsAgree=$(".choice_termsAgree").prop("checked");

//域名转成数组
domains=domains.replace(/\\s+/g,",").replace(/,+/g,",").split(/,+/);
for(var i=0,mp={};i<domains.length;i++){
var domain=domains[i];
if(!domain){
domains.splice(i,1); i--; continue;
}else if(mp[domain])
return ShowState(sEl,Lang("域名"+domain+"重复!","Duplicate domain name "+domain+"!"),1);
if(/[:\\/;]/.test(domain))//简单校验域名格式
return ShowState(sEl,Lang("域名"+domain+"格式错误!","Format error of domain name "+domain+"!"),1);
mp[domain]=1;
}
localStorage[InputDomainsStoreKey]=domainsStore?domains.join(", "):"";
localStorage[InputEmailStoreKey]=emailStore?email:"";

//校验是否输入
if(!domains.length)
return ShowState(sEl,Lang("请填写证书中要包含的域名!","Please fill in domain name to be included in the certificate!"),1);
if(!privateKey)
return ShowState(sEl,Lang("请选择证书的私钥!","Please select the private key of the certificate!"),1);
if(!accountKey)
return ShowState(sEl,Lang("请选择ACME账户的私钥!","Please select the private key of ACME account!"),1);
if(!/.+@.+\\..+/.test(email) || /[\\s,;]/.test(email))
return ShowState(sEl,Lang("请正确填写联系邮箱!","Please fill in the contact email correctly!"),1);
if(ACME.StepData.needEAB && !(eabKid && eabKey))
return ShowState(sEl,Lang("请填写EAB KID和HMAC KEY!","Please fill in EAB KID and HMAC KEY!"),1);
if(ACME.StepData.termsURL && !termsAgree)
return ShowState(sEl,Lang("未同意此ACME服务的服务条款!","Do not agree to the terms of service of this acme service!"),1);

//校验私钥格式是否支持
var privateKeyInfo, parsePrivateKey=function(){
X509.KeyParse(privateKey,function(info){
privateKeyInfo=info; parseAccountKey();
},function(err){
ShowState(sEl,Lang("证书的私钥无效:","The private key of the certificate is invalid: ")+err,1);
},1);
};
var accountKeyInfo,parseAccountKey=function(){
X509.KeyParse(accountKey,function(info){
accountKeyInfo=info; parseKeyOK();
},function(err){
ShowState(sEl,Lang("ACME账户的私钥无效:","The private key of the ACME account is invalid: ")+err,1);
},1);
};

var msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips(), 2));
//设置配置数据
var parseKeyOK=function(){
if(UserClickSyncKill(id,tag,msg0))return;

ACME.StepData.config={
domains:domains
,privateKey:privateKeyInfo
,accountKey:accountKeyInfo
,email:email
,eabKid:eabKid
,eabKey:eabKey
};
CLog(tag, 0, "config", ACME.StepData.config);

acmeNewAccount();
};

//ACME账户接口调用
var acmeNewAccount=function(){
var msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在调用ACME服务的newAccount接口:","The newAccount interface that is calling the ACME service: ")+ACME.DirData.newAccount, 2));
ACME.StepAccount(function(){
if(UserClickSyncKill(id,tag,msg0))return;
acmeNewOrder();
},function(err){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
CLog(tag,1, ShowState(sEl,Lang("调用ACME服务的newAccount接口:","Call the newAccount interface of the ACME service: ")
+ACME.DirData.newAccount+Lang(",发生错误:"+err,", An error occurred: "+err), 1));
});
};
//ACME订单创建接口调用
var acmeNewOrder=function(){
var msg0,onProgress=function(tips){
if(id!=UserClickSyncID)return;
msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()+Lang("正在调用ACME服务的订单接口。","The order interface that is calling the ACME service.")+' '+tips+" URL:"+ACME.DirData.newOrder, 2));
}; onProgress("");
ACME.StepOrder(onProgress,function(){
if(UserClickSyncKill(id,tag,msg0))return;
acmeOK();
},function(err){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
CLog(tag,1, ShowState(sEl,Lang("调用ACME服务的订单接口:","Call the order interface of the ACME service: ")
+ACME.DirData.newOrder+Lang(",发生错误:"+err,", An error occurred: "+err), 1));
});
};
//ACME接口调用完成,显示下一步
var acmeOK=function(){
verifyStepShow();

CLog(tag,0, ShowState(sEl,Lang(
"配置完成,"
,"Configuration is complete, ")
+NextStepTips(), 2), ACME.StepData);
};

parsePrivateKey();
};






/************** UI Step3: Verify Domain Ownership **************/
//显示第三步界面
var verifyStepShow=function(){
$(".step3Hide").hide();
$(".step3Show").show();
$(".verifyStepBtn").show();
$(".verifyRunStopBtn").hide();
$(".finalizeOrderBtn").hide();
ShowState(".verifyStepState",false);

//显示所有域名的验证界面
verifyBoxShow();
};
//停止验证
window.verifyRunStopClick=function(){
var id=++UserClickSyncID;
$(".verifyStepBtn").show();
$(".verifyRunStopBtn").hide();
$(".finalizeOrderBtn").hide();
ShowState(".verifyStepState",false);
verifyRunStopFn&&verifyRunStopFn();
};
var verifyRunStopFn;
//点击开始验证按钮,验证所有域名所有权
window.verifyStepClick=function(){
var id=++UserClickSyncID;
var tag="Step-3",sEl=".verifyStepState";

$(".step3Hide").hide();
$(".step3Show").show();
$(".verifyStepBtn").hide();
$(".verifyRunStopBtn").show();
$(".finalizeOrderBtn").hide();
ShowState(sEl,false);

var domains=ACME.StepData.config.domains,auths=ACME.StepData.auths;
//验证中更新状态显示
var updateState=function(init,stopNow,isFail){
var isStop=stopNow||id!=UserClickSyncID;
var okCount=0,errCount=0,execCount=0;
for(var i0=0;i0<domains.length;i0++){
var domain=domains[i0],auth=auths[domain],challs=auth.challenges;
var stateEl=$(".verifyItemState_"+i0);
//authState: 0 待验证,1验证中,2等待重试authTryCount,11验证成功,12验证失败authError
if(auth.authState==11){//验证成功的,初始化也不要修改验证方式了
ShowState(stateEl,ACME.ChallName(challs[auth.challIdx])+" OK!",2,"");
okCount++;
continue;
}
if(init){ //记住选中的验证类型
var choiceEl=$("input[name=choice_authItem_"+i0+"]");
for(var i=0;i<choiceEl.length;i++){
var el=choiceEl[i];
if(!el.checked){ //未选中的隐藏掉
$(el.parentNode).hide();
}else{
auth.challIdx=+$(el).attr("challidx");
}
}
auth.authState=0;
auth.authTryCount=0;
auth.authError="";
auth.authTimer=0;
}
var challName=ACME.ChallName(challs[auth.challIdx]);
if(auth.authState==12){//验证失败
ShowState(stateEl,challName+Lang(",验证失败:",", Verify failed: ")
+auth.authError,1,"");
errCount++;
continue;
}
execCount++;
if(isStop){
ShowState(stateEl,false);
clearTimeout(auth.authTimer); auth.authTimer=0;
}else if(auth.authState==2)
ShowState(stateEl,Lang("等待重试中...","Waiting for retry...")
+" "+auth.authTryCount+" "+auth.authError,3,"");
else if(auth.authState==1)
ShowState(stateEl,Lang("验证中...","Verify in progress..."),0,"");
else ShowState(stateEl,Lang("等待验证...","Waiting for verify..."),0,"");
}
if(!isStop || stopNow){
var goto2=Lang("请返回第二步重新开始操作!","Please go back to step 2 and start over! ");
var msg=ShowState(sEl,(isFail?Lang("验证失败,","Verify failed, ")+goto2:
isStop?Lang("已取消,","Canceled, ")+goto2:
Lang("正在验证,请耐心等待... ","Verifying, please wait... "))
+"<div>"
+Lang("验证通过:","Verify pass: ")+okCount+", "
+Lang("未通过:","Failed: ")+errCount+", "
+Lang("验证中:","Verify in progress: ")+execCount
+"</div>"
, isStop?1:0);
if(isStop){
CLog(tag, 1, msg);
}
}
}
updateState(1);
//取一个进行验证
var run=function(){
if(id!=UserClickSyncID)return;
updateState();
var authItem,hasRunning=0,okCount=0,errCount=0;
for(var i0=0;i0<domains.length;i0++){
var domain=domains[i0],auth=auths[domain];
if(!authItem && !auth.authState) authItem=auth;
if(auth.authState==1)hasRunning++;
if(auth.authState==11)okCount++;
if(auth.authState==12)errCount++;
}
if(okCount==domains.length)//全部验证成功
return verifyOK();
if(okCount+errCount==domains.length)//全部验证完成,存在不通过的
return verifyFail();
if(!authItem || hasRunning)return;//没有待验证的或已有验证中,继续等待

authItem.authState=1;
authItem.authTryCount++;
authItem.authError="";
updateState();
ACME.StepVerifyAuthItem(authItem, authItem.challIdx, function(isOk, retryTime, err){
if(id!=UserClickSyncID)return;
if(isOk){
authItem.authState=11;
}else{
authItem.authState=2;
authItem.authError=err;
authItem.authTimer=setTimeout(function(){
authItem.authState=0;
authItem.authTimer=0;
run();
}, retryTime);
}
run();
}, function(err){
if(id!=UserClickSyncID)return;
authItem.authState=12;
authItem.authError=err;
run();
});
};

CLog(tag,0,"==========Verify Start==========");
var verifyEnd=function(){
$(".verifyRunStopBtn").hide();
verifyRunStopFn=null;
CLog(tag,0,"==========Verify End==========");
};
//中途停止控制
verifyRunStopFn=function(){
verifyEnd();
updateState(0,1);
};
//验证完成,存在不通过的
var verifyFail=function(){
CLog(tag,1,"Verify Fail!");
updateState(0,1,1);
verifyEnd();
};
//全部验证成功
var verifyOK=function(){
CLog(tag,0,"Verify OK!");
verifyEnd();

finalizeOrderClick();
};

//调用完成订单接口,生成证书
window.finalizeOrderClick=function(){
$(".finalizeOrderBtn").hide();
var msg0,onProgress=function(tips){
if(id!=UserClickSyncID)return;
msg0=CLog(tag,0, ShowState(sEl,PleaseWaitTips()
+Lang("验证已通过,正在签发证书。","Verify passed, issuing certificate.")
+' '+tips, 2));
}; onProgress("");
ACME.StepFinalizeOrder(onProgress,function(){
if(UserClickSyncKill(id,tag,msg0))return;
//显示下一步
downloadStepShow();

CLog(tag,0, ShowState(sEl,Lang(
"验证已通过,证书已签发,"
,"Verification passed, The certificate has been issued, ")
+NextStepTips(), 2), ACME.StepData);
},function(err){
if(UserClickSyncKill(id,tag,msg0+" err: "+err))return;
$(".finalizeOrderBtn").show();
CLog(tag,1, ShowState(sEl,Lang("签发证书发生错误,","Error issuing certificate, ")+TryAgainTips()
+Lang("如果多次重试都无法签发证书,可能需要返回第二步重新开始操作。","If the certificate cannot be issued after multiple retries, you may need to return to step 2 to restart the operation.")
+" Error: "+err, 1));
});
};

run();
};







/************** UI Step4: Download and save the certificate PEM file **************/
//显示第四步界面
var downloadStepShow=function(){
$(".step4Hide").hide();
$(".step4Show").show();
ShowState(".downloadStepState",false);

var config=ACME.StepData.config;
var hasPEM=ACME.StepData.order.downloadPEM;
var pemTxt=hasPEM||Lang("未发现证书,请到第二步重新操作!","No certificate found, please go to the step 2 to operate again!",true);

$(".txt_downloadCert").val(pemTxt);
$(".txt_downloadKey").val(config.privateKey.pem);

downFileName=config.domains[0].replace(/^\\*\\./g,"").replace(/[^\\w]/g,"_");
downloadFileNameShow(downFileName);

var logTxts=[];
var SP=function(tag){
logTxts.push("\\n=========== "+tag+" ===========");
return logTxts
}
var logSet=Object.assign({
acmeURL:ACME.URL
,accountURL:ACME.StepData.account.url
,X509:{
DefaultType2_RSA:X509.DefaultType2_RSA
,DefaultType2_ECC:X509.DefaultType2_ECC
}
,Window:{
DefaultDownloadFileNames:DefaultDownloadFileNames
}
},config);
logSet.privateKey=config.privateKey.pem;
logSet.accountKey=config.accountKey.pem;

var logTitle='/********** '+Lang($(".clientNameCN").html(),$(".clientNameEN").html(),true)+' *********/';
logTxts.push(logTitle);
logTxts.push(Lang("在线网址(GitHub):","Online website (GitHub): ", true)+'https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html');
logTxts.push(Lang("在线网址(Gitee):","Online website (Gitee): ", true)+'https://xiangyuecn.gitee.io/acme-html-web-browser-client/ACME-HTML-Web-Browser-Client.html');
logTxts.push("");
logTxts.push('GitHub: https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client');
logTxts.push('Gitee: https://gitee.com/xiangyuecn/ACME-HTML-Web-Browser-Client');
logTxts.push("");
logTxts.push(Lang("提示:你可以将本文件拖拽进客户端网页内,将自动填充本次证书申请的所有配置参数。","Tip: You can drag and drop this file into the client web page, and all configuration parameters of this certificate application will be filled automatically.",true));
logTxts.push("");
SP(Lang("证书申请时间","Certificate Application Time",true))
.push(new Date().toLocaleString());
SP(Lang("域名列表","Domain Name List",true))
.push(config.domains.join(", "));
SP(Lang("ACME服务地址","ACME Service URL",true))
.push(ACME.URL);
SP(Lang("CSR文本","CSR Text",true))
.push(ACME.StepData.order.orderCSR);
SP(Lang("证书PEM文本","Certificate PEM Text",true))
.push(pemTxt);
SP(Lang("证书私钥PEM文本","Certificate Private Key PEM Text",true))
.push(config.privateKey.pem);
SP(Lang("账户私钥PEM文本","Account Private Key PEM Text",true))
.push(config.accountKey.pem);
SP(Lang("账户URL","Account URL",true))
.push(ACME.StepData.account.url);
SP(Lang("完整配置信息","Complete Configuration Information",true))
.push("<ACME-HTML-Web-Browser-Client>"+JSON.stringify(logSet)+"</ACME-HTML-Web-Browser-Client>");
logTxts.push("");logTxts.push(logTitle);logTxts.push("");

$(".txt_downloadLog").val(hasPEM?logTxts.join("\\n"):pemTxt);
};
var initStep4=function(){//页面启动时初始化,绑定配置文件拖拽事件
$("body").bind("dragover",function(e){
e.preventDefault();
}).bind("drop",function(e){
e.preventDefault();

var file=e.dataTransfer.files[0];
if(!file)return;
var reader = new FileReader();
reader.onload = function(e){
var txt=reader.result;
var m=/ACME-HTML-Web-Browser-Client>(.+?)<\\/ACME-HTML-Web-Browser-Client/.exec(txt);
if(!m) return Toast(Lang("拖入的文件中未发现配置信息,请拖上次申请证书时保存的记录LOG文件!","No configuration information is found in the dragged file. Please drag the LOG file saved in the last certificate application!"),1);

DropConfigFile=JSON.parse(m[1]);
for(var k in DropConfigFile.X509)X509[k]=DropConfigFile.X509[k];
for(var k in DropConfigFile.Window)window[k]=DropConfigFile.Window[k];

CLog("DropConfigFile",0,"Reset Config",DropConfigFile);
Toast(Lang("识别到拖入的记录LOG文件,已填充上次申请证书时使用的配置。","The LOG file of the dragged record is identified, and the configuration used in the last certificate application has been filled."),2);
resetStep1();//重新初始化第1步
downloadFileNameShow();
}
reader.readAsText(file);
});
};
var downFileName="";
window.DefaultDownloadFileNames={ //允许设置默认的文件名,下载时自动使用此文件名
Cert:"" /*domain.crt*/, Key:"" /*domain.key*/, Log:"" /*domain.log*/
};
window.downloadBtnClick=function(type){
var val=$(".txt_download"+type).val();
var fileName=downFileName;
if(type=="Cert") fileName+=".pem";
if(type=="Key") fileName+=".key";
if(type=="Log") fileName+=".log";
fileName=DefaultDownloadFileNames[type]||fileName;

var url=URL.createObjectURL(new Blob([val], {"type":"text/plain"}));
var downA=document.createElement("A");
downA.href=url;
downA.download=fileName;
downA.click();
};
window.downloadFileNameShow=function(name){//显示下载文件名称,优先使用手动设置的默认名称
name=name||"your_domain";
var name2=(DefaultDownloadFileNames.Cert||"").replace(/\\.[^\\.]+$/g,"");
$(".downloadFileName").html(name2||name);
$(".downloadKeyFileName").html(DefaultDownloadFileNames.Key||name+".key");
$(".downloadCertFileName").html(DefaultDownloadFileNames.Cert||name+".pem");
};













//Test_打头的方法仅供测试用:完成第二步后允许进行UI调试,手动调用Test_AllStepData_Save(),刷新页面可恢复界面
window.Test_AllStepData_Save=function(){
if(!ACME.StepData.order) throw new Error(Lang("未完成第二步操作","The step 2 is not completed",true));

var config=ACME.StepData.config;
delete ACME.PrevNonce;
config.privateKey=config.privateKey.pem;
config.accountKey=config.accountKey.pem;

localStorage[Test_AllStepData_StoreKey]=JSON.stringify(ACME);
ACME=null;
console.warn(Lang("仅供测试:已保存测试数据,需刷新页面","For testing only: the test data has been saved, and the page needs to be refreshed",true));
};
var Test_AllStepData_StoreKey="ACME_HTML_Test_AllStepData";
var initTest_Restore=function(){
if(localStorage[Test_AllStepData_StoreKey]){
console.warn(Lang("仅供测试:已保存测试数据,调用Test_Restore_StepXXX()进行恢复步骤界面","For testing only: test data has been saved, call Test_Restore_StepXXX() to restore the step interface",true));
}
}
var Test_AllStepData_Restore=function(next){
var data=JSON.parse(localStorage[Test_AllStepData_StoreKey]||"{}");
if(!data.StepData) throw new Error(Lang("未保存数据","No data saved",true));
for(var k in data) ACME[k]=data[k];

var config=ACME.StepData.config;
X509.KeyParse(config.privateKey,function(info){
config.privateKey=info;
X509.KeyParse(config.accountKey,function(info){
config.accountKey=info;
console.log("ACME.StepData", ACME.StepData);
setTimeout(function(){next()});
});
});
};
window.Test_Restore_StepAuth=function(){
Test_AllStepData_Restore(function(){
console.warn(Lang("仅供测试:已手动恢复步骤三界面","For testing only: Step 3 interface has been manually restored",true));
verifyStepShow();
});
};
window.Test_Restore_StepDownload=function(){
Test_AllStepData_Restore(function(){
console.warn(Lang("仅供测试:已手动恢复步骤四界面","For testing only: Step 4 interface has been manually restored",true));
downloadStepShow();
});
};



})();
</script>





<script>
//===================================================
//================= ACME functions ==================
//===================================================
//LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
(function(){
"use strict";

/************** ACME client implementation **************/
//RFC8555: https://www.rfc-editor.org/rfc/rfc8555.html
window.ACME={
URL:""
,SyncID:0
,DirData:{}
,Directory:function(True,False){
var id=++ACME.SyncID;
var url=ACME.URL,dirStoreKey="ACME_HTML_cache_"+url;
var ok=function(cache){
var data=cache.data;
if(id!=ACME.SyncID) return False("cancel");
var meta=data.meta||{};
if(!data.newOrder)
return False("Not newOrder found: "+FormatText(JSON.stringify(data)));
ACME.DirData=data;
ACME.StepData.termsURL=meta.termsOfService;
ACME.StepData.needEAB=!!meta.externalAccountRequired;
var saveCache=function(){
localStorage[dirStoreKey]=JSON.stringify(cache);
};
saveCache();
True(cache,saveCache);
};
var cache=JSON.parse(localStorage[dirStoreKey]||'{}');//先读缓存
if(cache.time && Date.now()-cache.time<24*60*60*1000){
return ok(cache);
}
request(url,null,function(data){
ok({data:data,time:Date.now()});
},False);
}

,StepData:{}
,ChallName:function(chall){ //验证类型名称
if(chall.type=="dns-01"){ // https://letsencrypt.org/docs/challenge-types/
return Lang("DNS验证","DNS Verify");
}else if(chall.type=="http-01"){
return Lang("文件URL验证","File URL Verify");
} // tls-alpn-01 https://www.rfc-editor.org/rfc/rfc8737
return chall.type.toUpperCase();
}
,ChallSort:function(chall){ //验证类型排序
if(chall.type=="dns-01") return 1+"_"+chall.type;
else if(chall.type=="http-01") return 2+"_"+chall.type;
return 3+"_"+chall.type;
}
// 生成JSON Web Signature(JWS),用账户私钥签名
,GetJwsA:async function(Protected, Payload){
var key=ACME.StepData.config.accountKey;
var alg="ES256",algorithm={name:"ECDSA", hash:"SHA-256"};
if(key.type=="RSA"){
alg="RS256";algorithm={name:"RSASSA-PKCS1-v1_5"}
}
Protected.alg=alg;
var rtv={
"protected":Json2UrlB64(Protected)
, payload:Payload?Json2UrlB64(Payload):""
};
var data=Str2Bytes(rtv["protected"]+"."+rtv.payload);
var sign=await crypto.subtle.sign(algorithm, key.key, data);
rtv.signature=Bytes2UrlB64(sign);
return rtv;
}
// 获得随机数Nonce
,GetNonceA:function(useNew){
return new Promise(function(resolve,reject){
ACME.GetNonce(useNew,function(val){
resolve(val);
},function(err){
reject(new Error(err));
});
});
}
,GetNonce:function(useNew, True, False){
var old=ACME.PrevNonce;ACME.PrevNonce="";
if(!useNew && old) return True(old);//使用上次调用返回的值
request({url:ACME.DirData.newNonce
,method:"HEAD",response:false
}, null, function(data,xhr){
ACME.PrevNonce="";
//跨域无解 Chrome ZeroSSL: Refused to get unsafe header "Replay-Nonce" , 需要 Access-Control-Expose-Headers: Link, Replay-Nonce, Location
var val=xhr.getResponseHeader("Replay-Nonce");
if(!val){
False("GetNonce: "+Lang('此ACME服务对浏览器访问支持太差,无法跨域获取Replay-Nonce响应头。','This ACME service has too poor browser access support to get the Replay-Nonce response header across domains.'), true);
return;
}
True(val);
},function(err){
False("GetNonce: "+err);
});
}
//测试账户接口的跨域访问
,TestAccountCORS:function(True,False){
request({url:ACME.DirData.newAccount
,method:"POST",response:false,nocheck:true
}, {}, function(data,xhr){
if(xhr.status>0){
True();
}else{
False("["+xhr.status+"]",true);
}
},function(err){
False(err);
});
}

//账户接口调用
,StepAccount:async function(True,False){
var id=++ACME.SyncID;
var tag="ACME.StepAccount";
CLog(tag, 0, "==========Account Start==========");
var Err="";
try{
await ACME._StepAccountA(id,tag);
}catch(e){
Err=e.message||"-";
CLog(tag, 1, Err, e);
}
CLog(tag, 0, "==========Account End==========");
if(Err) False(Err)
else True();
} , _StepAccountA:async function(id,tag){
var url=ACME.DirData.newAccount,config=ACME.StepData.config;
var accountData={
contact:["mailto:"+config.email]
,termsOfServiceAgreed:true
};

//externalAccountRequired https://github.com/fszlin/certes/blob/08bf850bbed9e026c718f56f1bcc454afafb4f92/src/Certes/Acme/AccountContext.cs
if(ACME.StepData.needEAB){
var eab={
"protected":Json2UrlB64({ alg:"HS256", kid:config.eabKid, url:url })
,payload:Json2UrlB64(X509.PublicKeyJwk(config.accountKey))
};
var key=await crypto.subtle.importKey("raw", UrlB642Bytes(config.eabKey)
,{name:"HMAC",hash:"SHA-256"}, true, ["sign"]);
var data=Str2Bytes(eab["protected"]+"."+eab.payload);
var sign=await crypto.subtle.sign("HMAC", key, data);
eab.signature=Bytes2UrlB64(sign);
accountData.externalAccountBinding=eab;
CLog(tag,0 ,"externalAccountBinding", eab);
};

//组装成jws,请求接口
var sendData=await ACME.GetJwsA({
jwk: X509.PublicKeyJwk(config.accountKey)
,nonce: await ACME.GetNonceA(true)
,url: url
},accountData);
var resp=await requestA(url, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
ACME.StepData.account={
url:xhrHeader(resp.xhr, "Location")
,data:resp.data
};
CLog(tag,0,"Account OK",ACME.StepData.account);
}

//订单接口调用
,StepOrder:async function(Progress,True,False){
var id=++ACME.SyncID;
var tag="ACME.StepOrder";
CLog(tag, 0, "==========Order Start==========");
var Err="";
try{
await ACME._StepOrderA(Progress,id,tag);
}catch(e){
Err=e.message||"-";
CLog(tag, 1, Err, e);
}
CLog(tag, 0, "==========Order End==========");
if(Err) False(Err)
else True();
} , _StepOrderA:async function(Progress,id,tag){
var url=ACME.DirData.newOrder,config=ACME.StepData.config;
var dnsArr=[];
for(var i=0;i<config.domains.length;i++){
dnsArr.push({ type:"dns", value:config.domains[i] });
}
var orderData={
identifiers:dnsArr
};

Progress("newOrder...");
//组装成jws,请求接口
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},orderData);
var resp=await requestA(url, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
resp.data.orderUrl=xhrHeader(resp.xhr, "Location");
ACME.StepData.order=resp.data;
CLog(tag,0,"Order OK",ACME.StepData.order);

//准备Key Authorizations需要的参数 参考:rfc8555 8.1
var jwkStr=JSON.stringify(X509.PublicKeyJwk(config.accountKey));
var thumbprint=await crypto.subtle.digest({name: "SHA-256"}, Str2Bytes(jwkStr));
thumbprint=Bytes2UrlB64(thumbprint);

//读取所有的验证信息
var idfs=ACME.StepData.order.identifiers,bad=0;
var auths=ACME.StepData.order.authorizations;
for(var i=0;i<idfs.length;i++){
if(config.domains.indexOf(idfs[i].value)==-1) bad=1;
}
if(bad || idfs.length!=auths.length || idfs.length!=config.domains.length)
throw new Error(Lang("创建的订单中的域名和配置的不一致","The domain name in the created order is inconsistent with the configuration"));
if(id!=ACME.SyncID) throw new Error("cancel");
ACME.StepData.auths={};
for(var i=0;i<auths.length;i++){
Progress("auth("+(i+1)+"/"+auths.length+")...");
var url=auths[i];
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},"");
var resp=await requestA(url, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
resp.data.domain=idfs[i].value;
resp.data.authUrl=url;
ACME.StepData.auths[idfs[i].value]=resp.data;

//生成Key Authorizations
var challs=resp.data.challenges;
for(var i2=0;i2<challs.length;i2++){
var chall=challs[i2];
chall.authTxt=chall.token+"."+thumbprint;
var sha=await crypto.subtle.digest({name: "SHA-256"}
, Str2Bytes(chall.authTxt));
if(id!=ACME.SyncID) throw new Error("cancel");
chall.authTxtSHA256=Bytes2UrlB64(sha);
chall.authTxtSHA256Base64=Bytes2Base64(sha);
}
}
CLog(tag,0,"Order Authorizations",ACME.StepData.auths);
}

//验证一个域名
,StepVerifyAuthItem:async function(authItem, challIdx, True,False){
var tag="ACME.verify["+authItem.challenges[challIdx].type+"]:"+authItem.domain;
var Err="";
try{
await ACME._StepVerifyAuthItemA(authItem,challIdx, ACME.SyncID,tag, True,False);
}catch(e){
Err=e.message||"-";
CLog(tag, 1, Err, e);
}
if(Err) True(false, 1000, Err); //重试
} , _StepVerifyAuthItemA:async function(authItem,challIdx, id,tag, True,False){
//先通知要用的验证方式,反复发送只要成功一次即可,不管结果
var chall=authItem.challenges[challIdx];
if(!chall.isSend){
var url=chall.url;
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},{});
var resp=await requestA({url:url,nocheck:true}, sendData);
var status=resp.xhr.status;
if(status>=200&&status<300)
chall.isSend=true;
}

//重新查询一下状态
var url=authItem.authUrl;
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},"");
var resp=await requestA(url, sendData);
var data=resp.data;
if(data.status=="pending"){
CLog(tag, 0, "pending...");
return True(false, 1000, "pending...");
}
if(data.status=="valid"){
CLog(tag, 0, "valid OK");
return True(true);
}
CLog(tag, 1, "Fail", data);
return False(data.status+": "+FormatText(JSON.stringify(data)));
}

//完成订单,生成证书
,StepFinalizeOrder:async function(Progress,True,False){
var id=++ACME.SyncID;
var tag="ACME.StepFinalizeOrder";
CLog(tag, 0, "==========Finalize Start==========");
var Err="";
try{
await ACME._StepFinalizeOrderA(Progress,id,tag);
}catch(e){
Err=e.message||"-";
CLog(tag, 1, Err, e);
}
CLog(tag, 0, "==========Finalize End==========");
if(Err) False(Err)
else True();
} , _StepFinalizeOrderA:async function(Progress,id,tag){
var order=ACME.StepData.order,config=ACME.StepData.config,domains=config.domains;

//先请求finalize
if(!order.finalizeIsSend){
Progress("finalize...");
//生成csr,第一个域名做CN
var csr=await new Promise(function(resolve,reject){
X509.CreateCSR(config.privateKey, domains[0], domains, function(csr){
resolve(csr);
},function(err){
reject(new Error(err));
});
});
order.orderCSR=csr;
CLog(tag,0,"CSR\\n"+csr);
csr=Bytes2UrlB64(ASN1.PEM2Bytes(csr));

var url=order.finalize;
//组装成jws,请求接口
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},{ csr:csr });
var resp=await requestA(url, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
CLog(tag,0,"finalize result",resp.data);
order.finalizeIsSend=true;
}

//轮询订单状态,60秒超时
var t1=Date.now(),tryCount=0;
while(!order.checkOK && Date.now()-t1<60*1000){
if(id!=ACME.SyncID) throw new Error("cancel");
tryCount++;
Progress("check retry:"+tryCount+"...");
var url=order.orderUrl;
//组装成jws,请求接口
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},"");
var resp=await requestA(url, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
var data=resp.data;
if(data.status=="valid"){
order.checkOK=true;
order.certUrl=data.certificate;
CLog(tag,0,"check OK",data);
break;
}else if(data.status=="invalid"){
CLog(tag,1,"check Fail",data);
throw new Error(data.status+": "+FormatText(JSON.stringify(data)));
}else{
CLog(tag,0,data.status+"... wait 1s",data);
await new Promise(function(s){ setTimeout(s, 1000) });
}
}

//下载证书
if(!order.downloadPEM){
Progress("download...");
var url=order.certUrl;
//组装成jws,请求接口
var sendData=await ACME.GetJwsA({
kid: ACME.StepData.account.url
,nonce: await ACME.GetNonceA()
,url: url
},"");
var resp=await requestA({url:url,response:false}, sendData);
if(id!=ACME.SyncID) throw new Error("cancel");
var pem=resp.xhr.responseText;
order.downloadPEM=pem;
CLog(tag,0,"download OK\\n"+pem);
}
}
};


// 读取响应头,读不到就当做跨域无法读取处理,自定义的头需要 Access-Control-Expose-Headers: Link, Replay-Nonce, Location
var xhrHeader=function(xhr,key){
var val=xhr.getResponseHeader(key);
if(!val){
acmeReadDirGotoCORS();
throw new Error(Lang("无法读取响应头"+key+",可能是因为此ACME服务对跨域访问支持不良,请按第一步显示的提示操作。"
,"The response header "+key+" cannot be read, This may be because this ACME service does not support cross domain access, Please follow the prompt displayed in step 1."));
}
return val;
};

// ajax
var requestA=function(url,post){
return new Promise(function(resolve,reject){
request(url,post,function(data,xhr){
resolve({data:data,xhr:xhr});
},function(err){
reject(new Error(err));
});
});
}
var request=function(url,post,True,False){
var set=typeof(url)=="string"?{url:url}:url; url=set.url;
var method=set.method||(post?"POST":"GET");
var tag="ACME.Request"; CLog(tag,4,"send "+method,set,post);

var xhr=new XMLHttpRequest();
xhr.timeout=30000;
xhr.open(method,url,true);
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
ACME.PrevNonce=xhr.getResponseHeader("Replay-Nonce")||"";//将此值存起来

var isBad=xhr.status<200 || xhr.status>=300;
var useResp=set.response==null || set.response;
var err="",data,logObj;
if(useResp || isBad){
logObj=xhr.responseText;
try{
data=JSON.parse(logObj);
logObj=data;
}catch(e){ };
}
CLog(tag,4,"send End",set, {
status:xhr.status
,headers:xhr.getAllResponseHeaders()
}, logObj);
if(set.nocheck || !isBad && (!useResp || data)){
return True(data, xhr);
}
False((isBad?"["+xhr.status+"]":"")+FormatText(xhr.responseText), xhr.status);
}
};
if(post){
if(typeof(post)=="object")post=JSON.stringify(post);
xhr.setRequestHeader("Content-Type",set.contentType||"application/jose+json");
xhr.send(post);
}else{
xhr.send();
}
};

})();
</script>





<script>
//==================================================================
//================= RSA/ECC/X.509/ASN.1 functions ==================
//==================================================================
//LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
(function(){
"use strict";

window.X509={
DefaultType2_RSA:"2048" //默认创建RSA密钥位数
,DefaultType2_ECC:"P-256" //默认创建ECC曲线
,SupportECCType2:{ //支持的ECC曲线和常见名称
"P-256":"prime256v1", "P-384":"secp384r1", "P-521":"secp521r1"
}
,SupportECCType2Names:function(){ var str=[]; for(var k in X509.SupportECCType2)str.push(X509.SupportECCType2[k]); return str; }

//创建RSA/ECC密钥对 type2取值:type=RSA时为密钥位数数值,type=ECC时为支持的曲线(X509.SupportECCType2)
,KeyGenerate:function(type,type2,True,False){
var algorithm=0;
if(type=="RSA"){
algorithm={ publicExponent: new Uint8Array([1, 0, 1]) //E: AQAB
,name:"RSASSA-PKCS1-v1_5", modulusLength:+type2, hash:"SHA-256" };
}else if(type=="ECC"){
algorithm={ name:"ECDSA", namedCurve:type2 };
}else{
False("Not support "+type);
return;
};
crypto.subtle.generateKey(algorithm,true,["sign","verify"])
.then(function(key){
//FireFox不支持导出pkcs8参数的ECC私钥,Chrome没问题,使用jwk获得最大兼容
crypto.subtle.exportKey("jwk", key.privateKey).then(function(jwk){
True(X509.KeyExport(jwk));
}).catch(function(e){
False(Lang('此浏览器不支持导出'+algorithm.name+'+PKCS#8格式密钥:','This browser does not support exporting '+algorithm.name+'+PKCS#8 format keys: ')+e.message);
});
}).catch(function(e){
False(Lang('此浏览器不支持生成'+algorithm.name+':','This browser does not support generating '+algorithm.name+': ')+e.message);
});
}
//解析密钥,检查是否支持,通过回调返回格式或错误信息,pem支持公钥和私钥
,KeyParse:function(pem, True, False, mustPrivate){
var rtv={};
var Err=function(msg){ rtv.error=msg; False(msg, rtv); };
//浏览器crypto不支持PKCS#1的pem导入,提取参数转成jwk导入

if(!/BEGIN\\s*(RSA|EC)?\\s*(PUBLIC|PRIVATE)\\s*KEY/.test(pem))
return Err(Lang('不是RSA或ECC密钥','Not an RSA or ECC key'));
rtv.type=RegExp.$1=="EC"?"ECC":RegExp.$1;
var isPKCS1=!!RegExp.$1;
var isPub=RegExp.$2=="PUBLIC";
if(isPub && mustPrivate)
return Err(Lang('不是私钥','Is not a private key'));

//解析提取参数信息
try{
var paramAsn1=null;
var asn1=ASN1.ParsePEM(pem);
rtv.asn1=asn1;

if(isPKCS1){
if(rtv.type=="RSA"){
paramAsn1=asn1; //直接按顺序存放的参数
}else if(rtv.type=="ECC"){
if(isPub) //没见过这种ecc公钥
return Err(Lang('不支持ECC PKCS#1格式公钥','ECC PKCS#1 format public key is not supported'));
var oid2=asn1.sub[2].sub[0].oid;
rtv.type2=ASN1.OID[oid2]||"";
paramAsn1=asn1;
}
}else{
var idx=isPub?0:1; //跳过私钥开头的version,就和公钥一样了
var oid=asn1.sub[idx].sub[0].oid;
var oid2=asn1.sub[idx].sub[1].oid;
rtv.type=ASN1.OID[oid]||"";
rtv.type2=ASN1.OID[oid2]||"";

if(rtv.type=="ECC"&&isPub)//ECC公钥直接就是值
paramAsn1=asn1;
else
paramAsn1=new ASN1().parse(asn1.sub[idx+1].bytes);//密钥参数
}
rtv.paramAsn1=paramAsn1;
}catch(e){
return Err(Lang('密钥解析失败:','Key resolution failed: ')+e.message);
}
if(rtv.type=="RSA"){
var idx=isPub?0:1; //私钥开头多一个版本号
rtv.param={
n:paramAsn1.sub[idx].bytes //Modulus
,e:paramAsn1.sub[idx+1].bytes //Exponent
,d:isPub?null:paramAsn1.sub[idx+2].bytes //D
};
var keys="p,q,dp,dq,qi".split(",");
for(var i=0;i<5;i++)
rtv.param[keys[i]]=isPub?null:paramAsn1.sub[idx+3+i].bytes;
rtv.type2=rtv.param.n.length*8+"";
}else if(rtv.type=="ECC"){
if(!X509.SupportECCType2[rtv.type2]){
return Err(Lang('只支持'+X509.SupportECCType2Names().join("、")+'曲线的ECC密钥','ECC key only supported for '+X509.SupportECCType2Names().join(",")+' curve'));
}
if(isPub){
var b2=paramAsn1.sub[1].bytes;
}else{
var idx=isPKCS1?3:2;
var b2=paramAsn1.sub[idx].sub[0].bytes;
}
if(b2[0]!=0x04)//0x04代表公钥未压缩 https://www.rfc-editor.org/rfc/rfc5480#section-2.2
return Err("ECC !0x04: "+b2[0]);
var bits=(b2.length-1)/2;
rtv.param={
x:b2.slice(1,1+bits) //xy为公钥
,y:b2.slice(1+bits)
,d:isPub?null:paramAsn1.sub[1].bytes //D
};
}else{
return Err(Lang('不支持的密钥类型:','Unsupported key type: ')+oid);
}
rtv.isPKCS1=isPKCS1;
rtv.hasPrivate=!isPub;
rtv.pem=pem;

//转成CryptoKey
var algorithm,jwk;
if(rtv.type=="RSA"){
algorithm={ publicExponent: new Uint8Array(rtv.param.n.buffer)
,name:"RSASSA-PKCS1-v1_5", modulusLength:+rtv.type2, hash:"SHA-256" };
jwk={ kty:"RSA", alg: "RS256" };
}else if(rtv.type=="ECC"){
algorithm={ name:"ECDSA", namedCurve:rtv.type2 };
jwk={ kty:"EC", crv:rtv.type2 };
}
jwk=Object.assign(jwk,{ ext:true, key_ops:[isPub?"verify":"sign"] });
for(var k in rtv.param)
rtv.param[k]&&( jwk[k]=Bytes2UrlB64(rtv.param[k]) );

crypto.subtle.importKey(
"jwk",jwk,algorithm,true,[isPub?"verify":"sign"]
).then(function(key){
rtv.key=key;
True(rtv);
}).catch(function(e){
Err(Lang('密钥转成CryptoKey失败:','Failed to convert key to CryptoKey: ')+e.message);
});
}
//解析出来的公钥转换成JSON Web Key(JWK)
,PublicKeyJwk:function(info){ // https://www.rfc-editor.org/rfc/rfc7638
var p=info.param;
if(info.type=="RSA"){
return { e:Bytes2UrlB64(p.e), kty:"RSA", n:Bytes2UrlB64(p.n) };
}else if(info.type=="ECC"){
return { crv:info.type2, kty:"EC", x:Bytes2UrlB64(p.x),y:Bytes2UrlB64(p.y) };
}else{
throw new Error("Jwk: "+info.type);
}
}
//解析出来的密钥或jwk对象转成PKCS#8格式 publicOnly:提供私钥时仅导出公钥 returnType:1 bytes,2 asn1,other pem
,KeyExport:function(info,publicOnly,returnType){
var tag="KeyExport: ", S=ASN1.S,V=ASN1.V; //ASN1快捷创建方式
var param=info.param,type=param&&info.type,type2=info.type2;//解析出来的格式
if(!param){//jwk
if(info.kty){
type=info.kty=="EC"?"ECC":info.kty;
if(type=="ECC")type2=info.crv;
}else throw new Error(tag+"bad key");
}
var keys=(type=="ECC"?"x,y,d":"n,e,d,p,q,dp,dq,qi").split(",");
if(!param){//jwk参数需转成二进制
param={};
for(var i=0;i<keys.length;i++){
var k=keys[i],b64=info[k];
if(b64)param[k]=UrlB642Bytes(b64);
}
}
var useD=!publicOnly&&param.d;//导出私钥

var bytes;//封装参数
if(type=="RSA"){
var asn1=S(0x30, V(0x02, param.n), V(0x02, param.e));
if(useD){//公钥只需n、e参数,私钥要全部
asn1.sub.splice(0,0,V(0x02, [0]));//开头插入版本号
for(var i=2;i<keys.length;i++){
asn1.push(V(0x02, param[keys[i]]));
}
}
bytes=asn1.toBytes();
}else if(type=="ECC"){ //公钥x、y参数,私钥外面再套一层d
var pubB=new Uint8Array(1+param.x.length*2); pubB[0]=0x04;
pubB.set(param.x, 1);
pubB.set(param.y, 1+param.x.length);
if(useD){
bytes=S(0x30, V(0x02,[1]), V(0x04, param.d)
, S(0xA1, V(0x03, pubB)) ).toBytes();
}else bytes=pubB;
}else{ throw new Error(tag+type); }

var typeASN1=S(0x30 //封装类型
,V(0x06, ASN1.OID2Bytes(ASN1.OID[type]))
,type=="RSA"?V(0x05,[])
:V(0x06, ASN1.OID2Bytes(ASN1.OID[type2]))
);

if(useD){//封装私钥
var keyA=S(0x30, V(0x02, [0]), typeASN1, V(0x04, bytes));
}else{ //封装公钥
var keyA=S(0x30 ,typeASN1, V(0x03, bytes));
};
if(returnType==2) return keyA;
bytes=keyA.toBytes();
if(returnType==1) return bytes;
var str=Bytes2Base64(bytes).replace(/(.{64})/g,"$1\\n").trim();
var sp=useD?"PRIVATE":"PUBLIC";
return '-----BEGIN '+sp+' KEY-----\\n'+str+'\\n-----END '+sp+' KEY-----';
}


//创建证书请求CSR,提供私钥用于CSR签名
,CreateCSR:function(keyInfo,commonName,domains,True,False){
//CSR格式:rfc2986,太复杂了,直接拿openssl生成csr用ASN1.ParsePEM来观看格式
var S=ASN1.S,V=ASN1.V; //ASN1快捷创建方式

//封装公钥
try{
var pubA=X509.KeyExport(keyInfo, true, 2);
}catch(e){ return False(e.message) }

//封装域名列表扩展属性
var altNameA=S(0x30);
for(var i=0;i<domains.length;i++)
altNameA.push(V(0x82, Str2Bytes(domains[i])));

//组装CSR主体
var bodyA=S(0x30
,V(0x02, [0]) //版本号 固定值0
,S(0x30, S(0x31 ,S(0x30 //只提供一个属性:CN
,V(0x06, ASN1.OID2Bytes("2.5.4.3"))
,V(0x0C, Str2Bytes(commonName))
)))
,pubA //公钥
,S(0xA0, S(0x30 //扩展属性,域名列表
,V(0x06, ASN1.OID2Bytes("1.2.840.113549.1.9.14"))
,S(0x31, S(0x30, S(0x30
,V(0x06, ASN1.OID2Bytes("2.5.29.17"))
,V(0x04, altNameA.toBytes())
)))
))
);

//签名生成CSR rfc2315
var bodyBytes=bodyA.toBytes();
var algorithm={name:"ECDSA", hash:"SHA-256"};
if(keyInfo.type=="RSA"){
algorithm={name:"RSASSA-PKCS1-v1_5"}
}
crypto.subtle.sign(algorithm, keyInfo.key, bodyBytes).then(function(arr){
var signBytes=new Uint8Array(arr);
if(keyInfo.type=="ECC"){//ECC分两段重新封装一下
var s1=signBytes.subarray(0,keyInfo.param.x.length);
var s2=signBytes.subarray(keyInfo.param.x.length);
signBytes=S(0x30, V(0x02,s1), V(0x02,s2)).toBytes();
}
var csrA=S(0x30, bodyA
,S(0x30 //签名类型
,V(0x06, ASN1.OID2Bytes(ASN1.OID["SHA256_"+keyInfo.type]))
,keyInfo.type=="RSA"?V(0x05,[]):null //ECC没有第二个参数
)
,V(0x03, signBytes)
);
var bytes=csrA.toBytes();
var str=Bytes2Base64(bytes).replace(/(.{64})/g,"$1\\n").trim();
True('-----BEGIN CERTIFICATE REQUEST-----\\n'+str+'\\n-----END CERTIFICATE REQUEST-----');
}).catch(function(e){
False("CSR sign:"+e.message);
});
}
};



//简单实现ASN.1解析和封包
window.ASN1=function(tag, bytes){
this.sub=[];
if(tag)this.setTag(tag);
if(bytes)this.setBytes(bytes);
};
ASN1.S=function(tag){ //快捷创建容器类型,并提供任意多个子元素
var v=new ASN1(tag);
for(var i=1,a=arguments;i<a.length;i++){a[i]&&v.push(a[i])}
return v;
};
ASN1.V=function(tag, bytes){ //快捷创建值类型
return new ASN1(tag, bytes)
};
ASN1.ParsePEM=function(pem){
return new ASN1().parsePEM(pem);
};
ASN1.TagNames={
'01':'BOOLEAN','02':'INTEGER','03':'BIT_STRING'
,'04':'OCTET_STRING','05':'NULL','06':'OID'
,'0C':'UTF8String','13':'Printable_String'
,'17':'UTCTime','18':'GeneralizedTime','30':'SEQUENCE','31':'SET'
};
ASN1.OID={
"1.2.840.113549.1.1.1":"RSA"
,"1.2.840.113549.1.1.11":"SHA256_RSA"
,"1.2.840.10045.2.1":"ECC"
,"1.2.840.10045.4.3.2":"SHA256_ECC"
,"1.2.840.10045.3.1.7":"P-256" //secp256r1 | prime256v1
,"1.3.132.0.34":"P-384" //secp384r1
,"1.3.132.0.35":"P-521" //secp521r1
}; for(var k in ASN1.OID)ASN1.OID[ASN1.OID[k]]=k;
ASN1.OID2Bytes=function(oid){
var arr = oid.split('.'), byts = [];
var v0=+arr[0],v1=+arr[1];
if (!/^[\\d\\.]+$/.test(oid)|| arr.length < 3 || v0 > 2 || v0 * 40 + v1 > 0xff)
throw new Error("bad oid: "+oid);
byts.push(v0 * 40 + v1);
for (var i = 2, len = arr.length; i < len; i++) {
var num = +arr[i], bits = [];
while (num >= 0x80) { bits.push(num % 0x80); num /= 0x80; }
bits.push(num); bits.reverse();
for (var j = 0, jl = bits.length - 1; j <= jl; j++) {
if (j != jl) {
byts.push(0x80 + bits[j]);
} else {
byts.push(bits[j]);
}
}
}
return new Uint8Array(byts);
};
ASN1.OID2Text=function(bytes){
var str = "", b0 = bytes[0];
var m = b0 < 80 ? b0 < 40 ? 0 : 1 : 2;
str+=m+"."+(b0 - m * 40);
for (var i = 1, len = bytes.length; i < len; ) {
var num = 0;
for (; i < len; ) {
var bit = bytes[i++]; num *= 0x80;
if (bit >= 0x80) num += bit - 0x80;
else { num += bit; break; }
}
str+="."+num;
}
return str;
};
ASN1.ParseSize=function(pos, bytes){ //简单解析长度数值
var bitCount=bytes[pos[0]++],size=0;
if(bitCount < 0x80) size=bitCount;
else if(bitCount == 0x80) size=-404; //不定长,需搜索两个0结尾,直接拒绝支持
else for(var i=0,len=bitCount&0x7F;i<len;i++)
size=size*256+bytes[pos[0]++];
if(size<0 || size>bytes.length-pos[0])throw new Error("ASN.1 Bad size "+size);
return size;
};
ASN1.ParseBlock=function(pos, bytes, sub){ //简单解析一块子内容
sub=sub||[];
while(pos[0]<bytes.length){
var idx0=pos[0],item=new ASN1();
var tag=bytes[pos[0]++],size=ASN1.ParseSize(pos, bytes);
if((tag&0x20) != 0){//结构化容器,嵌套调用
item.parse(bytes.slice(idx0, pos[0]+size));
}else{//普通内容
var chunk=bytes.slice(pos[0], pos[0]+size);
if(tag==0x02 || tag==0x03){//去掉开头补的0,正整数
if(chunk.length>1 && chunk[0]==0){
chunk=chunk.slice(1);
}
}
item.setTag(tag);
item.setBytes(chunk);
}
sub.push(item);
pos[0]+=size;
}
return sub;
};
ASN1.PEM2Bytes=function(pem){
pem=pem.replace(/[\\s\\r\\n]/g,"");
var m=/^-+BEGIN\\w*-+([^-]+)-+END\\w+-+$/i.exec(pem);
try{
return Base642Bytes(m[1]);
}catch(e){
throw new Error(Lang('不是pem格式。','Not a pem format.'));
}
};
ASN1.prototype={
setTag:function(tag){
var txt=(tag<16?"0":"")+tag.toString(16).toUpperCase();
this.tag=tag;
this.tagTxt=txt;
this.tagName=ASN1.TagNames[txt]||"0x"+txt;
}
,setBytes:function(bytes){
if(bytes.length==null||(bytes.slice==null && bytes.subarray==null))
throw new Error("Not Array");
if(this.tag==0x06) this.oid=ASN1.OID2Text(bytes);
if(this.tag==0x0C||this.tag==0x13) this.string=Bytes2Str(bytes);
this.bytes=bytes;
}
,push:function(asn1){
if(!asn1.parsePEM) throw new Error("Not ASN1");
this.sub.push(asn1);
return this;
}
,parsePEM:function(pem){
return this.parse(ASN1.PEM2Bytes(pem));
}
,parse:function(bytes){
var pos=[0];
//最外层必须是个结构化容器,第6位为1,为0为基础类型 https://www.jianshu.com/p/ce7ab5f3f33a
if((bytes[0]&0x20) == 0) throw new Error("ASN.1 parse: Not SEQ");
this.setTag(bytes[pos[0]++]);
var size=ASN1.ParseSize(pos, bytes);
bytes=bytes.slice(pos[0], pos[0]+size);
this.setBytes(bytes);
//解析子内容
ASN1.ParseBlock([0], bytes, this.sub);
return this;
}
,toBytes:function(innerOlny){
var chunks=[],len=0;
if(this.sub.length){//容器类型,递归调用
for(var i=0;i<this.sub.length;i++){
var arr=this.sub[i].toBytes();
chunks.push(arr); len+=arr.length;
}
}else if(this.bytes&&this.bytes.length){//简单类型
if(this.tag==0x02 && this.bytes[0] >= 0x80 || this.tag==0x03){
chunks.push([0]);len++; //0x02负数 0x03需要开头补0
}
chunks.push(this.bytes); len+=this.bytes.length;
}
if(!innerOlny){//添加标签和长度
var arr=[], num=len;
if(num<0x80) arr.push(num);
else {
while(num>0xff){ arr.push(num&0xff); num=num>>8; }
arr.push(num&0xff); arr.push(0x80+arr.length);
}
arr.push(this.tag); arr.reverse();
chunks.splice(0,0,arr); len+=arr.length;
}
var bytes=new Uint8Array(len),n=0;
for(var i=0;i<chunks.length;i++){
var arr=chunks[i];
bytes.set(arr, n); n+=arr.length;
}
return bytes;
}
};

})();
</script>







<script>
//===========================================================
//================= Common functions ==================
//===========================================================
//LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
(function(){
"use strict";

/************** Language **************/
window.LangCur=/\\b(zh|cn)\\b/i.test(navigator.language)?"cn":"en";
window.Lang=function(cn,en,txt){
if((cn||en) && (!cn||!en))throw new Error("Lang bad args");
if(txt)return LangCur=="cn"?cn:en;
var html="",ks={cn:cn,en:en};
for(var k in ks){
html+=(LangCur!=k?'<!--LangHide1-->':'')
+'<span class="lang'+k.toUpperCase()
+'" style="'+(LangCur==k?'':'display:none')
+'">'+ks[k]+'</span>'
+(LangCur!=k?'<!--LangHide2-->':'');
}
return html;
};
window.LangReview=function(cls){
var el=$(cls||"body");
el.find(".langCN")[LangCur=="cn"?"show":"hide"]();
el.find(".langEN")[LangCur!="cn"?"show":"hide"]();
var inputs=el.find(".inputLang");
for(var i=0;i<inputs.length;i++)
inputs[i].setAttribute("placeholder",inputs[i].getAttribute("placeholder-"+LangCur));
};
window.LangClick=function(lang){
LangCur=lang;
LangReview();
$(".langBtn").css("color",null);
$(".langBtn_"+lang).css("color","#000");
$("body").css("wordBreak",lang=="cn"?"break-all":null);

if(!document.titleLang)document.titleLang=document.title;
var arr=document.titleLang.split("|"),t1=[],t2=[];
for(var i=0;i<arr.length;i++)
if(/[^\\x00-\\xff]/.test(arr[i]) == (LangCur=="cn"))t1.push(arr[i].trim());
else t2.push(arr[i].trim());
document.title=t1.concat(t2).join(" | ");
};


/************** Console log output **************/
window.CLog=function(tag, color, msg){
var now=new Date();
var t=("0"+now.getMinutes()).substr(-2)
+":"+("0"+now.getSeconds()).substr(-2)
+"."+("00"+now.getMilliseconds()).substr(-3);
msg=msg.replace(/<!--LangHide1[\\S\\s]+?LangHide2-->/g,"");//去掉没有显示的语言
msg=msg.replace(/<[^>]+>/g,"");
var arr=["["+t+" "+tag+"]"+msg];
for(var i=3;i<arguments.length;i++){
arr.push(arguments[i]);
};
var fn=color==1?console.error:color==3?console.warn:color==4?console.debug:console.log;
fn.apply(console,arr);
return msg;
};


/************** $ Selector / like jQuery **************/
(function(){
window.$=function(cls){ if(cls&&cls.is$) return cls; return new fn(cls) }
var fn=function(cls,node){
this.length=0;
if(!cls)return;
if(cls.appendChild){this.push(cls); return}
var arr=(node||document).querySelectorAll(cls);
for(var i=0;i<arr.length;i++)this.push(arr[i]);
};
fn.prototype={
is$:1
,push:function(val){ this[this.length++]=val }
,find:function(cls){ var el0=this[0]; return new fn(el0?cls:"",el0) }
,val:function(val){ return this.prop("value",val) }
,hide:function(){ return this.css("display","none") }
,show:function(display){ return this.css("display",display===undefined?null:display) }

,html:function(html){
var el0=this[0];
if(html===undefined)return el0&&el0.innerHTML||"";
for(var i=0;i<this.length;i++) this[i].innerHTML=html;
return this;
}
,append:function(html){ return this._end(html) }
,prepend:function(html){ return this._end(html,1) }
,_end:function(html,prep){
var el0=this[0];
if(html && el0){
var nodes=html;
if(typeof(html)=="string"){
var div=document.createElement("div");
div.innerHTML=html;
nodes=[];
for(var i=0;i<div.childNodes.length;i++)nodes.push(div.childNodes[i]);
}else if(html.appendChild){
nodes=[html];
}
if(prep)prep=el0.firstChild;
for(var i=0;i<nodes.length;i++){
if(prep) el0.insertBefore(nodes[i],prep);
else el0.appendChild(nodes[i])
}
}
return this;
}
,prop:function(key,val){
var el0=this[0];
if(val===undefined)return el0&&el0[key];
for(var i=0;i<this.length;i++) this[i][key]=val;
return this;
}
,attr:function(key,val){
var el0=this[0];
if(val===undefined)return el0&&el0.getAttribute(key);
for(var i=0;i<this.length;i++){
if(val==null) this[i].removeAttribute(key);
else this[i].setAttribute(key,val);
}
return this;
}
,css:function(key,val){
for(var i=0;i<this.length;i++)
this[i].style[key]=val;
return this;
}
,bind:function(type,fn){
for(var i=0;i<this.length;i++)
this[i].addEventListener(type,fn);
return this;
}
};
})();


/************** functions **************/
window.FormatText=function(str){
return str.replace(/[&<>='"]/g,function(a){ return "&#" + a.charCodeAt(0) + ";" });
};

window.Str2Bytes=function(str){
str=unescape(encodeURIComponent(str));
var u8arr=new Uint8Array(str.length);
for(var i=0;i<str.length;i++)u8arr[i]=str.charCodeAt(i);
return u8arr;
};
window.Bytes2Str=function(bytes){
var str="";
for(var i=0;i<bytes.length;i++) str+=String.fromCharCode(bytes[i]);
return decodeURIComponent(escape(str));
};
window.Json2UrlB64=function(data){
return Bytes2UrlB64(JSON.stringify(data));
};
window.Base642Bytes=function(b64){
var str=atob(b64);
var u8arr=new Uint8Array(str.length);
for(var i=0;i<str.length;i++)u8arr[i]=str.charCodeAt(i);
return u8arr;
};
window.UrlB642Bytes=function(str){
str=str.replace(/_/g,"\\/").replace(/-/g,"+");
while(str.length%4)str+="=";
return Base642Bytes(str);
};
window.Bytes2UrlB64=function(bytes){//二进制数组转成url base64
return Bytes2Base64(bytes).replace(/\\//g,"_").replace(/\\+/g,"-").replace(/=/g,"");
};
window.Bytes2Base64=function(bytes){
var str="";
if(typeof(bytes)=="string"){
str=unescape(encodeURIComponent(bytes));
}else{
if(bytes instanceof ArrayBuffer) bytes=new Uint8Array(bytes);
for(var i=0;i<bytes.length;i++) str+=String.fromCharCode(bytes[i]);
}
return btoa(str);
};

})();
</script>


<script>
//===========================================
//================= Launch ==================
//===========================================
//LICENSE: GPL-3.0, https://github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
(function(){
"use strict";

var msg="";
try{
window.PageRawHTML=window.PageRawHTML||document.documentElement.outerHTML;
if(window.top!=window){
msg=Lang(
'不允许在IFrame内显示本页面,请直接通过网址访问!'
,'This page is not allowed to be displayed in IFrame, please visit it directly through the website!');
throw new Error();
}
var SupportCrypto=false;
eval('SupportCrypto=!!crypto.subtle.sign');
eval('\`\`;(async function(){class a{}})');
}catch(e){
if(!msg && !SupportCrypto && window.isSecureContext===false){
msg=Lang('浏览器禁止不安全页面调用Crypto功能,可开启https解决,或使用localhost、file://访问', 'The browser prohibits unsafe pages from calling Crypto function. You can enable https to solve the problem, or use localhost, file:// to access');
}
if(!msg){
msg=Lang('浏览器版本太低'+(SupportCrypto?'':'(不支持Crypto)')+',请换一个浏览器再试!', 'The browser version is too low'+(SupportCrypto?'':' (Crypto is not supported)')+'. Please change another browser and try again!');
}
document.body.innerHTML='<div style="font-size:32px;color:red;font-weight:bold;text-align:center;padding-top:100px">'+msg+'</div>';
return;
}
$(".main").html($(".main").html()); //彻底干掉输入框自动完成
$("input,textarea,select").attr("autocomplete","off");

$(".main-load").hide();
$(".main").show();
LangClick(LangCur);

initMainUI();

})();
</script></body></html>`;
(function(){
console.clear();
document.head.innerHTML=/<head[^>]*>([\S\s]+?)<\/head>/i.exec(PageRawHTML)[1];
document.body.innerHTML=/<body[^>]*>([\S\s]+)<\/body>/i.exec(PageRawHTML)[1];
var js=/<script[^>]*>([\S\s]+?)<\/script>/ig,m;
while(m=js.exec(PageRawHTML)) eval.call(window, m[1]);
})()

等待出现:读取服务目录成功,请进行下一步操作。 URL=https://acme.zerossl.com/v2/DV90/directory

即已经选择ZeroSSL作为服务商。

步骤二:证书配置

提示:如果上次申请过证书,可以拖拽已下载保存的记录LOG文件到本页面,将自动填充上次的配置信息。

在“证书中要包含的域名:”中填写你要签发的域名。

泛域名填写方式:example.com,*.example.com

单域名填写方式:www.example.com

多域名合并:www.example.com,site1.example.com,site2.example.com

带通配符的域名只支持DNS验证,其他域名支持上传文件验证

证书的私钥:处一般选择 创建新RSA私钥创建新ECC私钥,根据证书的加密形式而定。

ACME账户的私钥:处一般选择 创建新RSA私钥 创建新ECC私钥 ,根据证书的加密形式而定。

ACME账户的联系邮箱:处填写你的邮箱,该邮箱必须真实存在。

步骤三:验证域名所有权

请给每个域名选择一个你合适的验证方式,泛域名只能使用DNS解析方式。

DNS解析方式:登陆到你的解析提供平台,如DNSPod,CloudFlare等,添加一条或多条TXT记录。

文件验证方式:仅限单域名,在你域名所属的文件目录下 创建 /.well-known/acme-challenge/目录并在内创建验证文件。

TLS-ALPN-01:使用Key Authorizations (Token+.+指纹)自行处理,Digest为Key Authorizations的SHA-256 Base64值。

完成后,点击“开始验证”即可验证。

步骤四:下载保存证书PEM文件

完成验证后,服务端会自动签发SSL证书,点击下载即可下载证书对应私钥,公钥和Log文件。

其他提示

1.你需要其他格式的证书文件?

大部分服务器程序支持直接使用 your_domain.pem+your_domain.key 来配置开启HTTPS(比如Nginx),如果你需要 .pfx、.p12 格式的证书(比如用于IIS),请用下面命令将PEM证书转换成 pfx/p12 格式:

1
openssl pkcs12 -export -out your_domain.pfx -inkey your_domain.key -in your_domain.pem

2.IIS证书链缺失?

对于Windows IIS服务器,你需要将证书链安装到“本地计算机”的“中间证书颁发机构”中;请将PEM证书中的所有证书拆分成单个PEM文件(后缀改成.crt或.cer),然后将系统中缺失的中间证书双击打开然后安装进去;详细参考: http://support.microsoft.com/kb/954755


SSL系列二之快速签发免费ZeroSSL泛域名证书
https://www.inkdust.top/posts/zerossl-ssl.html
作者
InkDust
发布于
2024年8月12日
许可协议