Сертификаты: срок действия — по сроку закрытого ключа (PrivateKeyUsagePeriod)
В ГОСТ-сертификатах ключ подписи действует год–15 месяцев, а сам сертификат — до 15 лет; bj показывал и проверял срок сертификата (например, 06.10.2036 вместо 2027). Теперь справочник профиля ИШ и сертификаты с флешки берут срок ключа (2.5.29.16), если он раньше; срок сертификата показывается рядом. У УЦ — по-прежнему срок сертификата. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
@@ -95,6 +95,25 @@ certs_list_json(){
|
|||||||
python3 - "$PROFDIR/local.pse" "$PROFDIR/local.gdbm" <<'PY2'
|
python3 - "$PROFDIR/local.pse" "$PROFDIR/local.gdbm" <<'PY2'
|
||||||
import sys,subprocess,json,re
|
import sys,subprocess,json,re
|
||||||
out=[]; seen=set()
|
out=[]; seen=set()
|
||||||
|
MON=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
|
||||||
|
def key_not_after(b):
|
||||||
|
# срок закрытого ключа — расширение PrivateKeyUsagePeriod (2.5.29.16), [1] notAfter GeneralizedTime;
|
||||||
|
# у ГОСТ-сертификатов он короче срока самого сертификата (ключ подписи — год, сертификат — до 15 лет)
|
||||||
|
k=b.find(b'\x06\x03\x55\x1d\x10')
|
||||||
|
if k<0: return ''
|
||||||
|
j=k+5
|
||||||
|
try:
|
||||||
|
if b[j]==0x01: j+=3 # critical BOOLEAN
|
||||||
|
if b[j]!=0x04: return ''
|
||||||
|
j+=2 if b[j+1]<0x80 else 2+(b[j+1]&0x7f) # OCTET STRING
|
||||||
|
if b[j]!=0x30: return ''
|
||||||
|
end=j+2+b[j+1]; j+=2
|
||||||
|
while j<end:
|
||||||
|
t,n=b[j],b[j+1]; v=b[j+2:j+2+n].decode('ascii','replace'); j+=2+n
|
||||||
|
if t==0x81 and len(v)>=14:
|
||||||
|
return '%s %d %s:%s:%s %s GMT'%(MON[int(v[4:6])-1],int(v[6:8]),v[8:10],v[10:12],v[12:14],v[0:4])
|
||||||
|
except Exception: pass
|
||||||
|
return ''
|
||||||
for p in sys.argv[1:]:
|
for p in sys.argv[1:]:
|
||||||
store=p.rsplit('.',1)[-1]
|
store=p.rsplit('.',1)[-1]
|
||||||
try: d=open(p,'rb').read()
|
try: d=open(p,'rb').read()
|
||||||
@@ -112,7 +131,7 @@ for p in sys.argv[1:]:
|
|||||||
sn=g('serial').upper()
|
sn=g('serial').upper()
|
||||||
if sn in seen: i+=4; continue
|
if sn in seen: i+=4; continue
|
||||||
seen.add(sn)
|
seen.add(sn)
|
||||||
out.append({'kind':'x509','store':store,'subject':g('subject'),'subject_cn':cn(g('subject')),'issuer_cn':cn(g('issuer')),'serial':sn,'not_before':g('notBefore'),'not_after':g('notAfter'),'self_signed':g('subject')==g('issuer')})
|
out.append({'kind':'x509','store':store,'subject':g('subject'),'subject_cn':cn(g('subject')),'issuer_cn':cn(g('issuer')),'serial':sn,'not_before':g('notBefore'),'not_after':g('notAfter'),'key_not_after':key_not_after(b),'self_signed':g('subject')==g('issuer')})
|
||||||
else:
|
else:
|
||||||
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-nextupdate','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-nextupdate','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
|
||||||
if r.returncode==0:
|
if r.returncode==0:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@@ -242,10 +243,32 @@ func classifyCertFile(path string) *CertFile {
|
|||||||
cf.Serial = cert.SerialNumber.Text(16)
|
cf.Serial = cert.SerialNumber.Text(16)
|
||||||
cf.NotBefore = cert.NotBefore
|
cf.NotBefore = cert.NotBefore
|
||||||
cf.NotAfter = cert.NotAfter
|
cf.NotAfter = cert.NotAfter
|
||||||
|
if k := privateKeyUsageEnd(cert.Extensions); !k.IsZero() && k.Before(cf.NotAfter) {
|
||||||
|
cf.NotAfter = k // подписывать можно до конца срока ключа, а не сертификата
|
||||||
|
}
|
||||||
cf.INN = extractCertINN(cert)
|
cf.INN = extractCertINN(cert)
|
||||||
return cf
|
return cf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// privateKeyUsageEnd — конец срока закрытого ключа (расширение PrivateKeyUsagePeriod, 2.5.29.16). В ГОСТ-
|
||||||
|
// сертификатах ключ подписи действует год–15 месяцев, а сам сертификат — до 15 лет; подписывать после
|
||||||
|
// срока ключа нельзя. Нулевое время — расширения нет.
|
||||||
|
func privateKeyUsageEnd(exts []pkix.Extension) time.Time {
|
||||||
|
for _, e := range exts {
|
||||||
|
if !e.Id.Equal(asn1.ObjectIdentifier{2, 5, 29, 16}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var p struct {
|
||||||
|
NotBefore time.Time `asn1:"generalized,optional,tag:0"`
|
||||||
|
NotAfter time.Time `asn1:"generalized,optional,tag:1"`
|
||||||
|
}
|
||||||
|
if _, err := asn1.Unmarshal(e.Value, &p); err == nil {
|
||||||
|
return p.NotAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
// extractCertINN — ИНН из OID 1.2.643.3.131.1.1 в Subject.
|
// extractCertINN — ИНН из OID 1.2.643.3.131.1.1 в Subject.
|
||||||
func extractCertINN(c *x509.Certificate) string {
|
func extractCertINN(c *x509.Certificate) string {
|
||||||
innOID := asn1.ObjectIdentifier{1, 2, 643, 3, 131, 1, 1}
|
innOID := asn1.ObjectIdentifier{1, 2, 643, 3, 131, 1, 1}
|
||||||
|
|||||||
@@ -233,8 +233,12 @@ type NSDStoreCert struct {
|
|||||||
IssuerCN string `json:"issuer_cn"`
|
IssuerCN string `json:"issuer_cn"`
|
||||||
Serial string `json:"serial"`
|
Serial string `json:"serial"`
|
||||||
NotBefore string `json:"not_before"`
|
NotBefore string `json:"not_before"`
|
||||||
NotAfter string `json:"not_after"`
|
NotAfter string `json:"not_after"` // до какой даты действует: срок ключа подписи, если он раньше срока сертификата
|
||||||
SelfSigned bool `json:"self_signed"`
|
SelfSigned bool `json:"self_signed"`
|
||||||
|
// KeyNotAfter — срок закрытого ключа (PrivateKeyUsagePeriod); CertNotAfter — срок самого сертификата,
|
||||||
|
// если он позже ключа (в ГОСТ-сертификате ключ подписи действует год, сертификат — до 15 лет)
|
||||||
|
KeyNotAfter string `json:"key_not_after,omitempty"`
|
||||||
|
CertNotAfter string `json:"cert_not_after,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func nsdStoreFromResult(res map[string]any) []NSDStoreCert {
|
func nsdStoreFromResult(res map[string]any) []NSDStoreCert {
|
||||||
@@ -244,6 +248,13 @@ func nsdStoreFromResult(res map[string]any) []NSDStoreCert {
|
|||||||
for i := range st {
|
for i := range st {
|
||||||
st[i].NotBefore = nsdPrettyDate(st[i].NotBefore)
|
st[i].NotBefore = nsdPrettyDate(st[i].NotBefore)
|
||||||
st[i].NotAfter = nsdPrettyDate(st[i].NotAfter)
|
st[i].NotAfter = nsdPrettyDate(st[i].NotAfter)
|
||||||
|
st[i].KeyNotAfter = nsdPrettyDate(st[i].KeyNotAfter)
|
||||||
|
k, kerr := time.Parse("02.01.2006", st[i].KeyNotAfter)
|
||||||
|
c, cerr := time.Parse("02.01.2006", st[i].NotAfter)
|
||||||
|
// у УЦ срок ключа — только для выпуска новых сертификатов; проверять им можно весь срок сертификата
|
||||||
|
if kerr == nil && !st[i].SelfSigned && (cerr != nil || k.Before(c)) {
|
||||||
|
st[i].CertNotAfter, st[i].NotAfter = st[i].NotAfter, st[i].KeyNotAfter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,7 +314,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if .NSDCerts.LastLog}}<pre style="margin-top:10px;font-size:11.5px;white-space:pre-wrap">{{.NSDCerts.LastLog}}</pre>{{end}}
|
{{if .NSDCerts.LastLog}}<pre style="margin-top:10px;font-size:11.5px;white-space:pre-wrap">{{.NSDCerts.LastLog}}</pre>{{end}}
|
||||||
{{if .NSDCerts.Store}}<details style="margin-top:8px"><summary class="muted" style="cursor:pointer;font-size:12px">Содержимое справочника профиля ({{len .NSDCerts.Store}} записей)</summary>
|
{{if .NSDCerts.Store}}<details style="margin-top:8px"><summary class="muted" style="cursor:pointer;font-size:12px">Содержимое справочника профиля ({{len .NSDCerts.Store}} записей)</summary>
|
||||||
<table style="margin-top:8px;font-size:12px"><thead><tr><th>Владелец</th><th>Издатель</th><th>Серийный</th><th>До</th><th>Хранилище</th></tr></thead><tbody>{{range .NSDCerts.Store}}<tr><td>{{if eq .Kind "crl"}}<span class="muted">CRL</span>{{else}}{{.SubjectCN}}{{if .SelfSigned}} <span class="muted">(УЦ)</span>{{end}}{{end}}</td><td>{{.IssuerCN}}</td><td><code>{{.Serial}}</code></td><td>{{.NotAfter}}</td><td>{{.Store}}</td></tr>{{end}}</tbody></table></details>{{end}}
|
<table style="margin-top:8px;font-size:12px"><thead><tr><th>Владелец</th><th>Издатель</th><th>Серийный</th><th>До</th><th>Хранилище</th></tr></thead><tbody>{{range .NSDCerts.Store}}<tr><td>{{if eq .Kind "crl"}}<span class="muted">CRL</span>{{else}}{{.SubjectCN}}{{if .SelfSigned}} <span class="muted">(УЦ)</span>{{end}}{{end}}</td><td>{{.IssuerCN}}</td><td><code>{{.Serial}}</code></td><td>{{.NotAfter}}{{if .CertNotAfter}}<div class="muted" style="font-size:11px" title="Срок закрытого ключа (PrivateKeyUsagePeriod): подписывать можно до {{.NotAfter}}. Срок самого сертификата — {{.CertNotAfter}}.">ключ; сертификат до {{.CertNotAfter}}</div>{{end}}</td><td>{{.Store}}</td></tr>{{end}}</tbody></table></details>{{end}}
|
||||||
<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>
|
<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user