File: precerts.go

package info (click to toggle)
certspotter 0.9-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 340 kB
  • sloc: makefile: 7
file content (154 lines) | stat: -rw-r--r-- 5,265 bytes parent folder | download | duplicates (4)
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
}