Switch to loglist3 package for parsing CT log list (#7930)
The schema tool used to parse log_list_schema.json doesn't work well with the updated schema. This is going to be required to support static-ct-api logs from current Chrome log lists. Instead, use the loglist3 package inside the certificate-transparency-go project, which Boulder already uses for CT submission otherwise. As well, the Log IDs and keys returned from loglist3 have already been base64 decoded, so this re-encodes them to minimize the impact on the rest of the codebase and keep this change small. The test log_list.json file needed to be made a bit more realistic for loglist3 to parse without base64 or date parsing errors.
This commit is contained in:
parent
e4668b4ca7
commit
8a01611b70
|
@ -2,7 +2,7 @@ package loglist
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
@ -10,7 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/ctpolicy/loglist/schema"
|
||||
"github.com/google/certificate-transparency-go/loglist3"
|
||||
)
|
||||
|
||||
// purpose is the use to which a log list will be put. This type exists to allow
|
||||
|
@ -52,53 +52,19 @@ type Log struct {
|
|||
Key string
|
||||
StartInclusive time.Time
|
||||
EndExclusive time.Time
|
||||
State state
|
||||
}
|
||||
|
||||
// State is an enum representing the various states a CT log can be in. Only
|
||||
// pending, qualified, and usable logs can be submitted to. Only usable and
|
||||
// readonly logs are trusted by Chrome.
|
||||
type state int
|
||||
|
||||
const (
|
||||
unknown state = iota
|
||||
pending
|
||||
qualified
|
||||
usable
|
||||
readonly
|
||||
retired
|
||||
rejected
|
||||
)
|
||||
|
||||
func stateFromState(s *schema.LogListSchemaJsonOperatorsElemLogsElemState) state {
|
||||
if s == nil {
|
||||
return unknown
|
||||
} else if s.Rejected != nil {
|
||||
return rejected
|
||||
} else if s.Retired != nil {
|
||||
return retired
|
||||
} else if s.Readonly != nil {
|
||||
return readonly
|
||||
} else if s.Pending != nil {
|
||||
return pending
|
||||
} else if s.Qualified != nil {
|
||||
return qualified
|
||||
} else if s.Usable != nil {
|
||||
return usable
|
||||
}
|
||||
return unknown
|
||||
State loglist3.LogStatus
|
||||
}
|
||||
|
||||
// usableForPurpose returns true if the log state is acceptable for the given
|
||||
// log list purpose, and false otherwise.
|
||||
func usableForPurpose(s state, p purpose) bool {
|
||||
func usableForPurpose(s loglist3.LogStatus, p purpose) bool {
|
||||
switch p {
|
||||
case Issuance:
|
||||
return s == usable
|
||||
return s == loglist3.UsableLogStatus
|
||||
case Informational:
|
||||
return s == usable || s == qualified || s == pending
|
||||
return s == loglist3.UsableLogStatus || s == loglist3.QualifiedLogStatus || s == loglist3.PendingLogStatus
|
||||
case Validation:
|
||||
return s == usable || s == readonly
|
||||
return s == loglist3.UsableLogStatus || s == loglist3.ReadOnlyLogStatus
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -118,8 +84,7 @@ func New(path string) (List, error) {
|
|||
// newHelper is a helper to allow the core logic of `New()` to be unit tested
|
||||
// without having to write files to disk.
|
||||
func newHelper(file []byte) (List, error) {
|
||||
var parsed schema.LogListSchemaJson
|
||||
err := json.Unmarshal(file, &parsed)
|
||||
parsed, err := loglist3.NewFromJSON(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse CT Log List: %w", err)
|
||||
}
|
||||
|
@ -128,34 +93,19 @@ func newHelper(file []byte) (List, error) {
|
|||
for _, op := range parsed.Operators {
|
||||
group := make(OperatorGroup)
|
||||
for _, log := range op.Logs {
|
||||
var name string
|
||||
if log.Description != nil {
|
||||
name = *log.Description
|
||||
}
|
||||
|
||||
info := Log{
|
||||
Name: name,
|
||||
Url: log.Url,
|
||||
Key: log.Key,
|
||||
State: stateFromState(log.State),
|
||||
Name: log.Description,
|
||||
Url: log.URL,
|
||||
Key: base64.StdEncoding.EncodeToString(log.Key),
|
||||
State: log.State.LogStatus(),
|
||||
}
|
||||
|
||||
if log.TemporalInterval != nil {
|
||||
startInclusive, err := time.Parse(time.RFC3339, log.TemporalInterval.StartInclusive)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse log %q start timestamp: %w", log.Url, err)
|
||||
}
|
||||
|
||||
endExclusive, err := time.Parse(time.RFC3339, log.TemporalInterval.EndExclusive)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse log %q end timestamp: %w", log.Url, err)
|
||||
}
|
||||
|
||||
info.StartInclusive = startInclusive
|
||||
info.EndExclusive = endExclusive
|
||||
info.StartInclusive = log.TemporalInterval.StartInclusive
|
||||
info.EndExclusive = log.TemporalInterval.EndExclusive
|
||||
}
|
||||
|
||||
group[log.LogId] = info
|
||||
group[base64.StdEncoding.EncodeToString(log.LogID)] = info
|
||||
}
|
||||
result[op.Name] = group
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/certificate-transparency-go/loglist3"
|
||||
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
@ -56,24 +58,24 @@ func TestSubset(t *testing.T) {
|
|||
func TestForPurpose(t *testing.T) {
|
||||
input := List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A2": Log{Name: "Log A2", State: rejected},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
"ID A2": Log{Name: "Log A2", State: loglist3.RejectedLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: usable},
|
||||
"ID B2": Log{Name: "Log B2", State: retired},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.UsableLogStatus},
|
||||
"ID B2": Log{Name: "Log B2", State: loglist3.RetiredLogStatus},
|
||||
},
|
||||
"Operator C": {
|
||||
"ID C1": Log{Name: "Log C1", State: pending},
|
||||
"ID C2": Log{Name: "Log C2", State: readonly},
|
||||
"ID C1": Log{Name: "Log C1", State: loglist3.PendingLogStatus},
|
||||
"ID C2": Log{Name: "Log C2", State: loglist3.ReadOnlyLogStatus},
|
||||
},
|
||||
}
|
||||
expected := List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: usable},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.UsableLogStatus},
|
||||
},
|
||||
}
|
||||
actual, err := input.forPurpose(Issuance)
|
||||
|
@ -82,16 +84,16 @@ func TestForPurpose(t *testing.T) {
|
|||
|
||||
input = List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A2": Log{Name: "Log A2", State: rejected},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
"ID A2": Log{Name: "Log A2", State: loglist3.RejectedLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: qualified},
|
||||
"ID B2": Log{Name: "Log B2", State: retired},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.QualifiedLogStatus},
|
||||
"ID B2": Log{Name: "Log B2", State: loglist3.RetiredLogStatus},
|
||||
},
|
||||
"Operator C": {
|
||||
"ID C1": Log{Name: "Log C1", State: pending},
|
||||
"ID C2": Log{Name: "Log C2", State: readonly},
|
||||
"ID C1": Log{Name: "Log C1", State: loglist3.PendingLogStatus},
|
||||
"ID C2": Log{Name: "Log C2", State: loglist3.ReadOnlyLogStatus},
|
||||
},
|
||||
}
|
||||
_, err = input.forPurpose(Issuance)
|
||||
|
@ -99,10 +101,10 @@ func TestForPurpose(t *testing.T) {
|
|||
|
||||
expected = List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
},
|
||||
"Operator C": {
|
||||
"ID C2": Log{Name: "Log C2", State: readonly},
|
||||
"ID C2": Log{Name: "Log C2", State: loglist3.ReadOnlyLogStatus},
|
||||
},
|
||||
}
|
||||
actual, err = input.forPurpose(Validation)
|
||||
|
@ -111,13 +113,13 @@ func TestForPurpose(t *testing.T) {
|
|||
|
||||
expected = List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: qualified},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.QualifiedLogStatus},
|
||||
},
|
||||
"Operator C": {
|
||||
"ID C1": Log{Name: "Log C1", State: pending},
|
||||
"ID C1": Log{Name: "Log C1", State: loglist3.PendingLogStatus},
|
||||
},
|
||||
}
|
||||
actual, err = input.forPurpose(Informational)
|
||||
|
@ -128,10 +130,10 @@ func TestForPurpose(t *testing.T) {
|
|||
func TestOperatorForLogID(t *testing.T) {
|
||||
input := List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: qualified},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.QualifiedLogStatus},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -146,16 +148,16 @@ func TestOperatorForLogID(t *testing.T) {
|
|||
func TestPermute(t *testing.T) {
|
||||
input := List{
|
||||
"Operator A": {
|
||||
"ID A1": Log{Name: "Log A1", State: usable},
|
||||
"ID A2": Log{Name: "Log A2", State: rejected},
|
||||
"ID A1": Log{Name: "Log A1", State: loglist3.UsableLogStatus},
|
||||
"ID A2": Log{Name: "Log A2", State: loglist3.RejectedLogStatus},
|
||||
},
|
||||
"Operator B": {
|
||||
"ID B1": Log{Name: "Log B1", State: qualified},
|
||||
"ID B2": Log{Name: "Log B2", State: retired},
|
||||
"ID B1": Log{Name: "Log B1", State: loglist3.QualifiedLogStatus},
|
||||
"ID B2": Log{Name: "Log B2", State: loglist3.RetiredLogStatus},
|
||||
},
|
||||
"Operator C": {
|
||||
"ID C1": Log{Name: "Log C1", State: pending},
|
||||
"ID C2": Log{Name: "Log C2", State: readonly},
|
||||
"ID C1": Log{Name: "Log C1", State: loglist3.PendingLogStatus},
|
||||
"ID C2": Log{Name: "Log C2", State: loglist3.ReadOnlyLogStatus},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
{
|
||||
"type": "object",
|
||||
"id": "https://www.gstatic.com/ct/log_list/v3/log_list_schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"required": [
|
||||
"operators"
|
||||
],
|
||||
"definitions": {
|
||||
"state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timestamp": {
|
||||
"description": "The time at which the log entered this state.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2018-01-01T00:00:00Z"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"timestamp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"title": "Version of this log list",
|
||||
"description": "The version will change whenever a change is made to any part of this log list.",
|
||||
"examples": [
|
||||
"1",
|
||||
"1.0.0",
|
||||
"1.0.0b"
|
||||
]
|
||||
},
|
||||
"log_list_timestamp": {
|
||||
"description": "The time at which this version of the log list was published.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2018-01-01T00:00:00Z"
|
||||
]
|
||||
},
|
||||
"operators": {
|
||||
"title": "CT log operators",
|
||||
"description": "People/organizations that run Certificate Transparency logs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"email",
|
||||
"logs"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name of this log operator",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"title": "CT log operator email addresses",
|
||||
"description": "The log operator can be contacted using any of these email addresses.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"description": "Details of Certificate Transparency logs run by this operator.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"key",
|
||||
"log_id",
|
||||
"mmd",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"title": "Description of the CT log",
|
||||
"description": "A human-readable description that can be used to identify this log.",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"title": "The public key of the CT log",
|
||||
"description": "The log's public key as a DER-encoded ASN.1 SubjectPublicKeyInfo structure, then encoded as base64 (https://tools.ietf.org/html/rfc5280#section-4.1.2.7).",
|
||||
"type": "string"
|
||||
},
|
||||
"log_id": {
|
||||
"title": "The SHA-256 hash of the CT log's public key, base64-encoded",
|
||||
"description": "This is the LogID found in SCTs issued by this log (https://tools.ietf.org/html/rfc6962#section-3.2).",
|
||||
"type": "string",
|
||||
"minLength": 44,
|
||||
"maxLength": 44
|
||||
},
|
||||
"mmd": {
|
||||
"title": "The Maximum Merge Delay, in seconds",
|
||||
"description": "The CT log should not take longer than this to incorporate a certificate (https://tools.ietf.org/html/rfc6962#section-3).",
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"default": 86400
|
||||
},
|
||||
"url": {
|
||||
"title": "The base URL of the CT log's HTTP API",
|
||||
"description": "The API endpoints are defined in https://tools.ietf.org/html/rfc6962#section-4.",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": [
|
||||
"https://ct.googleapis.com/pilot/"
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"title": "The domain name of the CT log's DNS API",
|
||||
"description": "The API endpoints are defined in https://github.com/google/certificate-transparency-rfcs/blob/master/dns/draft-ct-over-dns.md.",
|
||||
"type": "string",
|
||||
"format": "hostname",
|
||||
"examples": [
|
||||
"pilot.ct.googleapis.com"
|
||||
]
|
||||
},
|
||||
"temporal_interval": {
|
||||
"description": "The log will only accept certificates that expire (have a NotAfter date) between these dates.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"start_inclusive",
|
||||
"end_exclusive"
|
||||
],
|
||||
"properties": {
|
||||
"start_inclusive": {
|
||||
"description": "All certificates must expire on this date or later.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2018-01-01T00:00:00Z"
|
||||
]
|
||||
},
|
||||
"end_exclusive": {
|
||||
"description": "All certificates must expire before this date.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2019-01-01T00:00:00Z"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"log_type": {
|
||||
"description": "The purpose of this log, e.g. test.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"prod",
|
||||
"test"
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"title": "The state of the log from the log list distributor's perspective.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pending": {
|
||||
"$ref": "#/definitions/state"
|
||||
},
|
||||
"qualified": {
|
||||
"$ref": "#/definitions/state"
|
||||
},
|
||||
"usable": {
|
||||
"$ref": "#/definitions/state"
|
||||
},
|
||||
"readonly": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/state"
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"final_tree_head"
|
||||
],
|
||||
"properties": {
|
||||
"final_tree_head": {
|
||||
"description": "The tree head (tree size and root hash) at which the log was made read-only.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tree_size",
|
||||
"sha256_root_hash"
|
||||
],
|
||||
"properties": {
|
||||
"tree_size": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"sha256_root_hash": {
|
||||
"type": "string",
|
||||
"minLength": 44,
|
||||
"maxLength": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"retired": {
|
||||
"$ref": "#/definitions/state"
|
||||
},
|
||||
"rejected": {
|
||||
"$ref": "#/definitions/state"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"required": [
|
||||
"pending"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"qualified"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"usable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"readonly"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"retired"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"rejected"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"previous_operators": {
|
||||
"title": "Previous operators that ran this log in the past, if any.",
|
||||
"description": "If the log has changed operators, this will contain a list of the previous operators, along with the timestamp when they stopped operating the log.",
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"end_time"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"title": "Name of the log operator",
|
||||
"type": "string"
|
||||
},
|
||||
"end_time": {
|
||||
"description": "The time at which this operator stopped operating this log.",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2018-01-01T00:00:00Z"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
|
||||
|
||||
package schema
|
||||
|
||||
import "fmt"
|
||||
import "encoding/json"
|
||||
import "reflect"
|
||||
|
||||
type LogListSchemaJson struct {
|
||||
// The time at which this version of the log list was published.
|
||||
LogListTimestamp *string `json:"log_list_timestamp,omitempty"`
|
||||
|
||||
// People/organizations that run Certificate Transparency logs.
|
||||
Operators []LogListSchemaJsonOperatorsElem `json:"operators"`
|
||||
|
||||
// The version will change whenever a change is made to any part of this log list.
|
||||
Version *string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type LogListSchemaJsonOperatorsElem struct {
|
||||
// The log operator can be contacted using any of these email addresses.
|
||||
Email []string `json:"email"`
|
||||
|
||||
// Details of Certificate Transparency logs run by this operator.
|
||||
Logs []LogListSchemaJsonOperatorsElemLogsElem `json:"logs"`
|
||||
|
||||
// Name corresponds to the JSON schema field "name".
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LogListSchemaJsonOperatorsElemLogsElem struct {
|
||||
// A human-readable description that can be used to identify this log.
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
||||
// The API endpoints are defined in
|
||||
// https://github.com/google/certificate-transparency-rfcs/blob/master/dns/draft-ct-over-dns.md.
|
||||
Dns *string `json:"dns,omitempty"`
|
||||
|
||||
// The log's public key as a DER-encoded ASN.1 SubjectPublicKeyInfo structure,
|
||||
// then encoded as base64 (https://tools.ietf.org/html/rfc5280#section-4.1.2.7).
|
||||
Key string `json:"key"`
|
||||
|
||||
// This is the LogID found in SCTs issued by this log
|
||||
// (https://tools.ietf.org/html/rfc6962#section-3.2).
|
||||
LogId string `json:"log_id"`
|
||||
|
||||
// The purpose of this log, e.g. test.
|
||||
LogType *LogListSchemaJsonOperatorsElemLogsElemLogType `json:"log_type,omitempty"`
|
||||
|
||||
// The CT log should not take longer than this to incorporate a certificate
|
||||
// (https://tools.ietf.org/html/rfc6962#section-3).
|
||||
Mmd float64 `json:"mmd"`
|
||||
|
||||
// If the log has changed operators, this will contain a list of the previous
|
||||
// operators, along with the timestamp when they stopped operating the log.
|
||||
PreviousOperators []LogListSchemaJsonOperatorsElemLogsElemPreviousOperatorsElem `json:"previous_operators,omitempty"`
|
||||
|
||||
// State corresponds to the JSON schema field "state".
|
||||
State *LogListSchemaJsonOperatorsElemLogsElemState `json:"state,omitempty"`
|
||||
|
||||
// The log will only accept certificates that expire (have a NotAfter date)
|
||||
// between these dates.
|
||||
TemporalInterval *LogListSchemaJsonOperatorsElemLogsElemTemporalInterval `json:"temporal_interval,omitempty"`
|
||||
|
||||
// The API endpoints are defined in https://tools.ietf.org/html/rfc6962#section-4.
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type LogListSchemaJsonOperatorsElemLogsElemLogType string
|
||||
|
||||
const LogListSchemaJsonOperatorsElemLogsElemLogTypeProd LogListSchemaJsonOperatorsElemLogsElemLogType = "prod"
|
||||
const LogListSchemaJsonOperatorsElemLogsElemLogTypeTest LogListSchemaJsonOperatorsElemLogsElemLogType = "test"
|
||||
|
||||
type LogListSchemaJsonOperatorsElemLogsElemPreviousOperatorsElem struct {
|
||||
// The time at which this operator stopped operating this log.
|
||||
EndTime string `json:"end_time"`
|
||||
|
||||
// Name corresponds to the JSON schema field "name".
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LogListSchemaJsonOperatorsElemLogsElemState struct {
|
||||
// Pending corresponds to the JSON schema field "pending".
|
||||
Pending *State `json:"pending,omitempty"`
|
||||
|
||||
// Qualified corresponds to the JSON schema field "qualified".
|
||||
Qualified *State `json:"qualified,omitempty"`
|
||||
|
||||
// Readonly corresponds to the JSON schema field "readonly".
|
||||
Readonly interface{} `json:"readonly,omitempty"`
|
||||
|
||||
// Rejected corresponds to the JSON schema field "rejected".
|
||||
Rejected *State `json:"rejected,omitempty"`
|
||||
|
||||
// Retired corresponds to the JSON schema field "retired".
|
||||
Retired *State `json:"retired,omitempty"`
|
||||
|
||||
// Usable corresponds to the JSON schema field "usable".
|
||||
Usable *State `json:"usable,omitempty"`
|
||||
}
|
||||
|
||||
// The log will only accept certificates that expire (have a NotAfter date) between
|
||||
// these dates.
|
||||
type LogListSchemaJsonOperatorsElemLogsElemTemporalInterval struct {
|
||||
// All certificates must expire before this date.
|
||||
EndExclusive string `json:"end_exclusive"`
|
||||
|
||||
// All certificates must expire on this date or later.
|
||||
StartInclusive string `json:"start_inclusive"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
// The time at which the log entered this state.
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJsonOperatorsElemLogsElemPreviousOperatorsElem) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["end_time"]; !ok || v == nil {
|
||||
return fmt.Errorf("field end_time: required")
|
||||
}
|
||||
if v, ok := raw["name"]; !ok || v == nil {
|
||||
return fmt.Errorf("field name: required")
|
||||
}
|
||||
type Plain LogListSchemaJsonOperatorsElemLogsElemPreviousOperatorsElem
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = LogListSchemaJsonOperatorsElemLogsElemPreviousOperatorsElem(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJsonOperatorsElemLogsElemTemporalInterval) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["end_exclusive"]; !ok || v == nil {
|
||||
return fmt.Errorf("field end_exclusive: required")
|
||||
}
|
||||
if v, ok := raw["start_inclusive"]; !ok || v == nil {
|
||||
return fmt.Errorf("field start_inclusive: required")
|
||||
}
|
||||
type Plain LogListSchemaJsonOperatorsElemLogsElemTemporalInterval
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = LogListSchemaJsonOperatorsElemLogsElemTemporalInterval(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJsonOperatorsElemLogsElemLogType) UnmarshalJSON(b []byte) error {
|
||||
var v string
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
var ok bool
|
||||
for _, expected := range enumValues_LogListSchemaJsonOperatorsElemLogsElemLogType {
|
||||
if reflect.DeepEqual(v, expected) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_LogListSchemaJsonOperatorsElemLogsElemLogType, v)
|
||||
}
|
||||
*j = LogListSchemaJsonOperatorsElemLogsElemLogType(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJsonOperatorsElemLogsElem) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["key"]; !ok || v == nil {
|
||||
return fmt.Errorf("field key: required")
|
||||
}
|
||||
if v, ok := raw["log_id"]; !ok || v == nil {
|
||||
return fmt.Errorf("field log_id: required")
|
||||
}
|
||||
if v, ok := raw["url"]; !ok || v == nil {
|
||||
return fmt.Errorf("field url: required")
|
||||
}
|
||||
type Plain LogListSchemaJsonOperatorsElemLogsElem
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["mmd"]; !ok || v == nil {
|
||||
plain.Mmd = 86400
|
||||
}
|
||||
*j = LogListSchemaJsonOperatorsElemLogsElem(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *State) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["timestamp"]; !ok || v == nil {
|
||||
return fmt.Errorf("field timestamp: required")
|
||||
}
|
||||
type Plain State
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = State(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJsonOperatorsElem) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["email"]; !ok || v == nil {
|
||||
return fmt.Errorf("field email: required")
|
||||
}
|
||||
if v, ok := raw["logs"]; !ok || v == nil {
|
||||
return fmt.Errorf("field logs: required")
|
||||
}
|
||||
if v, ok := raw["name"]; !ok || v == nil {
|
||||
return fmt.Errorf("field name: required")
|
||||
}
|
||||
type Plain LogListSchemaJsonOperatorsElem
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = LogListSchemaJsonOperatorsElem(plain)
|
||||
return nil
|
||||
}
|
||||
|
||||
var enumValues_LogListSchemaJsonOperatorsElemLogsElemLogType = []interface{}{
|
||||
"prod",
|
||||
"test",
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (j *LogListSchemaJson) UnmarshalJSON(b []byte) error {
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if v, ok := raw["operators"]; !ok || v == nil {
|
||||
return fmt.Errorf("field operators: required")
|
||||
}
|
||||
type Plain LogListSchemaJson
|
||||
var plain Plain
|
||||
if err := json.Unmarshal(b, &plain); err != nil {
|
||||
return err
|
||||
}
|
||||
*j = LogListSchemaJson(plain)
|
||||
return nil
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# This script updates the log list JSON Schema and the Go structs generated
|
||||
# from that schema.
|
||||
|
||||
# It is not intended to be run on a regular basis; we do not expect the JSON
|
||||
# Schema to change. It is retained here for historical purposes, so that if/when
|
||||
# the schema does change, or the ecosystem moves to a v4 version of the schema,
|
||||
# regenerating these files will be quick and easy.
|
||||
|
||||
# This script expects github.com/atombender/go-jsonschema to be installed:
|
||||
if ! command -v gojsonschema
|
||||
then
|
||||
echo "Install gojsonschema, then re-run this script:"
|
||||
echo "go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest"
|
||||
fi
|
||||
|
||||
this_dir=$(dirname $(readlink -f "${0}"))
|
||||
|
||||
curl https://www.gstatic.com/ct/log_list/v3/log_list_schema.json >| "${this_dir}"/log_list_schema.json
|
||||
|
||||
gojsonschema -p schema "${this_dir}"/log_list_schema.json >| "${this_dir}"/schema.go
|
4
go.sum
4
go.sum
|
@ -155,6 +155,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/letsencrypt/borp v0.0.0-20240620175310-a78493c6e2bd h1:3c+LdlAOEcW1qmG8gtkMCyAEoslmj6XCmniB+926kMM=
|
||||
github.com/letsencrypt/borp v0.0.0-20240620175310-a78493c6e2bd/go.mod h1:gMSMCNKhxox/ccR923EJsIvHeVVYfCABGbirqa0EwuM=
|
||||
github.com/letsencrypt/challtestsrv v1.2.1 h1:Lzv4jM+wSgVMCeO5a/F/IzSanhClstFMnX6SfrAJXjI=
|
||||
|
@ -226,6 +228,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
|||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -47,7 +47,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -98,7 +98,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,7 @@
|
|||
"url": "http://boulder.service.consul:4606",
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@
|
|||
},
|
||||
"state": {
|
||||
"usable": {
|
||||
"timestamp": "2000-00-00T00:00:00Z"
|
||||
"timestamp": "2000-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,8 +186,8 @@
|
|||
"logs": [
|
||||
{
|
||||
"description": "This Log Has Every Field To Ensure We Can Parse It",
|
||||
"log_id": "BaseSixtyFourEncodingOfSHA256HashOfPublicKey=",
|
||||
"key": "BaseSixtyFourEncodingOfDEREncodingOfPublicKey=",
|
||||
"log_id": "ZqBFtFIQLFnYQOwJfVnZRn4To/NPZJTlOf/TLBuzXxg=",
|
||||
"key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMVjHUOxzh2flagPhuEYy/AhAlpD9qqACg4fGcCxOhLU35r21CQXzKDdCHMu69QDFd6EAe8iGFsybg+Yn4/njtA==",
|
||||
"url": "https://example.com/ct/",
|
||||
"mmd": 86400,
|
||||
"state": {
|
||||
|
@ -206,8 +206,8 @@
|
|||
},
|
||||
{
|
||||
"description": "This Log Is Missing State To Ensure We Can Handle It",
|
||||
"log_id": "SomeOtherFakeLogID=",
|
||||
"key": "SomeOtherFakeKey=",
|
||||
"log_id": "gw0pzEo2G0THdJlm0i80NqV+qn0i9GnbcaBvhQOFxNc=",
|
||||
"key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMVjHUOxzh2flaFPhuEYy/AhAlpD9qqzHg4fGcCxOhLU39r21CQXzKDdCHMu69QDFd6EAe8iGFsybg+Yn4/njtA==",
|
||||
"url": "https://example.net/ct/",
|
||||
"mmd": 86400,
|
||||
"temporal_interval": {
|
||||
|
|
92
vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go
generated
vendored
Normal file
92
vendor/github.com/google/certificate-transparency-go/gossip/minimal/x509ext/x509ext.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2018 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509ext holds extensions types and values for minimal gossip.
|
||||
package x509ext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/certificate-transparency-go/asn1"
|
||||
"github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
)
|
||||
|
||||
// OIDExtensionCTSTH is the OID value for an X.509 extension that holds
|
||||
// a log STH value.
|
||||
// TODO(drysdale): get an official OID value
|
||||
var OIDExtensionCTSTH = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5}
|
||||
|
||||
// OIDExtKeyUsageCTMinimalGossip is the OID value for an extended key usage
|
||||
// (EKU) that indicates a leaf certificate is used for the validation of STH
|
||||
// values from public CT logs.
|
||||
// TODO(drysdale): get an official OID value
|
||||
var OIDExtKeyUsageCTMinimalGossip = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 6}
|
||||
|
||||
// LogSTHInfo is the structure that gets TLS-encoded into the X.509 extension
|
||||
// identified by OIDExtensionCTSTH.
|
||||
type LogSTHInfo struct {
|
||||
LogURL []byte `tls:"maxlen:255"`
|
||||
Version tls.Enum `tls:"maxval:255"`
|
||||
TreeSize uint64
|
||||
Timestamp uint64
|
||||
SHA256RootHash ct.SHA256Hash
|
||||
TreeHeadSignature ct.DigitallySigned
|
||||
}
|
||||
|
||||
// LogSTHInfoFromCert retrieves the STH information embedded in a certificate.
|
||||
func LogSTHInfoFromCert(cert *x509.Certificate) (*LogSTHInfo, error) {
|
||||
for _, ext := range cert.Extensions {
|
||||
if ext.Id.Equal(OIDExtensionCTSTH) {
|
||||
var sthInfo LogSTHInfo
|
||||
rest, err := tls.Unmarshal(ext.Value, &sthInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal STH: %v", err)
|
||||
} else if len(rest) > 0 {
|
||||
return nil, fmt.Errorf("trailing data (%d bytes) after STH", len(rest))
|
||||
}
|
||||
return &sthInfo, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no STH extension found")
|
||||
}
|
||||
|
||||
// HasSTHInfo indicates whether a certificate has embedded STH information.
|
||||
func HasSTHInfo(cert *x509.Certificate) bool {
|
||||
for _, ext := range cert.Extensions {
|
||||
if ext.Id.Equal(OIDExtensionCTSTH) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// STHFromCert retrieves the STH embedded in a certificate; note the returned STH
|
||||
// does not have the LogID field filled in.
|
||||
func STHFromCert(cert *x509.Certificate) (*ct.SignedTreeHead, error) {
|
||||
sthInfo, err := LogSTHInfoFromCert(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ct.SignedTreeHead{
|
||||
Version: ct.Version(sthInfo.Version),
|
||||
TreeSize: sthInfo.TreeSize,
|
||||
Timestamp: sthInfo.Timestamp,
|
||||
SHA256RootHash: sthInfo.SHA256RootHash,
|
||||
TreeHeadSignature: sthInfo.TreeHeadSignature,
|
||||
}, nil
|
||||
}
|
125
vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go
generated
vendored
Normal file
125
vendor/github.com/google/certificate-transparency-go/loglist3/logfilter.go
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2022 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 loglist3
|
||||
|
||||
import (
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/certificate-transparency-go/x509util"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// LogRoots maps Log-URLs (stated at LogList) to the pools of their accepted
|
||||
// root-certificates.
|
||||
type LogRoots map[string]*x509util.PEMCertPool
|
||||
|
||||
// Compatible creates a new LogList containing only Logs matching the temporal,
|
||||
// root-acceptance and Log-status conditions.
|
||||
func (ll *LogList) Compatible(cert *x509.Certificate, certRoot *x509.Certificate, roots LogRoots) LogList {
|
||||
active := ll.TemporallyCompatible(cert)
|
||||
return active.RootCompatible(certRoot, roots)
|
||||
}
|
||||
|
||||
// SelectByStatus creates a new LogList containing only logs with status
|
||||
// provided from the original.
|
||||
func (ll *LogList) SelectByStatus(lstats []LogStatus) LogList {
|
||||
var active LogList
|
||||
for _, op := range ll.Operators {
|
||||
activeOp := *op
|
||||
activeOp.Logs = []*Log{}
|
||||
for _, l := range op.Logs {
|
||||
for _, lstat := range lstats {
|
||||
if l.State.LogStatus() == lstat {
|
||||
activeOp.Logs = append(activeOp.Logs, l)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(activeOp.Logs) > 0 {
|
||||
active.Operators = append(active.Operators, &activeOp)
|
||||
}
|
||||
}
|
||||
return active
|
||||
}
|
||||
|
||||
// RootCompatible creates a new LogList containing only the logs of original
|
||||
// LogList that are compatible with the provided cert, according to
|
||||
// the passed in collection of per-log roots. Logs that are missing from
|
||||
// the collection are treated as always compatible and included, even if
|
||||
// an empty cert root is passed in.
|
||||
// Cert-root when provided is expected to be CA-cert.
|
||||
func (ll *LogList) RootCompatible(certRoot *x509.Certificate, roots LogRoots) LogList {
|
||||
var compatible LogList
|
||||
|
||||
// Check whether root is a CA-cert.
|
||||
if certRoot != nil && !certRoot.IsCA {
|
||||
klog.Warningf("Compatible method expects fully rooted chain, while last cert of the chain provided is not root")
|
||||
return compatible
|
||||
}
|
||||
|
||||
for _, op := range ll.Operators {
|
||||
compatibleOp := *op
|
||||
compatibleOp.Logs = []*Log{}
|
||||
for _, l := range op.Logs {
|
||||
// If root set is not defined, we treat Log as compatible assuming no
|
||||
// knowledge of its roots.
|
||||
if _, ok := roots[l.URL]; !ok {
|
||||
compatibleOp.Logs = append(compatibleOp.Logs, l)
|
||||
continue
|
||||
}
|
||||
|
||||
if certRoot == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check root is accepted.
|
||||
if roots[l.URL].Included(certRoot) {
|
||||
compatibleOp.Logs = append(compatibleOp.Logs, l)
|
||||
}
|
||||
}
|
||||
if len(compatibleOp.Logs) > 0 {
|
||||
compatible.Operators = append(compatible.Operators, &compatibleOp)
|
||||
}
|
||||
}
|
||||
return compatible
|
||||
}
|
||||
|
||||
// TemporallyCompatible creates a new LogList containing only the logs of
|
||||
// original LogList that are compatible with the provided cert, according to
|
||||
// NotAfter and TemporalInterval matching.
|
||||
// Returns empty LogList if nil-cert is provided.
|
||||
func (ll *LogList) TemporallyCompatible(cert *x509.Certificate) LogList {
|
||||
var compatible LogList
|
||||
if cert == nil {
|
||||
return compatible
|
||||
}
|
||||
|
||||
for _, op := range ll.Operators {
|
||||
compatibleOp := *op
|
||||
compatibleOp.Logs = []*Log{}
|
||||
for _, l := range op.Logs {
|
||||
if l.TemporalInterval == nil {
|
||||
compatibleOp.Logs = append(compatibleOp.Logs, l)
|
||||
continue
|
||||
}
|
||||
if cert.NotAfter.Before(l.TemporalInterval.EndExclusive) && (cert.NotAfter.After(l.TemporalInterval.StartInclusive) || cert.NotAfter.Equal(l.TemporalInterval.StartInclusive)) {
|
||||
compatibleOp.Logs = append(compatibleOp.Logs, l)
|
||||
}
|
||||
}
|
||||
if len(compatibleOp.Logs) > 0 {
|
||||
compatible.Operators = append(compatible.Operators, &compatibleOp)
|
||||
}
|
||||
}
|
||||
return compatible
|
||||
}
|
388
vendor/github.com/google/certificate-transparency-go/loglist3/loglist3.go
generated
vendored
Normal file
388
vendor/github.com/google/certificate-transparency-go/loglist3/loglist3.go
generated
vendored
Normal file
|
@ -0,0 +1,388 @@
|
|||
// Copyright 2022 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 loglist3 allows parsing and searching of the master CT Log list.
|
||||
// It expects the log list to conform to the v3 schema.
|
||||
package loglist3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/certificate-transparency-go/tls"
|
||||
)
|
||||
|
||||
const (
|
||||
// LogListURL has the master URL for Google Chrome's log list.
|
||||
LogListURL = "https://www.gstatic.com/ct/log_list/v3/log_list.json"
|
||||
// LogListSignatureURL has the URL for the signature over Google Chrome's log list.
|
||||
LogListSignatureURL = "https://www.gstatic.com/ct/log_list/v3/log_list.sig"
|
||||
// AllLogListURL has the URL for the list of all known logs (which isn't signed).
|
||||
AllLogListURL = "https://www.gstatic.com/ct/log_list/v3/all_logs_list.json"
|
||||
)
|
||||
|
||||
// Manually mapped from https://www.gstatic.com/ct/log_list/v3/log_list_schema.json
|
||||
|
||||
// LogList holds a collection of CT logs, grouped by operator.
|
||||
type LogList struct {
|
||||
// Version is the version of the log list.
|
||||
Version string `json:"version,omitempty"`
|
||||
// LogListTimestamp is the time at which the log list was published.
|
||||
LogListTimestamp time.Time `json:"log_list_timestamp,omitempty"`
|
||||
// Operators is a list of CT log operators and the logs they operate.
|
||||
Operators []*Operator `json:"operators"`
|
||||
}
|
||||
|
||||
// Operator holds a collection of CT logs run by the same organisation.
|
||||
// It also provides information about that organisation, e.g. contact details.
|
||||
type Operator struct {
|
||||
// Name is the name of the CT log operator.
|
||||
Name string `json:"name"`
|
||||
// Email lists the email addresses that can be used to contact this log
|
||||
// operator.
|
||||
Email []string `json:"email"`
|
||||
// Logs is a list of CT logs run by this operator.
|
||||
Logs []*Log `json:"logs"`
|
||||
}
|
||||
|
||||
// Log describes a single CT log.
|
||||
type Log struct {
|
||||
// Description is a human-readable string that describes the log.
|
||||
Description string `json:"description,omitempty"`
|
||||
// LogID is the SHA-256 hash of the log's public key.
|
||||
LogID []byte `json:"log_id"`
|
||||
// Key is the public key with which signatures can be verified.
|
||||
Key []byte `json:"key"`
|
||||
// URL is the address of the HTTPS API.
|
||||
URL string `json:"url"`
|
||||
// DNS is the address of the DNS API.
|
||||
DNS string `json:"dns,omitempty"`
|
||||
// MMD is the Maximum Merge Delay, in seconds. All submitted
|
||||
// certificates must be incorporated into the log within this time.
|
||||
MMD int32 `json:"mmd"`
|
||||
// PreviousOperators is a list of previous operators and the timestamp
|
||||
// of when they stopped running the log.
|
||||
PreviousOperators []*PreviousOperator `json:"previous_operators,omitempty"`
|
||||
// State is the current state of the log, from the perspective of the
|
||||
// log list distributor.
|
||||
State *LogStates `json:"state,omitempty"`
|
||||
// TemporalInterval, if set, indicates that this log only accepts
|
||||
// certificates with a NotAfter date in this time range.
|
||||
TemporalInterval *TemporalInterval `json:"temporal_interval,omitempty"`
|
||||
// Type indicates the purpose of this log, e.g. "test" or "prod".
|
||||
Type string `json:"log_type,omitempty"`
|
||||
}
|
||||
|
||||
// PreviousOperator holds information about a log operator and the time at which
|
||||
// they stopped running a log.
|
||||
type PreviousOperator struct {
|
||||
// Name is the name of the CT log operator.
|
||||
Name string `json:"name"`
|
||||
// EndTime is the time at which the operator stopped running a log.
|
||||
EndTime time.Time `json:"end_time"`
|
||||
}
|
||||
|
||||
// TemporalInterval is a time range.
|
||||
type TemporalInterval struct {
|
||||
// StartInclusive is the beginning of the time range.
|
||||
StartInclusive time.Time `json:"start_inclusive"`
|
||||
// EndExclusive is just after the end of the time range.
|
||||
EndExclusive time.Time `json:"end_exclusive"`
|
||||
}
|
||||
|
||||
// LogStatus indicates Log status.
|
||||
type LogStatus int
|
||||
|
||||
// LogStatus values
|
||||
const (
|
||||
UndefinedLogStatus LogStatus = iota
|
||||
PendingLogStatus
|
||||
QualifiedLogStatus
|
||||
UsableLogStatus
|
||||
ReadOnlyLogStatus
|
||||
RetiredLogStatus
|
||||
RejectedLogStatus
|
||||
)
|
||||
|
||||
//go:generate stringer -type=LogStatus
|
||||
|
||||
// LogStates are the states that a CT log can be in, from the perspective of a
|
||||
// user agent. Only one should be set - this is the current state.
|
||||
type LogStates struct {
|
||||
// Pending indicates that the log is in the "pending" state.
|
||||
Pending *LogState `json:"pending,omitempty"`
|
||||
// Qualified indicates that the log is in the "qualified" state.
|
||||
Qualified *LogState `json:"qualified,omitempty"`
|
||||
// Usable indicates that the log is in the "usable" state.
|
||||
Usable *LogState `json:"usable,omitempty"`
|
||||
// ReadOnly indicates that the log is in the "readonly" state.
|
||||
ReadOnly *ReadOnlyLogState `json:"readonly,omitempty"`
|
||||
// Retired indicates that the log is in the "retired" state.
|
||||
Retired *LogState `json:"retired,omitempty"`
|
||||
// Rejected indicates that the log is in the "rejected" state.
|
||||
Rejected *LogState `json:"rejected,omitempty"`
|
||||
}
|
||||
|
||||
// LogState contains details on the current state of a CT log.
|
||||
type LogState struct {
|
||||
// Timestamp is the time when the state began.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ReadOnlyLogState contains details on the current state of a read-only CT log.
|
||||
type ReadOnlyLogState struct {
|
||||
LogState
|
||||
// FinalTreeHead is the root hash and tree size at which the CT log was
|
||||
// made read-only. This should never change while the log is read-only.
|
||||
FinalTreeHead TreeHead `json:"final_tree_head"`
|
||||
}
|
||||
|
||||
// TreeHead is the root hash and tree size of a CT log.
|
||||
type TreeHead struct {
|
||||
// SHA256RootHash is the root hash of the CT log's Merkle tree.
|
||||
SHA256RootHash []byte `json:"sha256_root_hash"`
|
||||
// TreeSize is the size of the CT log's Merkle tree.
|
||||
TreeSize int64 `json:"tree_size"`
|
||||
}
|
||||
|
||||
// LogStatus method returns Log-status enum value for descriptive struct.
|
||||
func (ls *LogStates) LogStatus() LogStatus {
|
||||
switch {
|
||||
case ls == nil:
|
||||
return UndefinedLogStatus
|
||||
case ls.Pending != nil:
|
||||
return PendingLogStatus
|
||||
case ls.Qualified != nil:
|
||||
return QualifiedLogStatus
|
||||
case ls.Usable != nil:
|
||||
return UsableLogStatus
|
||||
case ls.ReadOnly != nil:
|
||||
return ReadOnlyLogStatus
|
||||
case ls.Retired != nil:
|
||||
return RetiredLogStatus
|
||||
case ls.Rejected != nil:
|
||||
return RejectedLogStatus
|
||||
default:
|
||||
return UndefinedLogStatus
|
||||
}
|
||||
}
|
||||
|
||||
// String method returns printable name of the state.
|
||||
func (ls *LogStates) String() string {
|
||||
return ls.LogStatus().String()
|
||||
}
|
||||
|
||||
// Active picks the set-up state. If multiple states are set (not expected) picks one of them.
|
||||
func (ls *LogStates) Active() (*LogState, *ReadOnlyLogState) {
|
||||
if ls == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch {
|
||||
case ls.Pending != nil:
|
||||
return ls.Pending, nil
|
||||
case ls.Qualified != nil:
|
||||
return ls.Qualified, nil
|
||||
case ls.Usable != nil:
|
||||
return ls.Usable, nil
|
||||
case ls.ReadOnly != nil:
|
||||
return nil, ls.ReadOnly
|
||||
case ls.Retired != nil:
|
||||
return ls.Retired, nil
|
||||
case ls.Rejected != nil:
|
||||
return ls.Rejected, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GoogleOperated returns whether Operator is considered to be Google.
|
||||
func (op *Operator) GoogleOperated() bool {
|
||||
for _, email := range op.Email {
|
||||
if strings.Contains(email, "google-ct-logs@googlegroups") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewFromJSON creates a LogList from JSON encoded data.
|
||||
func NewFromJSON(llData []byte) (*LogList, error) {
|
||||
var ll LogList
|
||||
if err := json.Unmarshal(llData, &ll); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse log list: %v", err)
|
||||
}
|
||||
return &ll, nil
|
||||
}
|
||||
|
||||
// NewFromSignedJSON creates a LogList from JSON encoded data, checking a
|
||||
// signature along the way. The signature data should be provided as the
|
||||
// raw signature data.
|
||||
func NewFromSignedJSON(llData, rawSig []byte, pubKey crypto.PublicKey) (*LogList, error) {
|
||||
var sigAlgo tls.SignatureAlgorithm
|
||||
switch pkType := pubKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
sigAlgo = tls.RSA
|
||||
case *ecdsa.PublicKey:
|
||||
sigAlgo = tls.ECDSA
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported public key type %v", pkType)
|
||||
}
|
||||
tlsSig := tls.DigitallySigned{
|
||||
Algorithm: tls.SignatureAndHashAlgorithm{
|
||||
Hash: tls.SHA256,
|
||||
Signature: sigAlgo,
|
||||
},
|
||||
Signature: rawSig,
|
||||
}
|
||||
if err := tls.VerifySignature(pubKey, llData, tlsSig); err != nil {
|
||||
return nil, fmt.Errorf("failed to verify signature: %v", err)
|
||||
}
|
||||
return NewFromJSON(llData)
|
||||
}
|
||||
|
||||
// FindLogByName returns all logs whose names contain the given string.
|
||||
func (ll *LogList) FindLogByName(name string) []*Log {
|
||||
name = strings.ToLower(name)
|
||||
var results []*Log
|
||||
for _, op := range ll.Operators {
|
||||
for _, log := range op.Logs {
|
||||
if strings.Contains(strings.ToLower(log.Description), name) {
|
||||
results = append(results, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// FindLogByURL finds the log with the given URL.
|
||||
func (ll *LogList) FindLogByURL(url string) *Log {
|
||||
for _, op := range ll.Operators {
|
||||
for _, log := range op.Logs {
|
||||
// Don't count trailing slashes
|
||||
if strings.TrimRight(log.URL, "/") == strings.TrimRight(url, "/") {
|
||||
return log
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindLogByKeyHash finds the log with the given key hash.
|
||||
func (ll *LogList) FindLogByKeyHash(keyhash [sha256.Size]byte) *Log {
|
||||
for _, op := range ll.Operators {
|
||||
for _, log := range op.Logs {
|
||||
if bytes.Equal(log.LogID, keyhash[:]) {
|
||||
return log
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindLogByKeyHashPrefix finds all logs whose key hash starts with the prefix.
|
||||
func (ll *LogList) FindLogByKeyHashPrefix(prefix string) []*Log {
|
||||
var results []*Log
|
||||
for _, op := range ll.Operators {
|
||||
for _, log := range op.Logs {
|
||||
hh := hex.EncodeToString(log.LogID[:])
|
||||
if strings.HasPrefix(hh, prefix) {
|
||||
results = append(results, log)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// FindLogByKey finds the log with the given DER-encoded key.
|
||||
func (ll *LogList) FindLogByKey(key []byte) *Log {
|
||||
for _, op := range ll.Operators {
|
||||
for _, log := range op.Logs {
|
||||
if bytes.Equal(log.Key[:], key) {
|
||||
return log
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var hexDigits = regexp.MustCompile("^[0-9a-fA-F]+$")
|
||||
|
||||
// FuzzyFindLog tries to find logs that match the given unspecified input,
|
||||
// whose format is unspecified. This generally returns a single log, but
|
||||
// if text input that matches multiple log descriptions is provided, then
|
||||
// multiple logs may be returned.
|
||||
func (ll *LogList) FuzzyFindLog(input string) []*Log {
|
||||
input = strings.Trim(input, " \t")
|
||||
if logs := ll.FindLogByName(input); len(logs) > 0 {
|
||||
return logs
|
||||
}
|
||||
if log := ll.FindLogByURL(input); log != nil {
|
||||
return []*Log{log}
|
||||
}
|
||||
// Try assuming the input is binary data of some form. First base64:
|
||||
if data, err := base64.StdEncoding.DecodeString(input); err == nil {
|
||||
if len(data) == sha256.Size {
|
||||
var hash [sha256.Size]byte
|
||||
copy(hash[:], data)
|
||||
if log := ll.FindLogByKeyHash(hash); log != nil {
|
||||
return []*Log{log}
|
||||
}
|
||||
}
|
||||
if log := ll.FindLogByKey(data); log != nil {
|
||||
return []*Log{log}
|
||||
}
|
||||
}
|
||||
// Now hex, but strip all internal whitespace first.
|
||||
input = stripInternalSpace(input)
|
||||
if data, err := hex.DecodeString(input); err == nil {
|
||||
if len(data) == sha256.Size {
|
||||
var hash [sha256.Size]byte
|
||||
copy(hash[:], data)
|
||||
if log := ll.FindLogByKeyHash(hash); log != nil {
|
||||
return []*Log{log}
|
||||
}
|
||||
}
|
||||
if log := ll.FindLogByKey(data); log != nil {
|
||||
return []*Log{log}
|
||||
}
|
||||
}
|
||||
// Finally, allow hex strings with an odd number of digits.
|
||||
if hexDigits.MatchString(input) {
|
||||
if logs := ll.FindLogByKeyHashPrefix(input); len(logs) > 0 {
|
||||
return logs
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stripInternalSpace(input string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if !unicode.IsSpace(r) {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, input)
|
||||
}
|
29
vendor/github.com/google/certificate-transparency-go/loglist3/logstatus_string.go
generated
vendored
Normal file
29
vendor/github.com/google/certificate-transparency-go/loglist3/logstatus_string.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Code generated by "stringer -type=LogStatus"; DO NOT EDIT.
|
||||
|
||||
package loglist3
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[UndefinedLogStatus-0]
|
||||
_ = x[PendingLogStatus-1]
|
||||
_ = x[QualifiedLogStatus-2]
|
||||
_ = x[UsableLogStatus-3]
|
||||
_ = x[ReadOnlyLogStatus-4]
|
||||
_ = x[RetiredLogStatus-5]
|
||||
_ = x[RejectedLogStatus-6]
|
||||
}
|
||||
|
||||
const _LogStatus_name = "UndefinedLogStatusPendingLogStatusQualifiedLogStatusUsableLogStatusReadOnlyLogStatusRetiredLogStatusRejectedLogStatus"
|
||||
|
||||
var _LogStatus_index = [...]uint8{0, 18, 34, 52, 67, 84, 100, 117}
|
||||
|
||||
func (i LogStatus) String() string {
|
||||
if i < 0 || i >= LogStatus(len(_LogStatus_index)-1) {
|
||||
return "LogStatus(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _LogStatus_name[_LogStatus_index[i]:_LogStatus_index[i+1]]
|
||||
}
|
116
vendor/github.com/google/certificate-transparency-go/x509util/files.go
generated
vendored
Normal file
116
vendor/github.com/google/certificate-transparency-go/x509util/files.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2016 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509util
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
)
|
||||
|
||||
// ReadPossiblePEMFile loads data from a file which may be in DER format
|
||||
// or may be in PEM format (with the given blockname).
|
||||
func ReadPossiblePEMFile(filename, blockname string) ([][]byte, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: failed to read data: %v", filename, err)
|
||||
}
|
||||
return dePEM(data, blockname), nil
|
||||
}
|
||||
|
||||
// ReadPossiblePEMURL attempts to determine if the given target is a local file or a
|
||||
// URL, and return the file contents regardless. It also copes with either PEM or DER
|
||||
// format data.
|
||||
func ReadPossiblePEMURL(target, blockname string) ([][]byte, error) {
|
||||
if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") {
|
||||
// Assume it's a filename
|
||||
return ReadPossiblePEMFile(target, blockname)
|
||||
}
|
||||
|
||||
rsp, err := http.Get(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err)
|
||||
}
|
||||
data, err := io.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to io.ReadAll(%q): %v", target, err)
|
||||
}
|
||||
return dePEM(data, blockname), nil
|
||||
}
|
||||
|
||||
func dePEM(data []byte, blockname string) [][]byte {
|
||||
var results [][]byte
|
||||
if strings.Contains(string(data), "BEGIN "+blockname) {
|
||||
rest := data
|
||||
for {
|
||||
var block *pem.Block
|
||||
block, rest = pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type == blockname {
|
||||
results = append(results, block.Bytes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results = append(results, data)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// ReadFileOrURL returns the data from a target which may be either a filename
|
||||
// or an HTTP(S) URL.
|
||||
func ReadFileOrURL(target string, client *http.Client) ([]byte, error) {
|
||||
u, err := url.Parse(target)
|
||||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
return os.ReadFile(target)
|
||||
}
|
||||
|
||||
rsp, err := client.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err)
|
||||
}
|
||||
return io.ReadAll(rsp.Body)
|
||||
}
|
||||
|
||||
// GetIssuer attempts to retrieve the issuer for a certificate, by examining
|
||||
// the cert's Authority Information Access extension (if present) for the
|
||||
// issuer's URL and retrieving from there.
|
||||
func GetIssuer(cert *x509.Certificate, client *http.Client) (*x509.Certificate, error) {
|
||||
if len(cert.IssuingCertificateURL) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
issuerURL := cert.IssuingCertificateURL[0]
|
||||
rsp, err := client.Get(issuerURL)
|
||||
if err != nil || rsp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get issuer from %q: %v", issuerURL, err)
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
body, err := io.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read issuer from %q: %v", issuerURL, err)
|
||||
}
|
||||
issuers, err := x509.ParseCertificates(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse issuer cert: %v", err)
|
||||
}
|
||||
return issuers[0], nil
|
||||
}
|
26
vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go
generated
vendored
Normal file
26
vendor/github.com/google/certificate-transparency-go/x509util/fuzz.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509util
|
||||
|
||||
import "github.com/google/certificate-transparency-go/x509"
|
||||
|
||||
// Fuzz is a go-fuzz (https://github.com/dvyukov/go-fuzz) entrypoint
|
||||
// for fuzzing the parsing of X509 certificates.
|
||||
func Fuzz(data []byte) int {
|
||||
if _, err := x509.ParseCertificate(data); err == nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
120
vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go
generated
vendored
Normal file
120
vendor/github.com/google/certificate-transparency-go/x509util/pem_cert_pool.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2016 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509util
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// String for certificate blocks in BEGIN / END PEM headers
|
||||
const pemCertificateBlockType string = "CERTIFICATE"
|
||||
|
||||
// PEMCertPool is a wrapper / extension to x509.CertPool. It allows us to access the
|
||||
// raw certs, which we need to serve get-roots request and has stricter handling on loading
|
||||
// certs into the pool. CertPool ignores errors if at least one cert loads correctly but
|
||||
// PEMCertPool requires all certs to load.
|
||||
type PEMCertPool struct {
|
||||
// maps from sha-256 to certificate, used for dup detection
|
||||
fingerprintToCertMap map[[sha256.Size]byte]x509.Certificate
|
||||
rawCerts []*x509.Certificate
|
||||
certPool *x509.CertPool
|
||||
}
|
||||
|
||||
// NewPEMCertPool creates a new, empty, instance of PEMCertPool.
|
||||
func NewPEMCertPool() *PEMCertPool {
|
||||
return &PEMCertPool{fingerprintToCertMap: make(map[[sha256.Size]byte]x509.Certificate), certPool: x509.NewCertPool()}
|
||||
}
|
||||
|
||||
// AddCert adds a certificate to a pool. Uses fingerprint to weed out duplicates.
|
||||
// cert must not be nil.
|
||||
func (p *PEMCertPool) AddCert(cert *x509.Certificate) {
|
||||
fingerprint := sha256.Sum256(cert.Raw)
|
||||
_, ok := p.fingerprintToCertMap[fingerprint]
|
||||
|
||||
if !ok {
|
||||
p.fingerprintToCertMap[fingerprint] = *cert
|
||||
p.certPool.AddCert(cert)
|
||||
p.rawCerts = append(p.rawCerts, cert)
|
||||
}
|
||||
}
|
||||
|
||||
// Included indicates whether the given cert is included in the pool.
|
||||
func (p *PEMCertPool) Included(cert *x509.Certificate) bool {
|
||||
fingerprint := sha256.Sum256(cert.Raw)
|
||||
_, ok := p.fingerprintToCertMap[fingerprint]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AppendCertsFromPEM adds certs to the pool from a byte slice assumed to contain PEM encoded data.
|
||||
// Skips over non certificate blocks in the data. Returns true if all certificates in the
|
||||
// data were parsed and added to the pool successfully and at least one certificate was found.
|
||||
func (p *PEMCertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
|
||||
for len(pemCerts) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemCerts = pem.Decode(pemCerts)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != pemCertificateBlockType || len(block.Headers) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if x509.IsFatal(err) {
|
||||
klog.Warningf("error parsing PEM certificate: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
p.AddCert(cert)
|
||||
ok = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AppendCertsFromPEMFile adds certs from a file that contains concatenated PEM data.
|
||||
func (p *PEMCertPool) AppendCertsFromPEMFile(pemFile string) error {
|
||||
pemData, err := os.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load PEM certs file: %v", err)
|
||||
}
|
||||
|
||||
if !p.AppendCertsFromPEM(pemData) {
|
||||
return errors.New("failed to parse PEM certs file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subjects returns a list of the DER-encoded subjects of all of the certificates in the pool.
|
||||
func (p *PEMCertPool) Subjects() (res [][]byte) {
|
||||
return p.certPool.Subjects()
|
||||
}
|
||||
|
||||
// CertPool returns the underlying CertPool.
|
||||
func (p *PEMCertPool) CertPool() *x509.CertPool {
|
||||
return p.certPool
|
||||
}
|
||||
|
||||
// RawCertificates returns a list of the raw bytes of certificates that are in this pool
|
||||
func (p *PEMCertPool) RawCertificates() []*x509.Certificate {
|
||||
return p.rawCerts
|
||||
}
|
169
vendor/github.com/google/certificate-transparency-go/x509util/revoked.go
generated
vendored
Normal file
169
vendor/github.com/google/certificate-transparency-go/x509util/revoked.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
// Copyright 2017 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/certificate-transparency-go/x509/pkix"
|
||||
)
|
||||
|
||||
// RevocationReasonToString generates a string describing a revocation reason code.
|
||||
func RevocationReasonToString(reason x509.RevocationReasonCode) string {
|
||||
switch reason {
|
||||
case x509.Unspecified:
|
||||
return "Unspecified"
|
||||
case x509.KeyCompromise:
|
||||
return "Key Compromise"
|
||||
case x509.CACompromise:
|
||||
return "CA Compromise"
|
||||
case x509.AffiliationChanged:
|
||||
return "Affiliation Changed"
|
||||
case x509.Superseded:
|
||||
return "Superseded"
|
||||
case x509.CessationOfOperation:
|
||||
return "Cessation Of Operation"
|
||||
case x509.CertificateHold:
|
||||
return "Certificate Hold"
|
||||
case x509.RemoveFromCRL:
|
||||
return "Remove From CRL"
|
||||
case x509.PrivilegeWithdrawn:
|
||||
return "Privilege Withdrawn"
|
||||
case x509.AACompromise:
|
||||
return "AA Compromise"
|
||||
default:
|
||||
return strconv.Itoa(int(reason))
|
||||
}
|
||||
}
|
||||
|
||||
// CRLToString generates a string describing the given certificate revocation list.
|
||||
// The output roughly resembles that from openssl crl -text.
|
||||
func CRLToString(crl *x509.CertificateList) string {
|
||||
var result bytes.Buffer
|
||||
var showCritical = func(critical bool) {
|
||||
if critical {
|
||||
result.WriteString(" critical")
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
result.WriteString("Certificate Revocation List (CRL):\n")
|
||||
result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", crl.TBSCertList.Version+1, crl.TBSCertList.Version))
|
||||
result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.TBSCertList.Signature)))
|
||||
var issuer pkix.Name
|
||||
issuer.FillFromRDNSequence(&crl.TBSCertList.Issuer)
|
||||
result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(issuer)))
|
||||
result.WriteString(fmt.Sprintf(" Last Update: %v\n", crl.TBSCertList.ThisUpdate))
|
||||
result.WriteString(fmt.Sprintf(" Next Update: %v\n", crl.TBSCertList.NextUpdate))
|
||||
|
||||
if len(crl.TBSCertList.Extensions) > 0 {
|
||||
result.WriteString(" CRL extensions:\n")
|
||||
}
|
||||
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Authority Key Identifier:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(crl.TBSCertList.AuthorityKeyID)))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionIssuerAltName, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Issuer Alt Name:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuerAltNames)))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionCRLNumber, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 CRLNumber:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.CRLNumber))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionDeltaCRLIndicator, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Delta CRL Indicator:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %d\n", crl.TBSCertList.BaseCRLNumber))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionIssuingDistributionPoint, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Issuing Distribution Point:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&crl.TBSCertList.IssuingDPFullNames)))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionFreshestCRL, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Freshest CRL:")
|
||||
showCritical(critical)
|
||||
result.WriteString(" Full Name:\n")
|
||||
var buf bytes.Buffer
|
||||
for _, pt := range crl.TBSCertList.FreshestCRLDistributionPoint {
|
||||
commaAppend(&buf, "URI:"+pt)
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %v\n", buf.String()))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, crl.TBSCertList.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Authority Information Access:")
|
||||
showCritical(critical)
|
||||
var issuerBuf bytes.Buffer
|
||||
for _, issuer := range crl.TBSCertList.IssuingCertificateURL {
|
||||
commaAppend(&issuerBuf, "URI:"+issuer)
|
||||
}
|
||||
if issuerBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String()))
|
||||
}
|
||||
var ocspBuf bytes.Buffer
|
||||
for _, ocsp := range crl.TBSCertList.OCSPServer {
|
||||
commaAppend(&ocspBuf, "URI:"+ocsp)
|
||||
}
|
||||
if ocspBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String()))
|
||||
}
|
||||
// TODO(drysdale): Display other GeneralName types
|
||||
}
|
||||
|
||||
result.WriteString("\n")
|
||||
result.WriteString("Revoked Certificates:\n")
|
||||
for _, c := range crl.TBSCertList.RevokedCertificates {
|
||||
result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", c.SerialNumber.Text(10), c.SerialNumber.Text(16)))
|
||||
result.WriteString(fmt.Sprintf(" Revocation Date : %v\n", c.RevocationTime))
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionCRLReasons, c.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 CRL Reason Code:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %s\n", RevocationReasonToString(c.RevocationReason)))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionInvalidityDate, c.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Invalidity Date:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %s\n", c.InvalidityDate))
|
||||
}
|
||||
count, critical = OIDInExtensions(x509.OIDExtensionCertificateIssuer, c.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Issuer:")
|
||||
showCritical(critical)
|
||||
result.WriteString(fmt.Sprintf(" %s\n", GeneralNamesToString(&c.Issuer)))
|
||||
}
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", x509.SignatureAlgorithmFromAI(crl.SignatureAlgorithm)))
|
||||
appendHexData(&result, crl.SignatureValue.Bytes, 18, " ")
|
||||
result.WriteString("\n")
|
||||
|
||||
return result.String()
|
||||
}
|
900
vendor/github.com/google/certificate-transparency-go/x509util/x509util.go
generated
vendored
Normal file
900
vendor/github.com/google/certificate-transparency-go/x509util/x509util.go
generated
vendored
Normal file
|
@ -0,0 +1,900 @@
|
|||
// Copyright 2016 Google LLC. All Rights Reserved.
|
||||
//
|
||||
// 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 x509util includes utility code for working with X.509
|
||||
// certificates from the x509 package.
|
||||
package x509util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
ct "github.com/google/certificate-transparency-go"
|
||||
"github.com/google/certificate-transparency-go/asn1"
|
||||
"github.com/google/certificate-transparency-go/gossip/minimal/x509ext"
|
||||
"github.com/google/certificate-transparency-go/tls"
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/certificate-transparency-go/x509/pkix"
|
||||
)
|
||||
|
||||
// OIDForStandardExtension indicates whether oid identifies a standard extension.
|
||||
// Standard extensions are listed in RFC 5280 (and other RFCs).
|
||||
func OIDForStandardExtension(oid asn1.ObjectIdentifier) bool {
|
||||
if oid.Equal(x509.OIDExtensionSubjectKeyId) ||
|
||||
oid.Equal(x509.OIDExtensionKeyUsage) ||
|
||||
oid.Equal(x509.OIDExtensionExtendedKeyUsage) ||
|
||||
oid.Equal(x509.OIDExtensionAuthorityKeyId) ||
|
||||
oid.Equal(x509.OIDExtensionBasicConstraints) ||
|
||||
oid.Equal(x509.OIDExtensionSubjectAltName) ||
|
||||
oid.Equal(x509.OIDExtensionCertificatePolicies) ||
|
||||
oid.Equal(x509.OIDExtensionNameConstraints) ||
|
||||
oid.Equal(x509.OIDExtensionCRLDistributionPoints) ||
|
||||
oid.Equal(x509.OIDExtensionIssuerAltName) ||
|
||||
oid.Equal(x509.OIDExtensionSubjectDirectoryAttributes) ||
|
||||
oid.Equal(x509.OIDExtensionInhibitAnyPolicy) ||
|
||||
oid.Equal(x509.OIDExtensionPolicyConstraints) ||
|
||||
oid.Equal(x509.OIDExtensionPolicyMappings) ||
|
||||
oid.Equal(x509.OIDExtensionFreshestCRL) ||
|
||||
oid.Equal(x509.OIDExtensionSubjectInfoAccess) ||
|
||||
oid.Equal(x509.OIDExtensionAuthorityInfoAccess) ||
|
||||
oid.Equal(x509.OIDExtensionIPPrefixList) ||
|
||||
oid.Equal(x509.OIDExtensionASList) ||
|
||||
oid.Equal(x509.OIDExtensionCTPoison) ||
|
||||
oid.Equal(x509.OIDExtensionCTSCT) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OIDInExtensions checks whether the extension identified by oid is present in extensions
|
||||
// and returns how many times it occurs together with an indication of whether any of them
|
||||
// are marked critical.
|
||||
func OIDInExtensions(oid asn1.ObjectIdentifier, extensions []pkix.Extension) (int, bool) {
|
||||
count := 0
|
||||
critical := false
|
||||
for _, ext := range extensions {
|
||||
if ext.Id.Equal(oid) {
|
||||
count++
|
||||
if ext.Critical {
|
||||
critical = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return count, critical
|
||||
}
|
||||
|
||||
// String formatting for various X.509/ASN.1 types
|
||||
func bitStringToString(b asn1.BitString) string { // nolint:deadcode,unused
|
||||
result := hex.EncodeToString(b.Bytes)
|
||||
bitsLeft := b.BitLength % 8
|
||||
if bitsLeft != 0 {
|
||||
result += " (" + strconv.Itoa(8-bitsLeft) + " unused bits)"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func publicKeyAlgorithmToString(algo x509.PublicKeyAlgorithm) string {
|
||||
// Use OpenSSL-compatible strings for the algorithms.
|
||||
switch algo {
|
||||
case x509.RSA:
|
||||
return "rsaEncryption"
|
||||
case x509.DSA:
|
||||
return "dsaEncryption"
|
||||
case x509.ECDSA:
|
||||
return "id-ecPublicKey"
|
||||
default:
|
||||
return strconv.Itoa(int(algo))
|
||||
}
|
||||
}
|
||||
|
||||
// appendHexData adds a hex dump of binary data to buf, with line breaks
|
||||
// after each set of count bytes, and with each new line prefixed with the
|
||||
// given prefix.
|
||||
func appendHexData(buf *bytes.Buffer, data []byte, count int, prefix string) {
|
||||
for ii, b := range data {
|
||||
if ii%count == 0 {
|
||||
if ii > 0 {
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
buf.WriteString(prefix)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%02x:", b))
|
||||
}
|
||||
}
|
||||
|
||||
func curveOIDToString(oid asn1.ObjectIdentifier) (t string, bitlen int) {
|
||||
switch {
|
||||
case oid.Equal(x509.OIDNamedCurveP224):
|
||||
return "secp224r1", 224
|
||||
case oid.Equal(x509.OIDNamedCurveP256):
|
||||
return "prime256v1", 256
|
||||
case oid.Equal(x509.OIDNamedCurveP384):
|
||||
return "secp384r1", 384
|
||||
case oid.Equal(x509.OIDNamedCurveP521):
|
||||
return "secp521r1", 521
|
||||
case oid.Equal(x509.OIDNamedCurveP192):
|
||||
return "secp192r1", 192
|
||||
}
|
||||
return fmt.Sprintf("%v", oid), -1
|
||||
}
|
||||
|
||||
func publicKeyToString(_ x509.PublicKeyAlgorithm, pub interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
bitlen := pub.N.BitLen()
|
||||
buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen))
|
||||
buf.WriteString(" Modulus:\n")
|
||||
data := pub.N.Bytes()
|
||||
appendHexData(&buf, data, 15, " ")
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(fmt.Sprintf(" Exponent: %d (0x%x)", pub.E, pub.E))
|
||||
case *dsa.PublicKey:
|
||||
buf.WriteString(" pub:\n")
|
||||
appendHexData(&buf, pub.Y.Bytes(), 15, " ")
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(" P:\n")
|
||||
appendHexData(&buf, pub.P.Bytes(), 15, " ")
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(" Q:\n")
|
||||
appendHexData(&buf, pub.Q.Bytes(), 15, " ")
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(" G:\n")
|
||||
appendHexData(&buf, pub.G.Bytes(), 15, " ")
|
||||
case *ecdsa.PublicKey:
|
||||
data := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
||||
oid, ok := x509.OIDFromNamedCurve(pub.Curve)
|
||||
if !ok {
|
||||
return " <unsupported elliptic curve>"
|
||||
}
|
||||
oidname, bitlen := curveOIDToString(oid)
|
||||
buf.WriteString(fmt.Sprintf(" Public Key: (%d bit)\n", bitlen))
|
||||
buf.WriteString(" pub:\n")
|
||||
appendHexData(&buf, data, 15, " ")
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(fmt.Sprintf(" ASN1 OID: %s", oidname))
|
||||
default:
|
||||
buf.WriteString(fmt.Sprintf("%v", pub))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func commaAppend(buf *bytes.Buffer, s string) {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(s)
|
||||
}
|
||||
|
||||
func keyUsageToString(k x509.KeyUsage) string {
|
||||
var buf bytes.Buffer
|
||||
if k&x509.KeyUsageDigitalSignature != 0 {
|
||||
commaAppend(&buf, "Digital Signature")
|
||||
}
|
||||
if k&x509.KeyUsageContentCommitment != 0 {
|
||||
commaAppend(&buf, "Content Commitment")
|
||||
}
|
||||
if k&x509.KeyUsageKeyEncipherment != 0 {
|
||||
commaAppend(&buf, "Key Encipherment")
|
||||
}
|
||||
if k&x509.KeyUsageDataEncipherment != 0 {
|
||||
commaAppend(&buf, "Data Encipherment")
|
||||
}
|
||||
if k&x509.KeyUsageKeyAgreement != 0 {
|
||||
commaAppend(&buf, "Key Agreement")
|
||||
}
|
||||
if k&x509.KeyUsageCertSign != 0 {
|
||||
commaAppend(&buf, "Certificate Signing")
|
||||
}
|
||||
if k&x509.KeyUsageCRLSign != 0 {
|
||||
commaAppend(&buf, "CRL Signing")
|
||||
}
|
||||
if k&x509.KeyUsageEncipherOnly != 0 {
|
||||
commaAppend(&buf, "Encipher Only")
|
||||
}
|
||||
if k&x509.KeyUsageDecipherOnly != 0 {
|
||||
commaAppend(&buf, "Decipher Only")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func extKeyUsageToString(u x509.ExtKeyUsage) string {
|
||||
switch u {
|
||||
case x509.ExtKeyUsageAny:
|
||||
return "Any"
|
||||
case x509.ExtKeyUsageServerAuth:
|
||||
return "TLS Web server authentication"
|
||||
case x509.ExtKeyUsageClientAuth:
|
||||
return "TLS Web client authentication"
|
||||
case x509.ExtKeyUsageCodeSigning:
|
||||
return "Signing of executable code"
|
||||
case x509.ExtKeyUsageEmailProtection:
|
||||
return "Email protection"
|
||||
case x509.ExtKeyUsageIPSECEndSystem:
|
||||
return "IPSEC end system"
|
||||
case x509.ExtKeyUsageIPSECTunnel:
|
||||
return "IPSEC tunnel"
|
||||
case x509.ExtKeyUsageIPSECUser:
|
||||
return "IPSEC user"
|
||||
case x509.ExtKeyUsageTimeStamping:
|
||||
return "Time stamping"
|
||||
case x509.ExtKeyUsageOCSPSigning:
|
||||
return "OCSP signing"
|
||||
case x509.ExtKeyUsageMicrosoftServerGatedCrypto:
|
||||
return "Microsoft server gated cryptography"
|
||||
case x509.ExtKeyUsageNetscapeServerGatedCrypto:
|
||||
return "Netscape server gated cryptography"
|
||||
case x509.ExtKeyUsageCertificateTransparency:
|
||||
return "Certificate transparency"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func attributeOIDToString(oid asn1.ObjectIdentifier) string { // nolint:deadcode,unused
|
||||
switch {
|
||||
case oid.Equal(pkix.OIDCountry):
|
||||
return "Country"
|
||||
case oid.Equal(pkix.OIDOrganization):
|
||||
return "Organization"
|
||||
case oid.Equal(pkix.OIDOrganizationalUnit):
|
||||
return "OrganizationalUnit"
|
||||
case oid.Equal(pkix.OIDCommonName):
|
||||
return "CommonName"
|
||||
case oid.Equal(pkix.OIDSerialNumber):
|
||||
return "SerialNumber"
|
||||
case oid.Equal(pkix.OIDLocality):
|
||||
return "Locality"
|
||||
case oid.Equal(pkix.OIDProvince):
|
||||
return "Province"
|
||||
case oid.Equal(pkix.OIDStreetAddress):
|
||||
return "StreetAddress"
|
||||
case oid.Equal(pkix.OIDPostalCode):
|
||||
return "PostalCode"
|
||||
case oid.Equal(pkix.OIDPseudonym):
|
||||
return "Pseudonym"
|
||||
case oid.Equal(pkix.OIDTitle):
|
||||
return "Title"
|
||||
case oid.Equal(pkix.OIDDnQualifier):
|
||||
return "DnQualifier"
|
||||
case oid.Equal(pkix.OIDName):
|
||||
return "Name"
|
||||
case oid.Equal(pkix.OIDSurname):
|
||||
return "Surname"
|
||||
case oid.Equal(pkix.OIDGivenName):
|
||||
return "GivenName"
|
||||
case oid.Equal(pkix.OIDInitials):
|
||||
return "Initials"
|
||||
case oid.Equal(pkix.OIDGenerationQualifier):
|
||||
return "GenerationQualifier"
|
||||
default:
|
||||
return oid.String()
|
||||
}
|
||||
}
|
||||
|
||||
// NameToString creates a string description of a pkix.Name object.
|
||||
func NameToString(name pkix.Name) string {
|
||||
var result bytes.Buffer
|
||||
addSingle := func(prefix, item string) {
|
||||
if len(item) == 0 {
|
||||
return
|
||||
}
|
||||
commaAppend(&result, prefix)
|
||||
result.WriteString(item)
|
||||
}
|
||||
addList := func(prefix string, items []string) {
|
||||
for _, item := range items {
|
||||
addSingle(prefix, item)
|
||||
}
|
||||
}
|
||||
addList("C=", name.Country)
|
||||
addList("O=", name.Organization)
|
||||
addList("OU=", name.OrganizationalUnit)
|
||||
addList("L=", name.Locality)
|
||||
addList("ST=", name.Province)
|
||||
addList("streetAddress=", name.StreetAddress)
|
||||
addList("postalCode=", name.PostalCode)
|
||||
addSingle("serialNumber=", name.SerialNumber)
|
||||
addSingle("CN=", name.CommonName)
|
||||
for _, atv := range name.Names {
|
||||
value, ok := atv.Value.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
t := atv.Type
|
||||
// All of the defined attribute OIDs are of the form 2.5.4.N, and OIDAttribute is
|
||||
// the 2.5.4 prefix ('id-at' in RFC 5280).
|
||||
if len(t) == 4 && t[0] == pkix.OIDAttribute[0] && t[1] == pkix.OIDAttribute[1] && t[2] == pkix.OIDAttribute[2] {
|
||||
// OID is 'id-at N', so check the final value to figure out which attribute.
|
||||
switch t[3] {
|
||||
case pkix.OIDCommonName[3], pkix.OIDSerialNumber[3], pkix.OIDCountry[3], pkix.OIDLocality[3], pkix.OIDProvince[3],
|
||||
pkix.OIDStreetAddress[3], pkix.OIDOrganization[3], pkix.OIDOrganizationalUnit[3], pkix.OIDPostalCode[3]:
|
||||
continue // covered by explicit fields
|
||||
case pkix.OIDPseudonym[3]:
|
||||
addSingle("pseudonym=", value)
|
||||
continue
|
||||
case pkix.OIDTitle[3]:
|
||||
addSingle("title=", value)
|
||||
continue
|
||||
case pkix.OIDDnQualifier[3]:
|
||||
addSingle("dnQualifier=", value)
|
||||
continue
|
||||
case pkix.OIDName[3]:
|
||||
addSingle("name=", value)
|
||||
continue
|
||||
case pkix.OIDSurname[3]:
|
||||
addSingle("surname=", value)
|
||||
continue
|
||||
case pkix.OIDGivenName[3]:
|
||||
addSingle("givenName=", value)
|
||||
continue
|
||||
case pkix.OIDInitials[3]:
|
||||
addSingle("initials=", value)
|
||||
continue
|
||||
case pkix.OIDGenerationQualifier[3]:
|
||||
addSingle("generationQualifier=", value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
addSingle(t.String()+"=", value)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// OtherNameToString creates a string description of an x509.OtherName object.
|
||||
func OtherNameToString(other x509.OtherName) string {
|
||||
return fmt.Sprintf("%v=%v", other.TypeID, hex.EncodeToString(other.Value.Bytes))
|
||||
}
|
||||
|
||||
// GeneralNamesToString creates a string description of an x509.GeneralNames object.
|
||||
func GeneralNamesToString(gname *x509.GeneralNames) string {
|
||||
var buf bytes.Buffer
|
||||
for _, name := range gname.DNSNames {
|
||||
commaAppend(&buf, "DNS:"+name)
|
||||
}
|
||||
for _, email := range gname.EmailAddresses {
|
||||
commaAppend(&buf, "email:"+email)
|
||||
}
|
||||
for _, name := range gname.DirectoryNames {
|
||||
commaAppend(&buf, "DirName:"+NameToString(name))
|
||||
}
|
||||
for _, uri := range gname.URIs {
|
||||
commaAppend(&buf, "URI:"+uri)
|
||||
}
|
||||
for _, ip := range gname.IPNets {
|
||||
if ip.Mask == nil {
|
||||
commaAppend(&buf, "IP Address:"+ip.IP.String())
|
||||
} else {
|
||||
commaAppend(&buf, "IP Address:"+ip.IP.String()+"/"+ip.Mask.String())
|
||||
}
|
||||
}
|
||||
for _, id := range gname.RegisteredIDs {
|
||||
commaAppend(&buf, "Registered ID:"+id.String())
|
||||
}
|
||||
for _, other := range gname.OtherNames {
|
||||
commaAppend(&buf, "othername:"+OtherNameToString(other))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// CertificateToString generates a string describing the given certificate.
|
||||
// The output roughly resembles that from openssl x509 -text.
|
||||
func CertificateToString(cert *x509.Certificate) string {
|
||||
var result bytes.Buffer
|
||||
result.WriteString("Certificate:\n")
|
||||
result.WriteString(" Data:\n")
|
||||
result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", cert.Version, cert.Version-1))
|
||||
result.WriteString(fmt.Sprintf(" Serial Number: %s (0x%s)\n", cert.SerialNumber.Text(10), cert.SerialNumber.Text(16)))
|
||||
result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm))
|
||||
result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(cert.Issuer)))
|
||||
result.WriteString(" Validity:\n")
|
||||
result.WriteString(fmt.Sprintf(" Not Before: %v\n", cert.NotBefore))
|
||||
result.WriteString(fmt.Sprintf(" Not After : %v\n", cert.NotAfter))
|
||||
result.WriteString(fmt.Sprintf(" Subject: %v\n", NameToString(cert.Subject)))
|
||||
result.WriteString(" Subject Public Key Info:\n")
|
||||
result.WriteString(fmt.Sprintf(" Public Key Algorithm: %v\n", publicKeyAlgorithmToString(cert.PublicKeyAlgorithm)))
|
||||
result.WriteString(fmt.Sprintf("%v\n", publicKeyToString(cert.PublicKeyAlgorithm, cert.PublicKey)))
|
||||
|
||||
if len(cert.Extensions) > 0 {
|
||||
result.WriteString(" X509v3 extensions:\n")
|
||||
}
|
||||
// First display the extensions that are already cracked out
|
||||
showAuthKeyID(&result, cert)
|
||||
showSubjectKeyID(&result, cert)
|
||||
showKeyUsage(&result, cert)
|
||||
showExtendedKeyUsage(&result, cert)
|
||||
showBasicConstraints(&result, cert)
|
||||
showSubjectAltName(&result, cert)
|
||||
showNameConstraints(&result, cert)
|
||||
showCertPolicies(&result, cert)
|
||||
showCRLDPs(&result, cert)
|
||||
showAuthInfoAccess(&result, cert)
|
||||
showSubjectInfoAccess(&result, cert)
|
||||
showRPKIAddressRanges(&result, cert)
|
||||
showRPKIASIdentifiers(&result, cert)
|
||||
showCTPoison(&result, cert)
|
||||
showCTSCT(&result, cert)
|
||||
showCTLogSTHInfo(&result, cert)
|
||||
|
||||
showUnhandledExtensions(&result, cert)
|
||||
showSignature(&result, cert)
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func showCritical(result *bytes.Buffer, critical bool) {
|
||||
if critical {
|
||||
result.WriteString(" critical")
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
func showAuthKeyID(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionAuthorityKeyId, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Authority Key Identifier:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.AuthorityKeyId)))
|
||||
}
|
||||
}
|
||||
|
||||
func showSubjectKeyID(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionSubjectKeyId, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Subject Key Identifier:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(fmt.Sprintf(" keyid:%v\n", hex.EncodeToString(cert.SubjectKeyId)))
|
||||
}
|
||||
}
|
||||
|
||||
func showKeyUsage(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionKeyUsage, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Key Usage:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(fmt.Sprintf(" %v\n", keyUsageToString(cert.KeyUsage)))
|
||||
}
|
||||
}
|
||||
|
||||
func showExtendedKeyUsage(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionExtendedKeyUsage, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Extended Key Usage:")
|
||||
showCritical(result, critical)
|
||||
var usages bytes.Buffer
|
||||
for _, usage := range cert.ExtKeyUsage {
|
||||
commaAppend(&usages, extKeyUsageToString(usage))
|
||||
}
|
||||
for _, oid := range cert.UnknownExtKeyUsage {
|
||||
commaAppend(&usages, oid.String())
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %v\n", usages.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func showBasicConstraints(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionBasicConstraints, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Basic Constraints:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(fmt.Sprintf(" CA:%t", cert.IsCA))
|
||||
if cert.MaxPathLen > 0 || cert.MaxPathLenZero {
|
||||
result.WriteString(fmt.Sprintf(", pathlen:%d", cert.MaxPathLen))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Subject Alternative Name:")
|
||||
showCritical(result, critical)
|
||||
var buf bytes.Buffer
|
||||
for _, name := range cert.DNSNames {
|
||||
commaAppend(&buf, "DNS:"+name)
|
||||
}
|
||||
for _, email := range cert.EmailAddresses {
|
||||
commaAppend(&buf, "email:"+email)
|
||||
}
|
||||
for _, ip := range cert.IPAddresses {
|
||||
commaAppend(&buf, "IP Address:"+ip.String())
|
||||
}
|
||||
|
||||
result.WriteString(fmt.Sprintf(" %v\n", buf.String()))
|
||||
// TODO(drysdale): include other name forms
|
||||
}
|
||||
}
|
||||
|
||||
func showNameConstraints(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionNameConstraints, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Name Constraints:")
|
||||
showCritical(result, critical)
|
||||
if len(cert.PermittedDNSDomains) > 0 {
|
||||
result.WriteString(" Permitted:\n")
|
||||
var buf bytes.Buffer
|
||||
for _, name := range cert.PermittedDNSDomains {
|
||||
commaAppend(&buf, "DNS:"+name)
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %v\n", buf.String()))
|
||||
}
|
||||
// TODO(drysdale): include other name forms
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func showCertPolicies(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionCertificatePolicies, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 Certificate Policies:")
|
||||
showCritical(result, critical)
|
||||
for _, oid := range cert.PolicyIdentifiers {
|
||||
result.WriteString(fmt.Sprintf(" Policy: %v\n", oid.String()))
|
||||
// TODO(drysdale): Display any qualifiers associated with the policy
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func showCRLDPs(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionCRLDistributionPoints, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" X509v3 CRL Distribution Points:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(" Full Name:\n")
|
||||
var buf bytes.Buffer
|
||||
for _, pt := range cert.CRLDistributionPoints {
|
||||
commaAppend(&buf, "URI:"+pt)
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %v\n", buf.String()))
|
||||
// TODO(drysdale): Display other GeneralNames types, plus issuer/reasons/relative-name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func showAuthInfoAccess(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionAuthorityInfoAccess, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Authority Information Access:")
|
||||
showCritical(result, critical)
|
||||
var issuerBuf bytes.Buffer
|
||||
for _, issuer := range cert.IssuingCertificateURL {
|
||||
commaAppend(&issuerBuf, "URI:"+issuer)
|
||||
}
|
||||
if issuerBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" CA Issuers - %v\n", issuerBuf.String()))
|
||||
}
|
||||
var ocspBuf bytes.Buffer
|
||||
for _, ocsp := range cert.OCSPServer {
|
||||
commaAppend(&ocspBuf, "URI:"+ocsp)
|
||||
}
|
||||
if ocspBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" OCSP - %v\n", ocspBuf.String()))
|
||||
}
|
||||
// TODO(drysdale): Display other GeneralNames types
|
||||
}
|
||||
}
|
||||
|
||||
func showSubjectInfoAccess(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionSubjectInfoAccess, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Subject Information Access:")
|
||||
showCritical(result, critical)
|
||||
var tsBuf bytes.Buffer
|
||||
for _, ts := range cert.SubjectTimestamps {
|
||||
commaAppend(&tsBuf, "URI:"+ts)
|
||||
}
|
||||
if tsBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" AD Time Stamping - %v\n", tsBuf.String()))
|
||||
}
|
||||
var repoBuf bytes.Buffer
|
||||
for _, repo := range cert.SubjectCARepositories {
|
||||
commaAppend(&repoBuf, "URI:"+repo)
|
||||
}
|
||||
if repoBuf.Len() > 0 {
|
||||
result.WriteString(fmt.Sprintf(" CA repository - %v\n", repoBuf.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showAddressRange(prefix x509.IPAddressPrefix, afi uint16) string {
|
||||
switch afi {
|
||||
case x509.IPv4AddressFamilyIndicator, x509.IPv6AddressFamilyIndicator:
|
||||
size := 4
|
||||
if afi == x509.IPv6AddressFamilyIndicator {
|
||||
size = 16
|
||||
}
|
||||
ip := make([]byte, size)
|
||||
copy(ip, prefix.Bytes)
|
||||
addr := net.IPNet{IP: ip, Mask: net.CIDRMask(prefix.BitLength, 8*size)}
|
||||
return addr.String()
|
||||
default:
|
||||
return fmt.Sprintf("%x/%d", prefix.Bytes, prefix.BitLength)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func showRPKIAddressRanges(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionIPPrefixList, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" sbgp-ipAddrBlock:")
|
||||
showCritical(result, critical)
|
||||
for _, blocks := range cert.RPKIAddressRanges {
|
||||
afi := blocks.AFI
|
||||
switch afi {
|
||||
case x509.IPv4AddressFamilyIndicator:
|
||||
result.WriteString(" IPv4")
|
||||
case x509.IPv6AddressFamilyIndicator:
|
||||
result.WriteString(" IPv6")
|
||||
default:
|
||||
result.WriteString(fmt.Sprintf(" %d", afi))
|
||||
}
|
||||
if blocks.SAFI != 0 {
|
||||
result.WriteString(fmt.Sprintf(" SAFI=%d", blocks.SAFI))
|
||||
}
|
||||
result.WriteString(":")
|
||||
if blocks.InheritFromIssuer {
|
||||
result.WriteString(" inherit\n")
|
||||
continue
|
||||
}
|
||||
result.WriteString("\n")
|
||||
for _, prefix := range blocks.AddressPrefixes {
|
||||
result.WriteString(fmt.Sprintf(" %s\n", showAddressRange(prefix, afi)))
|
||||
}
|
||||
for _, ipRange := range blocks.AddressRanges {
|
||||
result.WriteString(fmt.Sprintf(" [%s, %s]\n", showAddressRange(ipRange.Min, afi), showAddressRange(ipRange.Max, afi)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showASIDs(result *bytes.Buffer, asids *x509.ASIdentifiers, label string) {
|
||||
if asids == nil {
|
||||
return
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %s:\n", label))
|
||||
if asids.InheritFromIssuer {
|
||||
result.WriteString(" inherit\n")
|
||||
return
|
||||
}
|
||||
for _, id := range asids.ASIDs {
|
||||
result.WriteString(fmt.Sprintf(" %d\n", id))
|
||||
}
|
||||
for _, idRange := range asids.ASIDRanges {
|
||||
result.WriteString(fmt.Sprintf(" %d-%d\n", idRange.Min, idRange.Max))
|
||||
}
|
||||
}
|
||||
|
||||
func showRPKIASIdentifiers(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionASList, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" sbgp-autonomousSysNum:")
|
||||
showCritical(result, critical)
|
||||
showASIDs(result, cert.RPKIASNumbers, "Autonomous System Numbers")
|
||||
showASIDs(result, cert.RPKIRoutingDomainIDs, "Routing Domain Identifiers")
|
||||
}
|
||||
}
|
||||
func showCTPoison(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" RFC6962 Pre-Certificate Poison:")
|
||||
showCritical(result, critical)
|
||||
result.WriteString(" .....\n")
|
||||
}
|
||||
}
|
||||
|
||||
func showCTSCT(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509.OIDExtensionCTSCT, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" RFC6962 Certificate Transparency SCT:")
|
||||
showCritical(result, critical)
|
||||
for i, sctData := range cert.SCTList.SCTList {
|
||||
result.WriteString(fmt.Sprintf(" SCT [%d]:\n", i))
|
||||
var sct ct.SignedCertificateTimestamp
|
||||
_, err := tls.Unmarshal(sctData.Val, &sct)
|
||||
if err != nil {
|
||||
appendHexData(result, sctData.Val, 16, " ")
|
||||
result.WriteString("\n")
|
||||
continue
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" Version: %d\n", sct.SCTVersion))
|
||||
result.WriteString(fmt.Sprintf(" LogID: %s\n", base64.StdEncoding.EncodeToString(sct.LogID.KeyID[:])))
|
||||
result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sct.Timestamp))
|
||||
result.WriteString(fmt.Sprintf(" Signature: %s\n", sct.Signature.Algorithm))
|
||||
result.WriteString(" Signature:\n")
|
||||
appendHexData(result, sct.Signature.Signature, 16, " ")
|
||||
result.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showCTLogSTHInfo(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
count, critical := OIDInExtensions(x509ext.OIDExtensionCTSTH, cert.Extensions)
|
||||
if count > 0 {
|
||||
result.WriteString(" Certificate Transparency STH:")
|
||||
showCritical(result, critical)
|
||||
sthInfo, err := x509ext.LogSTHInfoFromCert(cert)
|
||||
if err != nil {
|
||||
result.WriteString(" Failed to decode STH:\n")
|
||||
return
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" LogURL: %s\n", string(sthInfo.LogURL)))
|
||||
result.WriteString(fmt.Sprintf(" Version: %d\n", sthInfo.Version))
|
||||
result.WriteString(fmt.Sprintf(" TreeSize: %d\n", sthInfo.TreeSize))
|
||||
result.WriteString(fmt.Sprintf(" Timestamp: %d\n", sthInfo.Timestamp))
|
||||
result.WriteString(" RootHash:\n")
|
||||
appendHexData(result, sthInfo.SHA256RootHash[:], 16, " ")
|
||||
result.WriteString("\n")
|
||||
result.WriteString(fmt.Sprintf(" TreeHeadSignature: %s\n", sthInfo.TreeHeadSignature.Algorithm))
|
||||
appendHexData(result, sthInfo.TreeHeadSignature.Signature, 16, " ")
|
||||
result.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func showUnhandledExtensions(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
for _, ext := range cert.Extensions {
|
||||
// Skip extensions that are already cracked out
|
||||
if oidAlreadyPrinted(ext.Id) {
|
||||
continue
|
||||
}
|
||||
result.WriteString(fmt.Sprintf(" %v:", ext.Id))
|
||||
showCritical(result, ext.Critical)
|
||||
appendHexData(result, ext.Value, 16, " ")
|
||||
result.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func showSignature(result *bytes.Buffer, cert *x509.Certificate) {
|
||||
result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm))
|
||||
appendHexData(result, cert.Signature, 18, " ")
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// TODO(drysdale): remove this once all standard OIDs are parsed and printed.
|
||||
func oidAlreadyPrinted(oid asn1.ObjectIdentifier) bool {
|
||||
if oid.Equal(x509.OIDExtensionSubjectKeyId) ||
|
||||
oid.Equal(x509.OIDExtensionKeyUsage) ||
|
||||
oid.Equal(x509.OIDExtensionExtendedKeyUsage) ||
|
||||
oid.Equal(x509.OIDExtensionAuthorityKeyId) ||
|
||||
oid.Equal(x509.OIDExtensionBasicConstraints) ||
|
||||
oid.Equal(x509.OIDExtensionSubjectAltName) ||
|
||||
oid.Equal(x509.OIDExtensionCertificatePolicies) ||
|
||||
oid.Equal(x509.OIDExtensionNameConstraints) ||
|
||||
oid.Equal(x509.OIDExtensionCRLDistributionPoints) ||
|
||||
oid.Equal(x509.OIDExtensionAuthorityInfoAccess) ||
|
||||
oid.Equal(x509.OIDExtensionSubjectInfoAccess) ||
|
||||
oid.Equal(x509.OIDExtensionIPPrefixList) ||
|
||||
oid.Equal(x509.OIDExtensionASList) ||
|
||||
oid.Equal(x509.OIDExtensionCTPoison) ||
|
||||
oid.Equal(x509.OIDExtensionCTSCT) ||
|
||||
oid.Equal(x509ext.OIDExtensionCTSTH) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CertificateFromPEM takes a certificate in PEM format and returns the
|
||||
// corresponding x509.Certificate object.
|
||||
func CertificateFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||
block, rest := pem.Decode(pemBytes)
|
||||
if len(rest) != 0 {
|
||||
return nil, errors.New("trailing data found after PEM block")
|
||||
}
|
||||
if block == nil {
|
||||
return nil, errors.New("PEM block is nil")
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, errors.New("PEM block is not a CERTIFICATE")
|
||||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
// CertificatesFromPEM parses one or more certificates from the given PEM data.
|
||||
// The PEM certificates must be concatenated. This function can be used for
|
||||
// parsing PEM-formatted certificate chains, but does not verify that the
|
||||
// resulting chain is a valid certificate chain.
|
||||
func CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
|
||||
var chain []*x509.Certificate
|
||||
for {
|
||||
var block *pem.Block
|
||||
block, pemBytes = pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return chain, nil
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("PEM block is not a CERTIFICATE")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse certificate")
|
||||
}
|
||||
chain = append(chain, cert)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseSCTsFromSCTList parses each of the SCTs contained within an SCT list.
|
||||
func ParseSCTsFromSCTList(sctList *x509.SignedCertificateTimestampList) ([]*ct.SignedCertificateTimestamp, error) {
|
||||
var scts []*ct.SignedCertificateTimestamp
|
||||
for i, data := range sctList.SCTList {
|
||||
sct, err := ExtractSCT(&data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting SCT number %d: %s", i, err)
|
||||
}
|
||||
scts = append(scts, sct)
|
||||
}
|
||||
return scts, nil
|
||||
}
|
||||
|
||||
// ExtractSCT deserializes an SCT from a TLS-encoded SCT.
|
||||
func ExtractSCT(sctData *x509.SerializedSCT) (*ct.SignedCertificateTimestamp, error) {
|
||||
if sctData == nil {
|
||||
return nil, errors.New("SCT is nil")
|
||||
}
|
||||
var sct ct.SignedCertificateTimestamp
|
||||
if rest, err := tls.Unmarshal(sctData.Val, &sct); err != nil {
|
||||
return nil, fmt.Errorf("error parsing SCT: %s", err)
|
||||
} else if len(rest) > 0 {
|
||||
return nil, fmt.Errorf("extra data (%d bytes) after serialized SCT", len(rest))
|
||||
}
|
||||
return &sct, nil
|
||||
}
|
||||
|
||||
// MarshalSCTsIntoSCTList serializes SCTs into SCT list.
|
||||
func MarshalSCTsIntoSCTList(scts []*ct.SignedCertificateTimestamp) (*x509.SignedCertificateTimestampList, error) {
|
||||
var sctList x509.SignedCertificateTimestampList
|
||||
sctList.SCTList = []x509.SerializedSCT{}
|
||||
for i, sct := range scts {
|
||||
if sct == nil {
|
||||
return nil, fmt.Errorf("SCT number %d is nil", i)
|
||||
}
|
||||
encd, err := tls.Marshal(*sct)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error serializing SCT number %d: %s", i, err)
|
||||
}
|
||||
sctData := x509.SerializedSCT{Val: encd}
|
||||
sctList.SCTList = append(sctList.SCTList, sctData)
|
||||
}
|
||||
return &sctList, nil
|
||||
}
|
||||
|
||||
var pemCertificatePrefix = []byte("-----BEGIN CERTIFICATE")
|
||||
|
||||
// ParseSCTsFromCertificate parses any SCTs that are embedded in the
|
||||
// certificate provided. The certificate bytes provided can be either DER or
|
||||
// PEM, provided the PEM data starts with the PEM block marker (i.e. has no
|
||||
// leading text).
|
||||
func ParseSCTsFromCertificate(certBytes []byte) ([]*ct.SignedCertificateTimestamp, error) {
|
||||
var cert *x509.Certificate
|
||||
var err error
|
||||
if bytes.HasPrefix(certBytes, pemCertificatePrefix) {
|
||||
cert, err = CertificateFromPEM(certBytes)
|
||||
} else {
|
||||
cert, err = x509.ParseCertificate(certBytes)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %s", err)
|
||||
}
|
||||
return ParseSCTsFromSCTList(&cert.SCTList)
|
||||
}
|
|
@ -180,10 +180,13 @@ github.com/google/certificate-transparency-go
|
|||
github.com/google/certificate-transparency-go/asn1
|
||||
github.com/google/certificate-transparency-go/client
|
||||
github.com/google/certificate-transparency-go/client/configpb
|
||||
github.com/google/certificate-transparency-go/gossip/minimal/x509ext
|
||||
github.com/google/certificate-transparency-go/jsonclient
|
||||
github.com/google/certificate-transparency-go/loglist3
|
||||
github.com/google/certificate-transparency-go/tls
|
||||
github.com/google/certificate-transparency-go/x509
|
||||
github.com/google/certificate-transparency-go/x509/pkix
|
||||
github.com/google/certificate-transparency-go/x509util
|
||||
# github.com/google/uuid v1.6.0
|
||||
## explicit
|
||||
github.com/google/uuid
|
||||
|
|
Loading…
Reference in New Issue