-
Notifications
You must be signed in to change notification settings - Fork 85
/
precerts.go
154 lines (139 loc) · 5.14 KB
/
precerts.go
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
// Copyright (C) 2016 Opsmate, Inc.
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License, v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This software is distributed WITHOUT A WARRANTY OF ANY KIND.
// See the Mozilla Public License for details.
package certspotter
import (
"bytes"
"encoding/asn1"
"errors"
"fmt"
)
func bitStringEqual(a, b *asn1.BitString) bool {
return a.BitLength == b.BitLength && bytes.Equal(a.Bytes, b.Bytes)
}
var (
oidExtensionAuthorityKeyId = []int{2, 5, 29, 35}
oidExtensionSCT = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
oidExtensionCTPoison = []int{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
)
type PrecertInfo struct {
SameIssuer bool // The pre-certificate was issued from the same CA as the final certificate
Issuer []byte // The pre-certificate's issuer, if different from the final certificate
AKI []byte // The pre-certificate's AKI, if present and different from the final certificate
}
func ValidatePrecert(precertBytes []byte, tbsBytes []byte) (*PrecertInfo, error) {
precert, err := ParseCertificate(precertBytes)
if err != nil {
return nil, errors.New("failed to parse pre-certificate: " + err.Error())
}
precertTBS, err := precert.ParseTBSCertificate()
if err != nil {
return nil, errors.New("failed to parse pre-certificate TBS: " + err.Error())
}
tbs, err := ParseTBSCertificate(tbsBytes)
if err != nil {
return nil, errors.New("failed to parse TBS: " + err.Error())
}
// Everything must be equal except:
// issuer
// Authority Key Identifier extension (both must have it OR neither can have it)
// CT poison extension (precert must have it, TBS must not have it)
if precertTBS.Version != tbs.Version {
return nil, errors.New("version not equal")
}
if !bytes.Equal(precertTBS.SerialNumber.FullBytes, tbs.SerialNumber.FullBytes) {
return nil, errors.New("serial number not equal")
}
sameIssuer := bytes.Equal(precertTBS.Issuer.FullBytes, tbs.Issuer.FullBytes)
if !bytes.Equal(precertTBS.SignatureAlgorithm.FullBytes, tbs.SignatureAlgorithm.FullBytes) {
return nil, errors.New("SignatureAlgorithm not equal")
}
if !bytes.Equal(precertTBS.Validity.FullBytes, tbs.Validity.FullBytes) {
return nil, errors.New("Validity not equal")
}
if !bytes.Equal(precertTBS.Subject.FullBytes, tbs.Subject.FullBytes) {
return nil, errors.New("Subject not equal")
}
if !bytes.Equal(precertTBS.PublicKey.FullBytes, tbs.PublicKey.FullBytes) {
return nil, errors.New("PublicKey not equal")
}
if !bitStringEqual(&precertTBS.UniqueId, &tbs.UniqueId) {
return nil, errors.New("UniqueId not equal")
}
if !bitStringEqual(&precertTBS.SubjectUniqueId, &tbs.SubjectUniqueId) {
return nil, errors.New("SubjectUniqueId not equal")
}
precertHasPoison := false
tbsIndex := 0
var aki []byte
for precertIndex := range precertTBS.Extensions {
precertExt := &precertTBS.Extensions[precertIndex]
if precertExt.Id.Equal(oidExtensionCTPoison) {
if !precertExt.Critical {
return nil, errors.New("pre-cert poison extension is not critical")
}
/* CAs can't even get this right, and Google's logs don't check. Fortunately,
it's not that important.
if !bytes.Equal(precertExt.Value, []byte{0x05, 0x00}) {
return errors.New("pre-cert poison extension contains incorrect value")
}
*/
precertHasPoison = true
continue
}
if tbsIndex >= len(tbs.Extensions) {
return nil, errors.New("pre-cert contains extension not in TBS")
}
tbsExt := &tbs.Extensions[tbsIndex]
if !precertExt.Id.Equal(tbsExt.Id) {
return nil, fmt.Errorf("pre-cert and TBS contain different extensions (%v vs %v)", precertExt.Id, tbsExt.Id)
}
if precertExt.Critical != tbsExt.Critical {
return nil, fmt.Errorf("pre-cert and TBS %v extension differs in criticality", precertExt.Id)
}
if !sameIssuer && precertExt.Id.Equal(oidExtensionAuthorityKeyId) {
aki = precertExt.Value
} else {
if !bytes.Equal(precertExt.Value, tbsExt.Value) {
return nil, fmt.Errorf("pre-cert and TBS %v extension differs in value", precertExt.Id)
}
}
tbsIndex++
}
if tbsIndex < len(tbs.Extensions) {
return nil, errors.New("TBS contains extension not in pre-cert")
}
if !precertHasPoison {
return nil, errors.New("pre-cert does not have poison extension")
}
return &PrecertInfo{SameIssuer: sameIssuer, Issuer: precertTBS.Issuer.FullBytes, AKI: aki}, nil
}
func ReconstructPrecertTBS(tbs *TBSCertificate) (*TBSCertificate, error) {
precertTBS := TBSCertificate{
Version: tbs.Version,
SerialNumber: tbs.SerialNumber,
SignatureAlgorithm: tbs.SignatureAlgorithm,
Issuer: tbs.Issuer,
Validity: tbs.Validity,
Subject: tbs.Subject,
PublicKey: tbs.PublicKey,
UniqueId: tbs.UniqueId,
SubjectUniqueId: tbs.SubjectUniqueId,
Extensions: make([]Extension, 0, len(tbs.Extensions)),
}
for _, ext := range tbs.Extensions {
switch {
case ext.Id.Equal(oidExtensionSCT):
default:
precertTBS.Extensions = append(precertTBS.Extensions, ext)
}
}
var err error
precertTBS.Raw, err = asn1.Marshal(precertTBS)
return &precertTBS, err
}