chore: update vendor

This commit is contained in:
Mikaël Cluseau
2018-07-03 18:25:07 +11:00
parent ecb3e9c868
commit f91ae88876
211 changed files with 18789 additions and 46221 deletions

View File

@ -16,9 +16,12 @@
/data
/dumpscts
/etcdiscover
/findlog
/gossip_server
/preloader
/scanlog
/sctcheck
/sctscan
/trillian_log_server
/trillian_log_signer
/trillian.json

View File

@ -1,23 +1,23 @@
sudo: false
sudo: true # required for CI push into Kubernetes.
language: go
os: linux
go: 1.9
go_import_path: github.com/google/certificate-transparency-go
env:
- GOFLAGS=
- GCE_CI=${ENABLE_GCE_CI} GOFLAGS=
- GOFLAGS=-race
- GOFLAGS= WITH_ETCD=true
- GOFLAGS= WITH_ETCD=true WITH_COVERAGE=true
- GOFLAGS=-race WITH_ETCD=true
matrix:
fast_finish: true
services:
- docker
install:
- |
if [ ! -d $HOME/gopath/src/github.com/google ]; then
mkdir -p $HOME/gopath/src/github.com/google
ln -s $TRAVIS_BUILD_DIR $HOME/gopath/src/github.com/google/certificate-transparency-go
fi
- mkdir ../protoc
- |
(
@ -41,9 +41,8 @@ install:
script:
- set -e
- export TRILLIAN_SQL_DRIVER=mysql
- cd $HOME/gopath/src/github.com/google/certificate-transparency-go
- ./scripts/presubmit.sh ${PRESUBMIT_OPTS}
- ./scripts/presubmit.sh ${PRESUBMIT_OPTS} ${WITH_COVERAGE:+--coverage}
- |
# Check re-generation didn't change anything
status=$(git status --porcelain | grep -v coverage) || :
@ -64,3 +63,12 @@ script:
after_success:
- cp /tmp/coverage.txt .
- bash <(curl -s https://codecov.io/bash)
- |
# Push up to GCE CI instance if we're running after a merge to master
if [[ "${GCE_CI}" == "true" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]] && [[ $TRAVIS_BRANCH == "master" ]]; then
. scripts/install_cloud.sh
echo ${GCLOUD_SERVICE_KEY_CI} | base64 --decode -i > ${HOME}/gcloud-service-key.json
gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json
rm ${HOME}/gcloud-service-key.json
. scripts/deploy_gce_ci.sh
fi

View File

@ -1,22 +1,12 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: multilog.proto
/*
Package configpb is a generated protocol buffer package.
It is generated from these files:
multilog.proto
It has these top-level messages:
TemporalLogConfig
LogShardConfig
*/
package configpb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
import timestamp "github.com/golang/protobuf/ptypes/timestamp"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -32,13 +22,35 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// TemporalLogConfig is a set of LogShardConfig messages, whose
// time limits should be contiguous.
type TemporalLogConfig struct {
Shard []*LogShardConfig `protobuf:"bytes,1,rep,name=shard" json:"shard,omitempty"`
Shard []*LogShardConfig `protobuf:"bytes,1,rep,name=shard" json:"shard,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *TemporalLogConfig) Reset() { *m = TemporalLogConfig{} }
func (m *TemporalLogConfig) String() string { return proto.CompactTextString(m) }
func (*TemporalLogConfig) ProtoMessage() {}
func (*TemporalLogConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *TemporalLogConfig) Reset() { *m = TemporalLogConfig{} }
func (m *TemporalLogConfig) String() string { return proto.CompactTextString(m) }
func (*TemporalLogConfig) ProtoMessage() {}
func (*TemporalLogConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_multilog_3c9b797b88da6f07, []int{0}
}
func (m *TemporalLogConfig) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TemporalLogConfig.Unmarshal(m, b)
}
func (m *TemporalLogConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_TemporalLogConfig.Marshal(b, m, deterministic)
}
func (dst *TemporalLogConfig) XXX_Merge(src proto.Message) {
xxx_messageInfo_TemporalLogConfig.Merge(dst, src)
}
func (m *TemporalLogConfig) XXX_Size() int {
return xxx_messageInfo_TemporalLogConfig.Size(m)
}
func (m *TemporalLogConfig) XXX_DiscardUnknown() {
xxx_messageInfo_TemporalLogConfig.DiscardUnknown(m)
}
var xxx_messageInfo_TemporalLogConfig proto.InternalMessageInfo
func (m *TemporalLogConfig) GetShard() []*LogShardConfig {
if m != nil {
@ -56,17 +68,39 @@ type LogShardConfig struct {
// not_after_start defines the start of the range of acceptable NotAfter
// values, inclusive.
// Leaving this unset implies no lower bound to the range.
NotAfterStart *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=not_after_start,json=notAfterStart" json:"not_after_start,omitempty"`
NotAfterStart *timestamp.Timestamp `protobuf:"bytes,3,opt,name=not_after_start,json=notAfterStart" json:"not_after_start,omitempty"`
// not_after_limit defines the end of the range of acceptable NotAfter values,
// exclusive.
// Leaving this unset implies no upper bound to the range.
NotAfterLimit *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=not_after_limit,json=notAfterLimit" json:"not_after_limit,omitempty"`
NotAfterLimit *timestamp.Timestamp `protobuf:"bytes,4,opt,name=not_after_limit,json=notAfterLimit" json:"not_after_limit,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LogShardConfig) Reset() { *m = LogShardConfig{} }
func (m *LogShardConfig) String() string { return proto.CompactTextString(m) }
func (*LogShardConfig) ProtoMessage() {}
func (*LogShardConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *LogShardConfig) Reset() { *m = LogShardConfig{} }
func (m *LogShardConfig) String() string { return proto.CompactTextString(m) }
func (*LogShardConfig) ProtoMessage() {}
func (*LogShardConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_multilog_3c9b797b88da6f07, []int{1}
}
func (m *LogShardConfig) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LogShardConfig.Unmarshal(m, b)
}
func (m *LogShardConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LogShardConfig.Marshal(b, m, deterministic)
}
func (dst *LogShardConfig) XXX_Merge(src proto.Message) {
xxx_messageInfo_LogShardConfig.Merge(dst, src)
}
func (m *LogShardConfig) XXX_Size() int {
return xxx_messageInfo_LogShardConfig.Size(m)
}
func (m *LogShardConfig) XXX_DiscardUnknown() {
xxx_messageInfo_LogShardConfig.DiscardUnknown(m)
}
var xxx_messageInfo_LogShardConfig proto.InternalMessageInfo
func (m *LogShardConfig) GetUri() string {
if m != nil {
@ -82,14 +116,14 @@ func (m *LogShardConfig) GetPublicKeyDer() []byte {
return nil
}
func (m *LogShardConfig) GetNotAfterStart() *google_protobuf.Timestamp {
func (m *LogShardConfig) GetNotAfterStart() *timestamp.Timestamp {
if m != nil {
return m.NotAfterStart
}
return nil
}
func (m *LogShardConfig) GetNotAfterLimit() *google_protobuf.Timestamp {
func (m *LogShardConfig) GetNotAfterLimit() *timestamp.Timestamp {
if m != nil {
return m.NotAfterLimit
}
@ -101,9 +135,9 @@ func init() {
proto.RegisterType((*LogShardConfig)(nil), "configpb.LogShardConfig")
}
func init() { proto.RegisterFile("multilog.proto", fileDescriptor0) }
func init() { proto.RegisterFile("multilog.proto", fileDescriptor_multilog_3c9b797b88da6f07) }
var fileDescriptor0 = []byte{
var fileDescriptor_multilog_3c9b797b88da6f07 = []byte{
// 241 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x8f, 0xb1, 0x4e, 0xc3, 0x30,
0x14, 0x45, 0x65, 0x02, 0x08, 0xdc, 0x12, 0xc0, 0x93, 0xd5, 0x85, 0xa8, 0x62, 0xc8, 0xe4, 0x4a,

View File

@ -19,7 +19,6 @@ package client
import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
@ -35,11 +34,19 @@ type LogClient struct {
jsonclient.JSONClient
}
// CheckLogClient is an interface that allows (just) checking of various log contents.
type CheckLogClient interface {
BaseURI() string
GetSTH(context.Context) (*ct.SignedTreeHead, error)
GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error)
GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error)
}
// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
// https://ct.googleapis.com/pilot
// |hc| is the underlying client to be used for HTTP requests to the CT log.
// |opts| can be used to provide a customer logger interface and a public key
// |opts| can be used to provide a custom logger interface and a public key
// for signature verification.
func New(uri string, hc *http.Client, opts jsonclient.Options) (*LogClient, error) {
logClient, err := jsonclient.New(uri, hc, opts)
@ -169,35 +176,16 @@ func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
}
return nil, err
}
sth := ct.SignedTreeHead{
TreeSize: resp.TreeSize,
Timestamp: resp.Timestamp,
}
if len(resp.SHA256RootHash) != sha256.Size {
return nil, RspError{
Err: fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(resp.SHA256RootHash)),
StatusCode: httpRsp.StatusCode,
Body: body,
}
}
copy(sth.SHA256RootHash[:], resp.SHA256RootHash)
var ds ct.DigitallySigned
if rest, err := tls.Unmarshal(resp.TreeHeadSignature, &ds); err != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
} else if len(rest) > 0 {
return nil, RspError{
Err: fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest)),
StatusCode: httpRsp.StatusCode,
Body: body,
}
}
sth.TreeHeadSignature = ds
if err := c.VerifySTHSignature(sth); err != nil {
sth, err := resp.ToSignedTreeHead()
if err != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return &sth, nil
if err := c.VerifySTHSignature(*sth); err != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return sth, nil
}
// VerifySTHSignature checks the signature in sth, returning any error encountered or nil if verification is
@ -281,3 +269,21 @@ func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error)
}
return roots, nil
}
// GetEntryAndProof returns a log entry and audit path for the index of a leaf.
func (c *LogClient) GetEntryAndProof(ctx context.Context, index, treeSize uint64) (*ct.GetEntryAndProofResponse, error) {
base10 := 10
params := map[string]string{
"leaf_index": strconv.FormatUint(index, base10),
"tree_size": strconv.FormatUint(treeSize, base10),
}
var resp ct.GetEntryAndProofResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetEntryAndProofPath, params, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
return &resp, nil
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package client
package client_test
import (
"bytes"
@ -32,6 +32,7 @@ import (
"time"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/client"
"github.com/google/certificate-transparency-go/jsonclient"
"github.com/google/certificate-transparency-go/testdata"
"github.com/google/certificate-transparency-go/tls"
@ -85,6 +86,15 @@ const (
]
}`
GetSTHConsistencyResp = `{ "consistency": [ "IqlrapPQKtmCY1jCr8+lpCtscRyjjZAA7nyadtFPRFQ=", "ytf6K2GnSRZ3Au+YkivCb7N1DygfKyZmE4aEs9OXl\/8=" ] }`
GetEntryAndProofResp = `{
"leaf_input": "AAAAAAFhw8UTtQAAAAJ1MIICcTCCAhegAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhvcml0eTAgFw0xNjEyMDcxNTEzMzZaGA8wMDAxMDEwMTAwMDAwMFowVjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxFzAVBgNVBAMMDmxlYWYwMS5jc3IucGVtMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6zdOUkWcRtWouMXtWLkwKaZwimmgJlyeL264ayNshOFGOpg2gkSliheLQYIy9C3gCFt+BzhS/EdWKCeb7WCLrKOBszCBsDAPBgNVHQ8BAf8EBQMDB/mAMIGLBgNVHQ4EgYMEgYBPRBC+90lR8pRLbTi3ID4j0WRzjoJOT3MGkKko87o8z6gEifk9zCwOiHeIgclTA0ZUTxXMRI5r+nUY0frjRCWZu4uthPlE90iJM+RyjcNTwDJGu2StvLnJ8y4t5fdnwdGssncXiBQMuM7/1eMEwAOfHgTFzJ0UBC2Umztl0hul3zAPBgNVHSMECDAGgAQBAgMEMAoGCCqGSM49BAMCA0gAMEUCIQCrwywGKvyt/BwR+e7yDs78qt4sSEVJltv7Y0W6gOI5awIgQ+IAjejYivLEfqNufFRezCBWHWhbq/HHGdNQtv6EArkAAA==",
"extra_data": "RXh0cmEK",
"audit_path": [
"pMumx96PIUB3TX543ljlpQ/RgZRqitRfykupIZrXq0Q=",
"5s2NQWkjmesu+Kqgp70TCwVLwq8obpHw/JyMGwN56pQ=",
"7VelXijfmGFSl62BWIsG8LRmxJGBq9XP8FxmszuT2Cg="
]
}`
)
func b64(s string) []byte {
@ -156,11 +166,11 @@ func TestGetEntries(t *testing.T) {
CertEntryExtraDataB64)
})
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
leaves, err := client.GetEntries(context.Background(), 0, 1)
leaves, err := lc.GetEntries(context.Background(), 0, 1)
if err != nil {
t.Errorf("GetEntries(0,1)=nil,%v; want 2 leaves,nil", err)
} else if len(leaves) != 2 {
@ -185,12 +195,12 @@ func TestGetEntriesErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetEntries(ctx, test.start, test.end)
got, err := lc.GetEntries(ctx, test.start, test.end)
if err == nil {
t.Errorf("GetEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -218,12 +228,12 @@ func TestGetRawEntriesErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-entries", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetRawEntries(ctx, test.start, test.end)
got, err := lc.GetRawEntries(ctx, test.start, test.end)
if err == nil {
t.Errorf("GetRawEntries(%d, %d)=%+v, nil; want nil, %q", test.start, test.end, got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -234,7 +244,7 @@ func TestGetRawEntriesErrors(t *testing.T) {
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(RspError); !ok {
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetRawEntries(%d, %d)=nil, .(%T); want nil, .(RspError)", test.start, test.end, err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetRawEntries(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.start, test.end, rspErr.Body, test.rsp)
@ -251,11 +261,11 @@ func TestGetSTH(t *testing.T) {
ValidSTHResponseSHA256RootHash,
ValidSTHResponseTreeHeadSignature))
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
sth, err := client.GetSTH(context.Background())
sth, err := lc.GetSTH(context.Background())
if err != nil {
t.Fatal(err)
}
@ -303,12 +313,12 @@ func TestGetSTHErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-sth", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetSTH(ctx)
got, err := lc.GetSTH(ctx)
if err == nil {
t.Errorf("GetSTH()=%+v, nil; want nil, %q", got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -319,7 +329,7 @@ func TestGetSTHErrors(t *testing.T) {
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(RspError); !ok {
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetSTH()=nil, .(%T); want nil, .(RspError)", err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetSTH()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
@ -417,7 +427,7 @@ func TestAddChainRetries(t *testing.T) {
for i, test := range tests {
deadline := context.Background()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
@ -431,7 +441,7 @@ func TestAddChainRetries(t *testing.T) {
currentFailures = 0
started := time.Now()
sct, err := client.AddChain(deadline, chain)
sct, err := lc.AddChain(deadline, chain)
took := time.Since(started)
delta := math.Abs(float64(took - test.expected))
ratio := delta / float64(test.expected)
@ -452,19 +462,19 @@ func TestAddChainRetries(t *testing.T) {
func TestAddChain(t *testing.T) {
hs := serveSCTAt(t, "/ct/v1/add-chain", testdata.TestCertProof)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
cert, err := x509util.CertificateFromPEM(testdata.TestCertPEM)
cert, err := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM))
if err != nil {
t.Fatalf("Failed to parse certificate from PEM: %v", err)
}
// AddChain will verify the signature because the client has a public key.
chain := []ct.ASN1Cert{{Data: cert.Raw}}
_, err = client.AddChain(context.Background(), chain)
_, err = lc.AddChain(context.Background(), chain)
if err != nil {
t.Errorf("AddChain()=nil,%v; want sct,nil", err)
}
@ -473,23 +483,23 @@ func TestAddChain(t *testing.T) {
func TestAddPreChain(t *testing.T) {
hs := serveSCTAt(t, "/ct/v1/add-pre-chain", testdata.TestPreCertProof)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{PublicKey: testdata.LogPublicKeyPEM})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
cert, err := x509util.CertificateFromPEM(testdata.TestPreCertPEM)
cert, err := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM))
if err != nil {
t.Fatalf("Failed to parse pre-certificate from PEM: %v", err)
}
issuer, err := x509util.CertificateFromPEM(testdata.CACertPEM)
issuer, err := x509util.CertificateFromPEM([]byte(testdata.CACertPEM))
if err != nil {
t.Fatalf("Failed to parse issuer certificate from PEM: %v", err)
}
// AddPreChain will verify the signature because the client has a public key.
chain := []ct.ASN1Cert{{Data: cert.Raw}, {Data: issuer.Raw}}
_, err = client.AddPreChain(context.Background(), chain)
_, err = lc.AddPreChain(context.Background(), chain)
if err != nil {
t.Errorf("AddPreChain()=nil,%v; want sct,nil", err)
}
@ -498,7 +508,7 @@ func TestAddPreChain(t *testing.T) {
func TestAddJSON(t *testing.T) {
hs := serveRspAt(t, "/ct/v1/add-json", AddJSONResp)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
@ -511,7 +521,7 @@ func TestAddJSON(t *testing.T) {
}
for _, test := range tests {
sct, err := client.AddJSON(context.Background(), test.data)
sct, err := lc.AddJSON(context.Background(), test.data)
if test.success && err != nil {
t.Errorf("AddJSON(%v)=nil,%v; want sct,nil", test.data, err)
} else if !test.success && err == nil {
@ -526,7 +536,7 @@ func TestAddJSON(t *testing.T) {
func TestGetSTHConsistency(t *testing.T) {
hs := serveRspAt(t, "/ct/v1/get-sth-consistency", GetSTHConsistencyResp)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
@ -543,7 +553,7 @@ func TestGetSTHConsistency(t *testing.T) {
}
for _, test := range tests {
proof, err := client.GetSTHConsistency(context.Background(), test.first, test.second)
proof, err := lc.GetSTHConsistency(context.Background(), test.first, test.second)
if err != nil {
t.Errorf("GetSTHConsistency(%d, %d)=nil,%v; want proof,nil", test.first, test.second, err)
} else if !reflect.DeepEqual(proof, test.proof) {
@ -567,12 +577,12 @@ func TestGetSTHConsistencyErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-sth-consistency", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetSTHConsistency(ctx, test.first, test.second)
got, err := lc.GetSTHConsistency(ctx, test.first, test.second)
if err == nil {
t.Errorf("GetSTHConsistency(%d, %d)=%+v, nil; want nil, %q", test.first, test.second, got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -583,7 +593,7 @@ func TestGetSTHConsistencyErrors(t *testing.T) {
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(RspError); !ok {
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetSTHConsistency(%d, %d)=nil, .(%T); want nil, .(RspError)", test.first, test.second, err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetSTHConsistency(%d, %d)=nil, .Body=%q; want nil, .Body=%q", test.first, test.second, rspErr.Body, test.rsp)
@ -595,7 +605,7 @@ func TestGetSTHConsistencyErrors(t *testing.T) {
func TestGetProofByHash(t *testing.T) {
hs := serveRspAt(t, "/ct/v1/get-proof-by-hash", ProofByHashResp)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
@ -608,7 +618,7 @@ func TestGetProofByHash(t *testing.T) {
}
for _, test := range tests {
resp, err := client.GetProofByHash(context.Background(), test.hash, test.treesize)
resp, err := lc.GetProofByHash(context.Background(), test.hash, test.treesize)
if err != nil {
t.Errorf("GetProofByHash(%v, %v)=nil,%v; want proof,nil", test.hash, test.treesize, err)
} else if got := len(resp.AuditPath); got < 1 {
@ -632,12 +642,12 @@ func TestGetProofByHashErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-proof-by-hash", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetProofByHash(ctx, aHash, 100)
got, err := lc.GetProofByHash(ctx, aHash, 100)
if err == nil {
t.Errorf("GetProofByHash()=%+v, nil; want nil, %q", got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -648,7 +658,7 @@ func TestGetProofByHashErrors(t *testing.T) {
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(RspError); !ok {
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetProofByHash()=nil, .(%T); want nil, .(RspError)", err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetProofByHash()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
@ -660,12 +670,12 @@ func TestGetProofByHashErrors(t *testing.T) {
func TestGetAcceptedRoots(t *testing.T) {
hs := serveRspAt(t, "/ct/v1/get-roots", GetRootsResp)
defer hs.Close()
client, err := New(hs.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
certs, err := client.GetAcceptedRoots(context.Background())
certs, err := lc.GetAcceptedRoots(context.Background())
if err != nil {
t.Errorf("GetAcceptedRoots()=nil,%q; want roots,nil", err.Error())
} else if len(certs) < 1 {
@ -687,12 +697,12 @@ func TestGetAcceptedRootsErrors(t *testing.T) {
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-roots", test.rsp)
defer ts.Close()
client, err := New(ts.URL, &http.Client{}, jsonclient.Options{})
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := client.GetAcceptedRoots(ctx)
got, err := lc.GetAcceptedRoots(ctx)
if err == nil {
t.Errorf("GetAcceptedRoots()=%+v, nil; want nil, %q", got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
@ -703,7 +713,7 @@ func TestGetAcceptedRootsErrors(t *testing.T) {
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(RspError); !ok {
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetAcceptedRoots()=nil, .(%T); want nil, .(RspError)", err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetAcceptedRoots()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
@ -711,3 +721,69 @@ func TestGetAcceptedRootsErrors(t *testing.T) {
}
}
}
func TestGetEntryAndProof(t *testing.T) {
hs := serveRspAt(t, "/ct/v1/get-entry-and-proof", GetEntryAndProofResp)
defer hs.Close()
lc, err := client.New(hs.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
tests := []struct {
index uint64
treesize uint64
}{
{1000, 2000},
}
for _, test := range tests {
resp, err := lc.GetEntryAndProof(context.Background(), test.index, test.treesize)
if err != nil {
t.Errorf("GetEntryAndProof(%v, %v)=nil,%v; want proof,nil", test.index, test.treesize, err)
} else if got := len(resp.AuditPath); got < 1 {
t.Errorf("len(GetEntryAndProof(%v, %v)): %v; want > 1", test.index, test.treesize, got)
}
}
}
func TestGetEntryAndProofErrors(t *testing.T) {
ctx := context.Background()
var tests = []struct {
rsp, want string
}{
{rsp: "", want: "EOF"},
{rsp: "not-json", want: "invalid"},
{rsp: `{"leaf_input": "bogus", "extra_data": "Z29vZAo=", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"},
{rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "bogus", "audit_path": ["Z29vZAo="]}`, want: "illegal base64"},
{rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bogus"]}`, want: "illegal base64"},
{rsp: `{"leaf_input": "Z29vZAo=", "extra_data": "Z29vZAo=", "audit_path": ["bbbb",]}`, want: "invalid"},
}
for _, test := range tests {
ts := serveRspAt(t, "/ct/v1/get-entry-and-proof", test.rsp)
defer ts.Close()
lc, err := client.New(ts.URL, &http.Client{}, jsonclient.Options{})
if err != nil {
t.Errorf("Failed to create client: %v", err)
continue
}
got, err := lc.GetEntryAndProof(ctx, 99, 100)
if err == nil {
t.Errorf("GetEntryAndProof()=%+v, nil; want nil, %q", got, test.want)
} else if !strings.Contains(err.Error(), test.want) {
t.Errorf("GetEntryAndProof()=nil, %q; want nil, %q", err, test.want)
}
if got != nil {
t.Errorf("GetEntryAndProof()=%+v, _; want nil, _", got)
}
if len(test.rsp) > 0 {
// Expect the error to include the HTTP response
if rspErr, ok := err.(client.RspError); !ok {
t.Errorf("GetEntryAndProof()=nil, .(%T); want nil, .(RspError)", err)
} else if string(rspErr.Body) != test.rsp {
t.Errorf("GetEntryAndProof()=nil, .Body=%q; want nil, .Body=%q", rspErr.Body, test.rsp)
}
}
}
}

View File

@ -23,7 +23,7 @@ import (
"net/http"
"time"
"github.com/gogo/protobuf/proto"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/client/configpb"

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package client
package client_test
import (
"context"
@ -26,6 +26,7 @@ import (
"github.com/golang/protobuf/ptypes"
tspb "github.com/golang/protobuf/ptypes/timestamp"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/client"
"github.com/google/certificate-transparency-go/client/configpb"
"github.com/google/certificate-transparency-go/testdata"
"github.com/google/certificate-transparency-go/x509util"
@ -200,7 +201,7 @@ func TestNewTemporalLogClient(t *testing.T) {
},
}
for _, test := range tests {
_, err := NewTemporalLogClient(test.cfg, nil)
_, err := client.NewTemporalLogClient(test.cfg, nil)
if err != nil {
if test.wantErr == "" {
t.Errorf("NewTemporalLogClient(%+v)=nil,%v; want _,nil", test.cfg, err)
@ -313,7 +314,7 @@ func TestIndexByDate(t *testing.T) {
{cfg: boundedCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true},
}
for _, test := range tests {
tlc, err := NewTemporalLogClient(test.cfg, nil)
tlc, err := client.NewTemporalLogClient(test.cfg, nil)
if err != nil {
t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err)
continue
@ -349,16 +350,16 @@ func TestTemporalAddChain(t *testing.T) {
}))
defer hs.Close()
cert, err := x509util.CertificateFromPEM(testdata.TestCertPEM)
cert, err := x509util.CertificateFromPEM([]byte(testdata.TestCertPEM))
if err != nil {
t.Fatalf("Failed to parse certificate from PEM: %v", err)
}
certChain := []ct.ASN1Cert{{Data: cert.Raw}}
precert, err := x509util.CertificateFromPEM(testdata.TestPreCertPEM)
precert, err := x509util.CertificateFromPEM([]byte(testdata.TestPreCertPEM))
if err != nil {
t.Fatalf("Failed to parse pre-certificate from PEM: %v", err)
}
issuer, err := x509util.CertificateFromPEM(testdata.CACertPEM)
issuer, err := x509util.CertificateFromPEM([]byte(testdata.CACertPEM))
if err != nil {
t.Fatalf("Failed to parse issuer certificate from PEM: %v", err)
}
@ -423,7 +424,7 @@ func TestTemporalAddChain(t *testing.T) {
ctx := context.Background()
for _, test := range tests {
tlc, err := NewTemporalLogClient(test.cfg, nil)
tlc, err := client.NewTemporalLogClient(test.cfg, nil)
if err != nil {
t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err)
continue
@ -464,7 +465,7 @@ func TestTemporalAddChainErrors(t *testing.T) {
}
ctx := context.Background()
tlc, err := NewTemporalLogClient(cfg, nil)
tlc, err := client.NewTemporalLogClient(cfg, nil)
if err != nil {
t.Fatalf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", cfg, err)
}

View File

@ -1,7 +1,8 @@
{
"Linters": {
"license": "./scripts/check_license.sh:PATH:LINE:MESSAGE",
"forked": "./scripts/check_forked.sh:PATH:LINE:MESSAGE"
"forked": "./scripts/check_forked.sh:PATH:LINE:MESSAGE",
"unforked": "./scripts/check_unforked.sh:PATH:LINE:MESSAGE"
},
"Enable": [
"forked",
@ -11,6 +12,7 @@
"golint",
"license",
"misspell",
"unforked",
"vet"
],
"Exclude": [

View File

@ -53,7 +53,7 @@ type backoffer interface {
// JSONClient provides common functionality for interacting with a JSON server
// that uses cryptographic signatures.
type JSONClient struct {
uri string // the base URI of the server. e.g. http://ct.googleapis/pilot
uri string // the base URI of the server. e.g. https://ct.googleapis/pilot
httpClient *http.Client // used to interact with the server via HTTP
Verifier *ct.SignatureVerifier // nil for no verification (e.g. no public key available)
logger Logger // interface to use for logging warnings and errors
@ -139,6 +139,11 @@ func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) {
}, nil
}
// BaseURI returns the base URI that the JSONClient makes queries to.
func (c *JSONClient) BaseURI() string {
return c.uri
}
// GetAndParse makes a HTTP GET call to the given path, and attempta to parse
// the response as a JSON representation of the rsp structure. Returns the
// http.Response, the body of the response, and an error. Note that the

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
@ -189,6 +190,53 @@ func MerkleTreeLeafFromChain(chain []*x509.Certificate, etype LogEntryType, time
return &leaf, nil
}
// MerkleTreeLeafForEmbeddedSCT generates a MerkleTreeLeaf from a chain and an
// SCT timestamp, where the leaf certificate at chain[0] is a certificate that
// contains embedded SCTs. It is assumed that the timestamp provided is from
// one of the SCTs embedded within the leaf certificate.
func MerkleTreeLeafForEmbeddedSCT(chain []*x509.Certificate, timestamp uint64) (*MerkleTreeLeaf, error) {
// For building the leaf for a certificate and SCT where the SCT is embedded
// in the certificate, we need to build the original precertificate TBS
// data. First, parse the leaf cert and its issuer.
if len(chain) < 2 {
return nil, fmt.Errorf("no issuer cert available for precert leaf building")
}
issuer := chain[1]
cert := chain[0]
// Next, post-process the DER-encoded TBSCertificate, to remove the SCTList
// extension.
tbs, err := x509.RemoveSCTList(cert.RawTBSCertificate)
if err != nil {
return nil, fmt.Errorf("failed to remove SCT List extension: %v", err)
}
return &MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
EntryType: PrecertLogEntryType,
Timestamp: timestamp,
PrecertEntry: &PreCert{
IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo),
TBSCertificate: tbs,
},
},
}, nil
}
// LeafHashForLeaf returns the leaf hash for a Merkle tree leaf.
func LeafHashForLeaf(leaf *MerkleTreeLeaf) ([sha256.Size]byte, error) {
leafData, err := tls.Marshal(*leaf)
if err != nil {
return [sha256.Size]byte{}, fmt.Errorf("failed to tls-encode MerkleTreeLeaf: %s", err)
}
data := append([]byte{TreeLeafPrefix}, leafData...)
leafHash := sha256.Sum256(data)
return leafHash, nil
}
// IsPreIssuer indicates whether a certificate is a pre-cert issuer with the specific
// certificate transparency extended key usage.
func IsPreIssuer(issuer *x509.Certificate) bool {
@ -253,3 +301,11 @@ func LogEntryFromLeaf(index int64, leafEntry *LeafEntry) (*LogEntry, error) {
// err may hold a x509.NonFatalErrors object.
return &entry, err
}
// TimestampToTime converts a timestamp in the style of RFC 6962 (milliseconds
// since UNIX epoch) to a Go Time.
func TimestampToTime(ts uint64) time.Time {
secs := int64(ts / 1000)
msecs := int64(ts % 1000)
return time.Unix(secs, msecs*1000000)
}

View File

@ -20,6 +20,7 @@ import (
"crypto/elliptic"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/pem"
"flag"
"fmt"
@ -42,6 +43,15 @@ func PublicKeyFromPEM(b []byte) (crypto.PublicKey, SHA256Hash, []byte, error) {
return k, sha256.Sum256(p.Bytes), rest, err
}
// PublicKeyFromB64 parses a base64-encoded public key.
func PublicKeyFromB64(b64PubKey string) (crypto.PublicKey, error) {
der, err := base64.StdEncoding.DecodeString(b64PubKey)
if err != nil {
return nil, fmt.Errorf("error decoding public key: %s", err)
}
return x509.ParsePKIXPublicKey(der)
}
// SignatureVerifier can verify signatures on SCTs and STHs
type SignatureVerifier struct {
pubKey crypto.PublicKey

View File

@ -208,6 +208,165 @@ const (
"BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf\n" +
"kGzKLC0+6/yBmWTr2M98CIY/vg==\n" +
"-----END CERTIFICATE-----\n"
// TestEmbeddedCertPEM is a certificate containing an embedded SCT that
// corresponds to TestPreCertProof.
// Certificate:
// Data:
// Version: 3 (0x2)
// Serial Number: 7 (0x7)
// Signature Algorithm: sha1WithRSAEncryption
// Issuer: C = GB, O = Certificate Transparency CA, ST = Wales, L = Erw Wen
// Validity
// Not Before: Jun 1 00:00:00 2012 GMT
// Not After : Jun 1 00:00:00 2022 GMT
// Subject: C = GB, O = Certificate Transparency, ST = Wales, L = Erw Wen
// Subject Public Key Info:
// Public Key Algorithm: rsaEncryption
// Public-Key: (1024 bit)
// Modulus:
// 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c:
// 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1:
// ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b:
// 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4:
// dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10:
// cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87:
// 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d:
// c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a:
// 05:b0:14:09:ff:5d:d8:7e:b5
// Exponent: 65537 (0x10001)
// X509v3 extensions:
// X509v3 Subject Key Identifier:
// 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4
// X509v3 Authority Key Identifier:
// keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55
// DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen
// serial:00
//
// X509v3 Basic Constraints:
// CA:FALSE
// CT Precertificate SCTs:
// Signed Certificate Timestamp:
// Version : v1 (0x0)
// Log ID : DF:1C:2E:C1:15:00:94:52:47:A9:61:68:32:5D:DC:5C:
// 79:59:E8:F7:C6:D3:88:FC:00:2E:0B:BD:3F:74:D7:64
// Timestamp : Apr 5 17:04:16.275 2013 GMT
// Extensions: none
// Signature : ecdsa-with-SHA256
// 30:45:02:20:48:2F:67:51:AF:35:DB:A6:54:36:BE:1F:
// D6:64:0F:3D:BF:9A:41:42:94:95:92:45:30:28:8F:A3:
// E5:E2:3E:06:02:21:00:E4:ED:C0:DB:3A:C5:72:B1:E2:
// F5:E8:AB:6A:68:06:53:98:7D:CF:41:02:7D:FE:FF:A1:
// 05:51:9D:89:ED:BF:08
// Signature Algorithm: sha1WithRSAEncryption
// 8a:0c:4b:ef:09:9d:47:92:79:af:a0:a2:8e:68:9f:91:e1:c4:
// 42:1b:e2:d2:69:a2:ea:6c:a4:e8:21:5d:de:dd:ca:15:04:a1:
// 1e:7c:87:c4:b7:7e:80:f0:e9:79:03:52:68:f2:7c:a2:0e:16:
// 68:04:ae:55:6f:31:69:81:f9:6a:39:4a:b7:ab:fd:3e:25:5a:
// c0:04:45:13:fe:76:57:0c:67:95:ab:e4:70:31:33:d3:03:f8:
// 9f:3a:fa:6b:bc:fc:51:73:19:df:d9:5b:93:42:41:21:1f:63:
// 40:35:c3:d0:78:30:7a:68:c6:07:5a:2e:20:c8:9f:36:b8:91:
// 0c:a0
TestEmbeddedCertPEM = "-----BEGIN CERTIFICATE-----\n" +
"MIIDWTCCAsKgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" +
"MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" +
"YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" +
"MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" +
"c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" +
"CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" +
"BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" +
"EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" +
"FAn/Xdh+tQIDAQABo4IBOjCCATYwHQYDVR0OBBYEFCAxVBryXAX/2GWLaEN5T16Q\n" +
"Nve0MH0GA1UdIwR2MHSAFF+diA3Ic+ZU1PgN2OawwSS0R8NVoVmkVzBVMQswCQYD\n" +
"VQQGEwJHQjEkMCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4w\n" +
"DAYDVQQIEwVXYWxlczEQMA4GA1UEBxMHRXJ3IFdlboIBADAJBgNVHRMEAjAAMIGK\n" +
"BgorBgEEAdZ5AgQCBHwEegB4AHYA3xwuwRUAlFJHqWFoMl3cXHlZ6PfG04j8AC4L\n" +
"vT9012QAAAE92yffkwAABAMARzBFAiBIL2dRrzXbplQ2vh/WZA89v5pBQpSVkkUw\n" +
"KI+j5eI+BgIhAOTtwNs6xXKx4vXoq2poBlOYfc9BAn3+/6EFUZ2J7b8IMA0GCSqG\n" +
"SIb3DQEBBQUAA4GBAIoMS+8JnUeSea+goo5on5HhxEIb4tJpoupspOghXd7dyhUE\n" +
"oR58h8S3foDw6XkDUmjyfKIOFmgErlVvMWmB+Wo5Srer/T4lWsAERRP+dlcMZ5Wr\n" +
"5HAxM9MD+J86+mu8/FFzGd/ZW5NCQSEfY0A1w9B4MHpoxgdaLiDInza4kQyg\n" +
"-----END CERTIFICATE-----\n"
// TestInvalidEmbeddedCertPEM is a certificate that contains an SCT that is
// not for the corresponding pre-certificate. The SCT embedded corresponds
// to TestInvalidProof.
// Certificate:
// Data:
// Version: 3 (0x2)
// Serial Number: 7 (0x7)
// Signature Algorithm: sha1WithRSAEncryption
// Issuer: C = GB, O = Certificate Transparency CA, ST = Wales, L = Erw Wen
// Validity
// Not Before: Jun 1 00:00:00 2012 GMT
// Not After : Jun 1 00:00:00 2022 GMT
// Subject: C = GB, O = Certificate Transparency, ST = Wales, L = Erw Wen
// Subject Public Key Info:
// Public Key Algorithm: rsaEncryption
// Public-Key: (1024 bit)
// Modulus:
// 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c:
// 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1:
// ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b:
// 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4:
// dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10:
// cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87:
// 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d:
// c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a:
// 05:b0:14:09:ff:5d:d8:7e:b5
// Exponent: 65537 (0x10001)
// X509v3 extensions:
// X509v3 Subject Key Identifier:
// 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4
// X509v3 Authority Key Identifier:
// keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55
// DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen
// serial:00
//
// X509v3 Basic Constraints:
// CA:FALSE
// CT Precertificate SCTs:
// Signed Certificate Timestamp:
// Version : v1 (0x0)
// Log ID : DF:1C:2E:C1:15:00:94:52:47:A9:61:68:32:5D:DC:5C:
// 79:59:E8:F7:C6:D3:88:FC:00:2E:0B:BD:3F:74:D7:64
// Timestamp : Apr 5 17:04:17.060 2013 GMT
// Extensions: none
// Signature : ecdsa-with-SHA256
// 30:45:02:21:00:A6:D3:45:17:F3:39:2D:9E:C5:D2:57:
// AD:F1:C5:97:DC:45:BD:4C:D3:B7:38:56:C6:16:A9:FB:
// 99:E5:AE:75:A8:02:20:5E:26:C8:D1:C7:E2:22:FE:8C:
// DA:29:BA:EB:04:A8:34:EE:97:D3:4F:D8:17:18:F1:AA:
// E0:CD:66:F4:B8:A9:3F
// Signature Algorithm: sha1WithRSAEncryption
// af:28:89:06:38:b0:12:6f:dd:64:5d:d0:62:80:f8:10:6c:ec:
// 49:4c:f8:22:86:0a:29:d4:f1:7e:6a:a5:7c:5a:58:b2:96:cc:
// 90:c6:db:f1:22:10:4b:7f:4a:76:d6:fd:df:f2:1a:41:3a:9e:
// e7:88:7e:32:a3:c7:a2:07:3c:e6:af:ae:01:b4:1a:a2:3d:ce:
// 98:f3:ab:5e:c7:5c:e7:59:fa:7c:cc:ab:4f:fa:7a:a7:3e:7d:
// 98:38:77:c6:d0:f1:de:cd:dd:37:49:00:59:b7:91:90:b2:7f:
// 85:94:2b:7c:c8:b2:3c:bf:90:30:68:5d:21:43:c4:95:a5:39:
// 6d:9f
TestInvalidEmbeddedCertPEM = "-----BEGIN CERTIFICATE-----\n" +
"MIIDWTCCAsKgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" +
"MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" +
"YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" +
"MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" +
"c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" +
"CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" +
"BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" +
"EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" +
"FAn/Xdh+tQIDAQABo4IBOjCCATYwHQYDVR0OBBYEFCAxVBryXAX/2GWLaEN5T16Q\n" +
"Nve0MH0GA1UdIwR2MHSAFF+diA3Ic+ZU1PgN2OawwSS0R8NVoVmkVzBVMQswCQYD\n" +
"VQQGEwJHQjEkMCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4w\n" +
"DAYDVQQIEwVXYWxlczEQMA4GA1UEBxMHRXJ3IFdlboIBADAJBgNVHRMEAjAAMIGK\n" +
"BgorBgEEAdZ5AgQCBHwEegB4AHYA3xwuwRUAlFJHqWFoMl3cXHlZ6PfG04j8AC4L\n" +
"vT9012QAAAE92yfipAAABAMARzBFAiEAptNFF/M5LZ7F0let8cWX3EW9TNO3OFbG\n" +
"Fqn7meWudagCIF4myNHH4iL+jNopuusEqDTul9NP2BcY8argzWb0uKk/MA0GCSqG\n" +
"SIb3DQEBBQUAA4GBAK8oiQY4sBJv3WRd0GKA+BBs7ElM+CKGCinU8X5qpXxaWLKW\n" +
"zJDG2/EiEEt/SnbW/d/yGkE6nueIfjKjx6IHPOavrgG0GqI9zpjzq17HXOdZ+nzM\n" +
"q0/6eqc+fZg4d8bQ8d7N3TdJAFm3kZCyf4WUK3zIsjy/kDBoXSFDxJWlOW2f\n" +
"-----END CERTIFICATE-----\n"
)
var (
@ -224,6 +383,21 @@ var (
"640000013ddb27df9300000403004730450220482f6751af35dba65436be1fd6" +
"640f3dbf9a41429495924530288fa3e5e23e06022100e4edc0db3ac572b1e2f5" +
"e8ab6a680653987dcf41027dfeffa105519d89edbf08")
// TestInvalidProof is a TLS-encoded ct.SignedCertificateTimestamp
// corresponding to the invalid SCT embedded in TestInvalidEmbeddedCertPEM.
TestInvalidProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" +
"640000013ddb27e2a40000040300473045022100a6d34517f3392d9ec5d257ad" +
"f1c597dc45bd4cd3b73856c616a9fb99e5ae75a802205e26c8d1c7e222fe8cda" +
"29baeb04a834ee97d34fd81718f1aae0cd66f4b8a93f")
// TestCertB64LeafHash is the base64-encoded leaf hash of TestCertPEM with
// TestCertProof as the corresponding SCT.
TestCertB64LeafHash = "BKZLZGMbAnDW0gQWjNJNCyS2IgweWn76YW3tFlu3AuY="
// TestPreCertB64LeafHash is the base64-encoded leaf hash of TestPreCertPEM
// with TestPreCertProof as the corresponding SCT.
TestPreCertB64LeafHash = "mbk+d5FDW3oNZJCbOi828rqC3hf+qhigTWVGcXEZq1U="
)
func dh(h string) []byte {

View File

@ -34,6 +34,9 @@ const (
)
const (
// LogPublicKeyB64 is an ECDSA key copied from test/testdata/ct-server-key-public.pem
LogPublicKeyB64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3UyEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w=="
// LogPublicKeyPEM is an ECDSA key copied from test/testdata/ct-server-key-public.pem
LogPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U\n" +

View File

@ -14,7 +14,13 @@
package tls
import "fmt"
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
)
// DigitallySigned gives information about a signature, including the algorithm used
// and the signature value. Defined in RFC 5246 s4.7.
@ -94,3 +100,18 @@ func (s SignatureAlgorithm) String() string {
return fmt.Sprintf("UNKNOWN(%d)", s)
}
}
// SignatureAlgorithmFromPubKey returns the algorithm used for this public key.
// ECDSA, RSA, and DSA keys are supported. Other key types will return Anonymous.
func SignatureAlgorithmFromPubKey(k crypto.PublicKey) SignatureAlgorithm {
switch k.(type) {
case *ecdsa.PublicKey:
return ECDSA
case *rsa.PublicKey:
return RSA
case *dsa.PublicKey:
return DSA
default:
return Anonymous
}
}

View File

@ -14,7 +14,13 @@
package tls
import "testing"
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"testing"
)
func TestHashAlgorithmString(t *testing.T) {
var tests = []struct {
@ -75,3 +81,20 @@ func TestDigitallySignedString(t *testing.T) {
}
}
}
func TestSignatureAlgorithm(t *testing.T) {
for _, test := range []struct {
name string
key crypto.PublicKey
want SignatureAlgorithm
}{
{name: "ECDSA", key: new(ecdsa.PublicKey), want: ECDSA},
{name: "RSA", key: new(rsa.PublicKey), want: RSA},
{name: "DSA", key: new(dsa.PublicKey), want: DSA},
{name: "Other", key: "foo", want: Anonymous},
} {
if got := SignatureAlgorithmFromPubKey(test.key); got != test.want {
t.Errorf("%v: SignatureAlgorithm() = %v, want %v", test.name, got, test.want)
}
}
}

View File

@ -54,6 +54,12 @@ func (e LogEntryType) String() string {
}
}
// RFC6962 section 2.1 requires a prefix byte on hash inputs for second preimage resistance.
const (
TreeLeafPrefix = byte(0x00)
TreeNodePrefix = byte(0x01)
)
// MerkleLeafType represents the MerkleLeafType enum from section 3.4:
// enum { timestamped_entry(0), (255) } MerkleLeafType;
type MerkleLeafType tls.Enum // tls:"maxval:255"
@ -415,6 +421,29 @@ type GetSTHResponse struct {
TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH
}
// ToSignedTreeHead creates a SignedTreeHead from the GetSTHResponse.
func (r *GetSTHResponse) ToSignedTreeHead() (*SignedTreeHead, error) {
sth := SignedTreeHead{
TreeSize: r.TreeSize,
Timestamp: r.Timestamp,
}
if len(r.SHA256RootHash) != sha256.Size {
return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(r.SHA256RootHash))
}
copy(sth.SHA256RootHash[:], r.SHA256RootHash)
var ds DigitallySigned
if rest, err := tls.Unmarshal(r.TreeHeadSignature, &ds); err != nil {
return nil, fmt.Errorf("tls.Unmarshal(): %s", err)
} else if len(rest) > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest))
}
sth.TreeHeadSignature = ds
return &sth, nil
}
// GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency
// GET method from section 4.4. (The corresponding GET request has parameters 'first' and
// 'second'.)

View File

@ -64,3 +64,68 @@ func TestUnmarshalMerkleTreeLeaf(t *testing.T) {
}
}
}
const (
validRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20fc0"
longRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20fc000"
shortRootHash = "708981e91d1487c2a9ea901ab5a8d053c1348585afcdb5e107bf60c0c1d20f"
validSignature = "040300473045022007fb5ae3cea8f076b534a01a9a19e60625c6cc70704c6c1a7c88b30d8f67d4af022100840d37b8f2f9ce134e74eefda6a0c2ad034d591b785cdc4973c4c4f5d03f0439"
longSignature = "040300473045022007fb5ae3cea8f076b534a01a9a19e60625c6cc70704c6c1a7c88b30d8f67d4af022100840d37b8f2f9ce134e74eefda6a0c2ad034d591b785cdc4973c4c4f5d03f043900"
)
func mustHexDecode(s string) []byte {
h, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return h
}
func TestToSignedTreeHead(t *testing.T) {
tests := []struct {
desc string
rootHash string
signature string
wantErr bool
}{
{
desc: "success",
rootHash: validRootHash,
signature: validSignature,
},
{
desc: "root hash too long",
rootHash: longRootHash,
signature: validSignature,
wantErr: true,
},
{
desc: "root hash too short",
rootHash: shortRootHash,
signature: validSignature,
wantErr: true,
},
{
desc: "signature trailing data",
rootHash: validRootHash,
signature: longSignature,
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
sthResponse := &GetSTHResponse{
TreeSize: 278437663,
Timestamp: 1527076172068,
SHA256RootHash: mustHexDecode(test.rootHash),
TreeHeadSignature: mustHexDecode(test.signature),
}
sth, err := sthResponse.ToSignedTreeHead()
if gotErr := (err != nil); gotErr != test.wantErr {
t.Errorf("GetSTHResponse.ToSignedTreeHead() = %+v, %v, want err? %t", sth, err, test.wantErr)
}
})
}
}

View File

@ -9,6 +9,7 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"encoding/pem"
"fmt"
"io/ioutil"
@ -43,6 +44,7 @@ type nameConstraintsTest struct {
roots []constraintsSpec
intermediates [][]constraintsSpec
leaf leafSpec
requestedEKUs []ExtKeyUsage
expectedError string
noOpenSSL bool
}
@ -1445,6 +1447,118 @@ var nameConstraintsTests = []nameConstraintsTest{
},
expectedError: "\"https://example.com/test\" is excluded",
},
// #75: While serverAuth in a CA certificate permits clientAuth in a leaf,
// serverAuth in a leaf shouldn't permit clientAuth when requested in
// VerifyOptions.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"dns:example.com"},
ekus: []string{"serverAuth"},
},
requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth},
expectedError: "incompatible key usage",
},
// #76: However, MSSGC in a leaf should match a request for serverAuth.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"dns:example.com"},
ekus: []string{"msSGC"},
},
requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth},
},
// An invalid DNS SAN should be detected only at validation time so
// that we can process CA certificates in the wild that have invalid SANs.
// See https://github.com/golang/go/issues/23995
// #77: an invalid DNS or mail SAN will not be detected if name constaint
// checking is not triggered.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"dns:this is invalid", "email:this @ is invalid"},
},
},
// #78: an invalid DNS SAN will be detected if any name constraint checking
// is triggered.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{
bad: []string{"uri:"},
},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"dns:this is invalid"},
},
expectedError: "cannot parse dnsName",
},
// #79: an invalid email SAN will be detected if any name constraint
// checking is triggered.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{
bad: []string{"uri:"},
},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"email:this @ is invalid"},
},
expectedError: "cannot parse rfc822Name",
},
// #80: if several EKUs are requested, satisfying any of them is sufficient.
nameConstraintsTest{
roots: []constraintsSpec{
constraintsSpec{},
},
intermediates: [][]constraintsSpec{
[]constraintsSpec{
constraintsSpec{},
},
},
leaf: leafSpec{
sans: []string{"dns:example.com"},
ekus: []string{"email"},
},
requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection},
},
}
func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) {
@ -1513,6 +1627,13 @@ func makeConstraintsLeafCert(leaf leafSpec, key *ecdsa.PrivateKey, parent *Certi
}
template.IPAddresses = append(template.IPAddresses, ip)
case strings.HasPrefix(name, "invalidip:"):
ipBytes, err := hex.DecodeString(name[10:])
if err != nil {
return nil, fmt.Errorf("cannot parse invalid IP: %s", err)
}
template.IPAddresses = append(template.IPAddresses, net.IP(ipBytes))
case strings.HasPrefix(name, "email:"):
template.EmailAddresses = append(template.EmailAddresses, name[6:])
@ -1782,6 +1903,7 @@ func TestConstraintCases(t *testing.T) {
Roots: rootPool,
Intermediates: intermediatePool,
CurrentTime: time.Unix(1500, 0),
KeyUsages: test.requestedEKUs,
}
_, err = leafCert.Verify(verifyOpts)
@ -1973,12 +2095,13 @@ func TestBadNamesInConstraints(t *testing.T) {
}
func TestBadNamesInSANs(t *testing.T) {
// Bad names in SANs should not parse.
// Bad names in URI and IP SANs should not parse. Bad DNS and email SANs
// will parse and are tested in name constraint tests at the top of this
// file.
badNames := []string{
"dns:foo.com.",
"email:abc@foo.com.",
"email:foo.com.",
"uri:https://example.com./dsf",
"invalidip:0102",
"invalidip:0102030405",
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

View File

@ -12,9 +12,12 @@ import (
"net/url"
"reflect"
"runtime"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/google/certificate-transparency-go/asn1"
)
type InvalidReason int
@ -174,19 +177,29 @@ var errNotParsed = errors.New("x509: missing ASN.1 contents; use ParseCertificat
// VerifyOptions contains parameters for Certificate.Verify. It's a structure
// because other PKIX verification APIs have ended up needing many options.
type VerifyOptions struct {
DNSName string
Intermediates *CertPool
Roots *CertPool // if nil, the system roots are used
CurrentTime time.Time // if zero, the current time is used
DisableTimeChecks bool
// KeyUsage specifies which Extended Key Usage values are acceptable.
// An empty list means ExtKeyUsageServerAuth. Key usage is considered a
// constraint down the chain which mirrors Windows CryptoAPI behavior,
// but not the spec. To accept any key usage, include ExtKeyUsageAny.
DNSName string
Intermediates *CertPool
Roots *CertPool // if nil, the system roots are used
CurrentTime time.Time // if zero, the current time is used
// Options to disable various verification checks.
DisableTimeChecks bool
DisableCriticalExtensionChecks bool
DisableNameChecks bool
DisableEKUChecks bool
DisablePathLenChecks bool
DisableNameConstraintChecks bool
// KeyUsage specifies which Extended Key Usage values are acceptable. A leaf
// certificate is accepted if it contains any of the listed values. An empty
// list means ExtKeyUsageServerAuth. To accept any key usage, include
// ExtKeyUsageAny.
//
// Certificate chains are required to nest extended key usage values,
// irrespective of this value. This matches the Windows CryptoAPI behavior,
// but not the spec.
KeyUsages []ExtKeyUsage
// MaxConstraintComparisions is the maximum number of comparisons to
// perform when checking a given certificate's name constraints. If
// zero, a sensible default is used. This limit prevents pathalogical
// zero, a sensible default is used. This limit prevents pathological
// certificates from consuming excessive amounts of CPU time when
// validating.
MaxConstraintComparisions int
@ -544,11 +557,16 @@ func (c *Certificate) checkNameConstraints(count *int,
return nil
}
const (
checkingAgainstIssuerCert = iota
checkingAgainstLeafCert
)
// ekuPermittedBy returns true iff the given extended key usage is permitted by
// the given EKU from a certificate. Normally, this would be a simple
// comparison plus a special case for the “any” EKU. But, in order to support
// existing certificates, some exceptions are made.
func ekuPermittedBy(eku, certEKU ExtKeyUsage) bool {
func ekuPermittedBy(eku, certEKU ExtKeyUsage, context int) bool {
if certEKU == ExtKeyUsageAny || eku == certEKU {
return true
}
@ -565,28 +583,33 @@ func ekuPermittedBy(eku, certEKU ExtKeyUsage) bool {
eku = mapServerAuthEKUs(eku)
certEKU = mapServerAuthEKUs(certEKU)
if eku == certEKU ||
// ServerAuth in a CA permits ClientAuth in the leaf.
(eku == ExtKeyUsageClientAuth && certEKU == ExtKeyUsageServerAuth) ||
if eku == certEKU {
return true
}
// If checking a requested EKU against the list in a leaf certificate there
// are fewer exceptions.
if context == checkingAgainstLeafCert {
return false
}
// ServerAuth in a CA permits ClientAuth in the leaf.
return (eku == ExtKeyUsageClientAuth && certEKU == ExtKeyUsageServerAuth) ||
// Any CA may issue an OCSP responder certificate.
eku == ExtKeyUsageOCSPSigning ||
// Code-signing CAs can use Microsoft's commercial and
// kernel-mode EKUs.
((eku == ExtKeyUsageMicrosoftCommercialCodeSigning || eku == ExtKeyUsageMicrosoftKernelCodeSigning) && certEKU == ExtKeyUsageCodeSigning) {
return true
}
return false
(eku == ExtKeyUsageMicrosoftCommercialCodeSigning || eku == ExtKeyUsageMicrosoftKernelCodeSigning) && certEKU == ExtKeyUsageCodeSigning
}
// isValid performs validity checks on c given that it is a candidate to append
// to the chain in currentChain.
func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error {
if len(c.UnhandledCriticalExtensions) > 0 {
if !opts.DisableCriticalExtensionChecks && len(c.UnhandledCriticalExtensions) > 0 {
return UnhandledCriticalExtension{ID: c.UnhandledCriticalExtensions[0]}
}
if len(currentChain) > 0 {
if !opts.DisableNameChecks && len(currentChain) > 0 {
child := currentChain[len(currentChain)-1]
if !bytes.Equal(child.RawIssuer, c.RawSubject) {
return CertificateInvalidError{c, NameMismatch, ""}
@ -617,7 +640,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
leaf = currentChain[0]
}
if (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
if !opts.DisableNameConstraintChecks && (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() {
sanExtension, ok := leaf.getSANExtension()
if !ok {
// This is the deprecated, legacy case of depending on
@ -633,8 +656,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
name := string(data)
mailbox, ok := parseRFC2821Mailbox(name)
if !ok {
// This certificate should not have parsed.
return errors.New("x509: internal error: rfc822Name SAN failed to parse")
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
}
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
@ -646,6 +668,10 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
case nameTypeDNS:
name := string(data)
if _, ok := domainToReverseLabels(name); !ok {
return fmt.Errorf("x509: cannot parse dnsName %q", name)
}
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
func(parsedName, constraint interface{}) (bool, error) {
return matchDomainConstraint(parsedName.(string), constraint.(string))
@ -692,7 +718,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
}
}
checkEKUs := certType == intermediateCertificate
checkEKUs := !opts.DisableEKUChecks && certType == intermediateCertificate
// If no extended key usages are specified, then all are acceptable.
if checkEKUs && (len(c.ExtKeyUsage) == 0 && len(c.UnknownExtKeyUsage) == 0) {
@ -719,7 +745,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
for _, caEKU := range c.ExtKeyUsage {
comparisonCount++
if ekuPermittedBy(eku, caEKU) {
if ekuPermittedBy(eku, caEKU, checkingAgainstIssuerCert) {
continue NextEKU
}
}
@ -766,7 +792,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
return CertificateInvalidError{c, NotAuthorizedToSign, ""}
}
if c.BasicConstraintsValid && c.MaxPathLen >= 0 {
if !opts.DisablePathLenChecks && c.BasicConstraintsValid && c.MaxPathLen >= 0 {
numIntermediates := len(currentChain) - 1
if numIntermediates > c.MaxPathLen {
return CertificateInvalidError{c, TooManyIntermediates, ""}
@ -776,6 +802,18 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
return nil
}
// formatOID formats an ASN.1 OBJECT IDENTIFER in the common, dotted style.
func formatOID(oid asn1.ObjectIdentifier) string {
ret := ""
for i, v := range oid {
if i > 0 {
ret += "."
}
ret += strconv.Itoa(v)
}
return ret
}
// Verify attempts to verify c by building one or more chains from c to a
// certificate in opts.Roots, using certificates in opts.Intermediates if
// needed. If successful, it returns one or more chains where the first
@ -840,7 +878,7 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
}
// If no key usages are specified, then any are acceptable.
checkEKU := len(c.ExtKeyUsage) > 0
checkEKU := !opts.DisableEKUChecks && len(c.ExtKeyUsage) > 0
for _, eku := range requestedKeyUsages {
if eku == ExtKeyUsageAny {
@ -850,16 +888,33 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e
}
if checkEKU {
foundMatch := false
NextUsage:
for _, eku := range requestedKeyUsages {
for _, leafEKU := range c.ExtKeyUsage {
if ekuPermittedBy(eku, leafEKU) {
continue NextUsage
if ekuPermittedBy(eku, leafEKU, checkingAgainstLeafCert) {
foundMatch = true
break NextUsage
}
}
}
oid, _ := oidFromExtKeyUsage(eku)
return nil, CertificateInvalidError{c, IncompatibleUsage, fmt.Sprintf("%#v", oid)}
if !foundMatch {
msg := "leaf contains the following, recognized EKUs: "
for i, leafEKU := range c.ExtKeyUsage {
oid, ok := oidFromExtKeyUsage(leafEKU)
if !ok {
continue
}
if i > 0 {
msg += ", "
}
msg += formatOID(oid)
}
return nil, CertificateInvalidError{c, IncompatibleUsage, msg}
}
}

View File

@ -19,16 +19,18 @@ import (
var supportSHA2 = true
type verifyTest struct {
leaf string
intermediates []string
roots []string
currentTime int64
dnsName string
systemSkip bool
keyUsages []ExtKeyUsage
testSystemRootsError bool
sha2 bool
disableTimeChecks bool
leaf string
intermediates []string
roots []string
currentTime int64
dnsName string
systemSkip bool
keyUsages []ExtKeyUsage
testSystemRootsError bool
sha2 bool
disableTimeChecks bool
disableCriticalExtensionChecks bool
disableNameChecks bool
errorCallback func(*testing.T, int, error) bool
expectedChains [][]string
@ -296,7 +298,18 @@ var verifyTests = []verifyTest{
currentTime: 1475787715,
systemSkip: true,
errorCallback: expectSubjectIssuerMismatcthError,
errorCallback: expectSubjectIssuerMismatchError,
},
{
leaf: issuerSubjectMatchLeaf,
roots: []string{issuerSubjectMatchRoot},
currentTime: 1475787715,
systemSkip: true,
disableNameChecks: true,
expectedChains: [][]string{
{"Leaf", "Root ca"},
},
},
{
// An X.509 v1 certificate should not be accepted as an
@ -355,6 +368,40 @@ var verifyTests = []verifyTest{
errorCallback: expectUnhandledCriticalExtension,
},
{
leaf: criticalExtLeafWithExt,
dnsName: "example.com",
intermediates: []string{criticalExtIntermediate},
roots: []string{criticalExtRoot},
currentTime: 1486684488,
systemSkip: true,
disableCriticalExtensionChecks: true,
expectedChains: [][]string{
{
"example.com",
"Intermediate",
"Root",
},
},
},
{
leaf: criticalExtLeaf,
dnsName: "example.com",
intermediates: []string{criticalExtIntermediateWithExt},
roots: []string{criticalExtRoot},
currentTime: 1486684488,
systemSkip: true,
disableCriticalExtensionChecks: true,
expectedChains: [][]string{
{
"example.com",
"Intermediate with Critical Extension",
"Root",
},
},
},
}
func expectHostnameError(t *testing.T, i int, err error) (ok bool) {
@ -414,7 +461,7 @@ func expectHashError(t *testing.T, i int, err error) bool {
return true
}
func expectSubjectIssuerMismatcthError(t *testing.T, i int, err error) (ok bool) {
func expectSubjectIssuerMismatchError(t *testing.T, i int, err error) (ok bool) {
if inval, ok := err.(CertificateInvalidError); !ok || inval.Reason != NameMismatch {
t.Errorf("#%d: error was not a NameMismatch: %s", i, err)
return false
@ -467,11 +514,13 @@ func testVerify(t *testing.T, useSystemRoots bool) {
}
opts := VerifyOptions{
Intermediates: NewCertPool(),
DNSName: test.dnsName,
CurrentTime: time.Unix(test.currentTime, 0),
KeyUsages: test.keyUsages,
DisableTimeChecks: test.disableTimeChecks,
Intermediates: NewCertPool(),
DNSName: test.dnsName,
CurrentTime: time.Unix(test.currentTime, 0),
KeyUsages: test.keyUsages,
DisableTimeChecks: test.disableTimeChecks,
DisableCriticalExtensionChecks: test.disableCriticalExtensionChecks,
DisableNameChecks: test.disableNameChecks,
}
if !useSystemRoots {

View File

@ -737,7 +737,9 @@ type Certificate struct {
OCSPServer []string
IssuingCertificateURL []string
// Subject Alternate Name values
// Subject Alternate Name values. (Note that these values may not be valid
// if invalid values were contained within a parsed certificate. For
// example, an element of DNSNames may not be a valid DNS domain name.)
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP
@ -792,6 +794,20 @@ func (c *Certificate) Equal(other *Certificate) bool {
return bytes.Equal(c.Raw, other.Raw)
}
// IsPrecertificate checks whether the certificate is a precertificate, by
// checking for the presence of the CT Poison extension.
func (c *Certificate) IsPrecertificate() bool {
if c == nil {
return false
}
for _, ext := range c.Extensions {
if ext.Id.Equal(OIDExtensionCTPoison) {
return true
}
}
return false
}
func (c *Certificate) hasSANExtension() bool {
return oidInExtensions(OIDExtensionSubjectAltName, c.Extensions)
}
@ -995,6 +1011,50 @@ func (h UnhandledCriticalExtension) Error() string {
return fmt.Sprintf("x509: unhandled critical extension (%v)", h.ID)
}
// removeExtension takes a DER-encoded TBSCertificate, removes the extension
// specified by oid (preserving the order of other extensions), and returns the
// result still as a DER-encoded TBSCertificate. This function will fail if
// there is not exactly 1 extension of the type specified by the oid present.
func removeExtension(tbsData []byte, oid asn1.ObjectIdentifier) ([]byte, error) {
var tbs tbsCertificate
rest, err := asn1.Unmarshal(tbsData, &tbs)
if err != nil {
return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err)
} else if rLen := len(rest); rLen > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen)
}
extAt := -1
for i, ext := range tbs.Extensions {
if ext.Id.Equal(oid) {
if extAt != -1 {
return nil, errors.New("multiple extensions of specified type present")
}
extAt = i
}
}
if extAt == -1 {
return nil, errors.New("no extension of specified type present")
}
tbs.Extensions = append(tbs.Extensions[:extAt], tbs.Extensions[extAt+1:]...)
// Clear out the asn1.RawContent so the re-marshal operation sees the
// updated structure (rather than just copying the out-of-date DER data).
tbs.Raw = nil
data, err := asn1.Marshal(tbs)
if err != nil {
return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err)
}
return data, nil
}
// RemoveSCTList takes a DER-encoded TBSCertificate and removes the CT SCT
// extension that contains the SCT list (preserving the order of other
// extensions), and returns the result still as a DER-encoded TBSCertificate.
// This function will fail if there is not exactly 1 CT SCT extension present.
func RemoveSCTList(tbsData []byte) ([]byte, error) {
return removeExtension(tbsData, OIDExtensionCTSCT)
}
// RemoveCTPoison takes a DER-encoded TBSCertificate and removes the CT poison
// extension (preserving the order of other extensions), and returns the result
// still as a DER-encoded TBSCertificate. This function will fail if there is
@ -1019,27 +1079,18 @@ func RemoveCTPoison(tbsData []byte) ([]byte, error) {
// - The precert's AuthorityKeyId is changed to the AuthorityKeyId of the
// intermediate.
func BuildPrecertTBS(tbsData []byte, preIssuer *Certificate) ([]byte, error) {
data, err := removeExtension(tbsData, OIDExtensionCTPoison)
if err != nil {
return nil, err
}
var tbs tbsCertificate
rest, err := asn1.Unmarshal(tbsData, &tbs)
rest, err := asn1.Unmarshal(data, &tbs)
if err != nil {
return nil, fmt.Errorf("failed to parse TBSCertificate: %v", err)
} else if rLen := len(rest); rLen > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after TBSCertificate", rLen)
}
poisonAt := -1
for i, ext := range tbs.Extensions {
if ext.Id.Equal(OIDExtensionCTPoison) {
if poisonAt != -1 {
return nil, errors.New("multiple CT poison extensions present")
}
poisonAt = i
}
}
if poisonAt == -1 {
return nil, errors.New("no CT poison extension present")
}
tbs.Extensions = append(tbs.Extensions[:poisonAt], tbs.Extensions[poisonAt+1:]...)
tbs.Raw = nil
if preIssuer != nil {
// Update the precert's Issuer field. Use the RawIssuer rather than the
@ -1092,9 +1143,13 @@ func BuildPrecertTBS(tbsData []byte, preIssuer *Certificate) ([]byte, error) {
}
tbs.Extensions = append(tbs.Extensions, authKeyIDExt)
}
// Clear out the asn1.RawContent so the re-marshal operation sees the
// updated structure (rather than just copying the out-of-date DER data).
tbs.Raw = nil
}
data, err := asn1.Marshal(tbs)
data, err = asn1.Marshal(tbs)
if err != nil {
return nil, fmt.Errorf("failed to re-marshal TBSCertificate: %v", err)
}
@ -1235,7 +1290,7 @@ type NonFatalErrors struct {
Errors []error
}
// Adds an error to the list of errors contained by NonFatalErrors.
// AddError adds an error to the list of errors contained by NonFatalErrors.
func (e *NonFatalErrors) AddError(err error) {
e.Errors = append(e.Errors, err)
}
@ -1250,7 +1305,7 @@ func (e NonFatalErrors) Error() string {
return r
}
// Returns true if |e| contains at least one error
// HasError returns true if |e| contains at least one error
func (e *NonFatalErrors) HasError() bool {
return len(e.Errors) > 0
}
@ -1337,17 +1392,9 @@ func parseSANExtension(value []byte, nfe *NonFatalErrors) (dnsNames, emailAddres
err = forEachSAN(value, func(tag int, data []byte) error {
switch tag {
case nameTypeEmail:
mailbox := string(data)
if _, ok := parseRFC2821Mailbox(mailbox); !ok {
return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox)
}
emailAddresses = append(emailAddresses, mailbox)
emailAddresses = append(emailAddresses, string(data))
case nameTypeDNS:
domain := string(data)
if _, ok := domainToReverseLabels(domain); !ok {
return fmt.Errorf("x509: cannot parse dnsName %q", string(data))
}
dnsNames = append(dnsNames, domain)
dnsNames = append(dnsNames, string(data))
case nameTypeURI:
uri, err := url.Parse(string(data))
if err != nil {
@ -1364,7 +1411,7 @@ func parseSANExtension(value []byte, nfe *NonFatalErrors) (dnsNames, emailAddres
case net.IPv4len, net.IPv6len:
ipAddresses = append(ipAddresses, data)
default:
nfe.AddError(fmt.Errorf("x509: certificate contained IP address of length %d : %v", len(data), data))
nfe.AddError(errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data))))
}
}

View File

@ -1085,7 +1085,8 @@ func TestRSAPSSSelfSigned(t *testing.T) {
}
}
const pemCertificate = `-----BEGIN CERTIFICATE-----
const (
pemCertificate = `-----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIRAKQkkrFx1T/dgB/Go/xBM5swDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjA4MTcyMDM2MDdaFw0xNzA4MTcyMDM2
MDdaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
@ -1104,6 +1105,64 @@ fnnktsblSUV4lRCit0ymC7Ojhe+gzCCwkgs5kDzVVag+tnl/0e2DloIjASwOhpbH
KVcg7fBd484ht/sS+l0dsB4KDOSpd8JzVDMF8OZqlaydizoJO0yWr9GbCN1+OKq5
EhLrEqU=
-----END CERTIFICATE-----`
pemPrecertificate = `-----BEGIN CERTIFICATE-----
MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu
c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/
BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk
EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw
FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3
tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE
BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG
A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor
BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg
h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL
BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf
kGzKLC0+6/yBmWTr2M98CIY/vg==
-----END CERTIFICATE-----`
)
func TestIsPrecertificate(t *testing.T) {
tests := []struct {
desc string
certPEM string
want bool
}{
{
desc: "certificate",
certPEM: pemCertificate,
want: false,
},
{
desc: "precertificate",
certPEM: pemPrecertificate,
want: true,
},
{
desc: "nil",
certPEM: "",
want: false,
},
}
for _, test := range tests {
var cert *Certificate
if test.certPEM != "" {
var err error
cert, err = certificateFromPEM(test.certPEM)
if err != nil {
t.Errorf("%s: error parsing certificate: %s", test.desc, err)
continue
}
}
if got := cert.IsPrecertificate(); got != test.want {
t.Errorf("%s: c.IsPrecertificate() = %t, want %t", test.desc, got, test.want)
}
}
}
func TestCRLCreation(t *testing.T) {
block, _ := pem.Decode([]byte(pemPrivateKey))
@ -1338,8 +1397,8 @@ func TestRemoveCTPoison(t *testing.T) {
}{
{name: "invalid-der", tbs: "01020304", errstr: "failed to parse"},
{name: "trailing-data", tbs: tbsPoisonMiddle + "01020304", errstr: "trailing data"},
{name: "no-poison-ext", tbs: tbsNoPoison, errstr: "no CT poison extension present"},
{name: "two-poison-exts", tbs: tbsPoisonTwice, errstr: "multiple CT poison extensions present"},
{name: "no-poison-ext", tbs: tbsNoPoison, errstr: "no extension of specified type present"},
{name: "two-poison-exts", tbs: tbsPoisonTwice, errstr: "multiple extensions of specified type present"},
{name: "poison-first", tbs: tbsPoisonFirst, want: tbsNoPoison},
{name: "poison-last", tbs: tbsPoisonLast, want: tbsNoPoison},
{name: "poison-middle", tbs: tbsPoisonMiddle, want: tbsNoPoison},

View File

@ -19,7 +19,10 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/google/certificate-transparency-go/x509"
)
// ReadPossiblePEMFile loads data from a file which may be in DER format
@ -71,3 +74,42 @@ func dePEM(data []byte, blockname string) [][]byte {
}
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 ioutil.ReadFile(target)
}
rsp, err := client.Get(u.String())
if err != nil {
return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err)
}
return ioutil.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 := ioutil.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
}

View File

@ -659,12 +659,86 @@ func oidAlreadyPrinted(oid asn1.ObjectIdentifier) bool {
return false
}
// CertificateFromPEM takes a string representing a certificate in PEM format
// and returns the corresponding x509.Certificate object.
func CertificateFromPEM(pemBytes string) (*x509.Certificate, error) {
block, _ := pem.Decode([]byte(pemBytes))
// 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("failed to decode PEM")
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 _, err := tls.Unmarshal(sctData.Val, &sct); err != nil {
return nil, fmt.Errorf("error parsing SCT: %s", err)
}
return &sct, 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)
}