mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
243 lines
8.2 KiB
Go
243 lines
8.2 KiB
Go
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ofdgo
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/asn1"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/emmansun/gmsm/smx509"
|
|
)
|
|
|
|
// signatureCRL 已验证的直接CRL及目标条目
|
|
type signatureCRL struct {
|
|
list *smx509.RevocationList
|
|
baseNumber *big.Int
|
|
authority []byte
|
|
entry *smx509.RevocationListEntry
|
|
}
|
|
|
|
// verifySignatureCRLs 验证完整CRL及增量合并后的目标状态
|
|
// 入参: raws CRL字节, cert 证书, issuer 颁发者, at 验证时间, maxAge 最大年龄
|
|
// 返回: SignatureRevocationReport 吊销结果
|
|
func verifySignatureCRLs(raws [][]byte, cert, issuer *smx509.Certificate, at time.Time, maxAge time.Duration) SignatureRevocationReport {
|
|
result := SignatureRevocationReport{Certificate: signatureCertInfo(cert.Raw), Source: "CRL", Status: "unknown"}
|
|
lists := make([]signatureCRL, 0, len(raws))
|
|
for _, raw := range raws {
|
|
item, err := readSignatureCRL(raw, cert, issuer, at)
|
|
if err != nil {
|
|
result.Error = err.Error()
|
|
continue
|
|
}
|
|
lists = append(lists, item)
|
|
}
|
|
sort.Slice(lists, func(i, j int) bool {
|
|
if !lists[i].list.ThisUpdate.Equal(lists[j].list.ThisUpdate) {
|
|
return lists[i].list.ThisUpdate.Before(lists[j].list.ThisUpdate)
|
|
}
|
|
return lists[i].list.Number.Cmp(lists[j].list.Number) < 0
|
|
})
|
|
versions := make(map[struct {
|
|
number string
|
|
delta bool
|
|
}]*smx509.RevocationList, len(lists))
|
|
bases := make(map[string][]*signatureCRL)
|
|
for n := range lists {
|
|
item := &lists[n]
|
|
if n != 0 {
|
|
previous := lists[n-1].list
|
|
order := item.list.Number.Cmp(previous.Number)
|
|
if order < 0 || order == 0 && !item.list.ThisUpdate.Equal(previous.ThisUpdate) {
|
|
result.Error = "inconsistent CRL numbering"
|
|
return result
|
|
}
|
|
}
|
|
key := struct {
|
|
number string
|
|
delta bool
|
|
}{item.list.Number.String(), item.baseNumber != nil}
|
|
if previous := versions[key]; previous != nil && !bytes.Equal(item.list.RawTBSRevocationList, previous.RawTBSRevocationList) {
|
|
result.Error = "conflicting CRL version"
|
|
return result
|
|
}
|
|
versions[key] = item.list
|
|
if item.baseNumber == nil {
|
|
id := string(item.authority)
|
|
bases[id] = append(bases[id], item)
|
|
}
|
|
}
|
|
var selected *signatureCRL
|
|
var selectedEntry *smx509.RevocationListEntry
|
|
for n := range lists {
|
|
item := &lists[n]
|
|
if !signatureEvidenceFresh(item.list.ThisUpdate, item.list.NextUpdate, at, maxAge) {
|
|
result.Error = "stale or future CRL"
|
|
continue
|
|
}
|
|
entry := item.entry
|
|
if item.baseNumber != nil {
|
|
candidates := bases[string(item.authority)]
|
|
index := sort.Search(len(candidates), func(n int) bool {
|
|
return candidates[n].list.Number.Cmp(item.list.Number) >= 0
|
|
})
|
|
if index == 0 || candidates[index-1].list.Number.Cmp(item.baseNumber) < 0 {
|
|
result.Error = "matching complete CRL not found"
|
|
continue
|
|
}
|
|
if entry == nil {
|
|
entry = candidates[index-1].entry
|
|
}
|
|
}
|
|
if selected != nil && item.list.Number.Cmp(selected.list.Number) == 0 && !signatureCRLStatusEqual(entry, selectedEntry) {
|
|
result.Error = "conflicting complete and delta CRL status"
|
|
return result
|
|
}
|
|
selected, selectedEntry = item, entry
|
|
}
|
|
if selected == nil {
|
|
if result.Error == "" {
|
|
result.Error = "usable CRL not found"
|
|
}
|
|
return result
|
|
}
|
|
result.ThisUpdate, result.NextUpdate = selected.list.ThisUpdate, selected.list.NextUpdate
|
|
result.Checked, result.OK, result.Status, result.Error = true, true, "good", ""
|
|
if selectedEntry != nil && selectedEntry.ReasonCode != 8 {
|
|
result.OK, result.Status, result.RevokedAt = false, "revoked", selectedEntry.RevocationTime
|
|
}
|
|
return result
|
|
}
|
|
|
|
// readSignatureCRL 校验直接CRL签名、扩展及目标条目
|
|
// 入参: raw CRL字节, cert 证书, issuer 颁发者, at 验证时间
|
|
// 返回: signatureCRL 已验证列表, error 校验错误
|
|
func readSignatureCRL(raw []byte, cert, issuer *smx509.Certificate, at time.Time) (signatureCRL, error) {
|
|
crl, err := smx509.ParseRevocationList(raw)
|
|
if err != nil {
|
|
return signatureCRL{}, err
|
|
}
|
|
if len(crl.Raw) != len(raw) {
|
|
return signatureCRL{}, fmt.Errorf("trailing CRL data")
|
|
}
|
|
if !bytes.Equal(crl.RawIssuer, issuer.RawSubject) {
|
|
return signatureCRL{}, fmt.Errorf("CRL issuer mismatch")
|
|
}
|
|
if len(crl.AuthorityKeyId) != 0 && !bytes.Equal(crl.AuthorityKeyId, issuer.SubjectKeyId) {
|
|
return signatureCRL{}, fmt.Errorf("CRL authority key mismatch")
|
|
}
|
|
if err := crl.CheckSignatureFrom(issuer); err != nil {
|
|
return signatureCRL{}, err
|
|
}
|
|
if crl.ThisUpdate.IsZero() || crl.ThisUpdate.After(at) || !crl.NextUpdate.IsZero() && !crl.NextUpdate.After(crl.ThisUpdate) {
|
|
return signatureCRL{}, fmt.Errorf("invalid CRL time window")
|
|
}
|
|
item := signatureCRL{list: crl}
|
|
for n, ext := range crl.Extensions {
|
|
for _, previous := range crl.Extensions[:n] {
|
|
if ext.Id.Equal(previous.Id) {
|
|
return signatureCRL{}, fmt.Errorf("duplicate CRL extension")
|
|
}
|
|
}
|
|
switch ext.Id.String() {
|
|
case "2.5.29.35":
|
|
if ext.Critical {
|
|
return signatureCRL{}, fmt.Errorf("invalid CRL authority key extension")
|
|
}
|
|
item.authority = ext.Value
|
|
case "2.5.29.20":
|
|
number, err := signatureCRLNumber(ext.Value)
|
|
if err != nil || ext.Critical {
|
|
return signatureCRL{}, fmt.Errorf("invalid CRL number")
|
|
}
|
|
crl.Number = number
|
|
case "2.5.29.27":
|
|
number, err := signatureCRLNumber(ext.Value)
|
|
if err != nil || !ext.Critical {
|
|
return signatureCRL{}, fmt.Errorf("invalid delta CRL indicator")
|
|
}
|
|
item.baseNumber = number
|
|
case "2.5.29.28":
|
|
return signatureCRL{}, fmt.Errorf("scoped CRL not supported")
|
|
default:
|
|
if ext.Critical {
|
|
return signatureCRL{}, fmt.Errorf("unsupported critical CRL extension")
|
|
}
|
|
}
|
|
}
|
|
if crl.Number == nil || item.baseNumber != nil && item.baseNumber.Cmp(crl.Number) >= 0 {
|
|
return signatureCRL{}, fmt.Errorf("invalid CRL numbering")
|
|
}
|
|
for n := range crl.RevokedCertificateEntries {
|
|
entry := &crl.RevokedCertificateEntries[n]
|
|
for m, ext := range entry.Extensions {
|
|
for _, previous := range entry.Extensions[:m] {
|
|
if ext.Id.Equal(previous.Id) {
|
|
return signatureCRL{}, fmt.Errorf("duplicate CRL entry extension")
|
|
}
|
|
}
|
|
if ext.Critical || ext.Id.String() == "2.5.29.29" {
|
|
return signatureCRL{}, fmt.Errorf("unsupported CRL entry extension")
|
|
}
|
|
if ext.Id.String() == "2.5.29.21" {
|
|
var reason asn1.Enumerated
|
|
rest, err := asn1.Unmarshal(ext.Value, &reason)
|
|
if err != nil || len(rest) != 0 || reason < 0 || reason > 10 || reason == 7 {
|
|
return signatureCRL{}, fmt.Errorf("invalid CRL reason code")
|
|
}
|
|
}
|
|
}
|
|
if entry.ReasonCode == 8 && item.baseNumber == nil {
|
|
return signatureCRL{}, fmt.Errorf("removeFromCRL requires delta processing")
|
|
}
|
|
if entry.RevocationTime.After(crl.ThisUpdate) {
|
|
return signatureCRL{}, fmt.Errorf("CRL revocation time is in the future")
|
|
}
|
|
if entry.SerialNumber.Cmp(cert.SerialNumber) == 0 {
|
|
if item.entry != nil {
|
|
return signatureCRL{}, fmt.Errorf("duplicate CRL certificate entry")
|
|
}
|
|
item.entry = entry
|
|
}
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
// signatureCRLNumber 解析非负CRL编号
|
|
// 入参: raw ASN.1整数
|
|
// 返回: *big.Int 编号, error 编码错误
|
|
func signatureCRLNumber(raw []byte) (*big.Int, error) {
|
|
var number *big.Int
|
|
rest, err := asn1.Unmarshal(raw, &number)
|
|
if err != nil || len(rest) != 0 || number.Sign() < 0 {
|
|
return nil, fmt.Errorf("invalid CRL number encoding")
|
|
}
|
|
return number, nil
|
|
}
|
|
|
|
// signatureCRLStatusEqual 比较完整及增量列表的目标状态
|
|
// 入参: a 首个条目, b 第二个条目
|
|
// 返回: bool 状态是否一致
|
|
func signatureCRLStatusEqual(a, b *smx509.RevocationListEntry) bool {
|
|
aGood, bGood := a == nil || a.ReasonCode == 8, b == nil || b.ReasonCode == 8
|
|
if aGood || bGood {
|
|
return aGood == bGood
|
|
}
|
|
return a.ReasonCode == b.ReasonCode && a.RevocationTime.Equal(b.RevocationTime)
|
|
}
|