This commit is contained in:
Mikaël Cluseau
2018-06-17 18:32:44 +11:00
parent f92c531f5d
commit 4d889632f6
500 changed files with 133832 additions and 0 deletions

View File

@ -0,0 +1,24 @@
*.iml
*.swo
*.swp
*.tfstate
*.tfstate.backup
*~
/.idea
/certcheck
/chainfix
/coverage.txt
/createtree
/crlcheck
/ctclient
/ct_server
/ct_hammer
/data
/dumpscts
/etcdiscover
/gossip_server
/preloader
/scanlog
/trillian_log_server
/trillian_log_signer
/trillian.json

View File

@ -0,0 +1,66 @@
sudo: false
language: go
os: linux
go: 1.9
env:
- GOFLAGS=
- GOFLAGS=-race
- GOFLAGS= WITH_ETCD=true
- GOFLAGS=-race WITH_ETCD=true
matrix:
fast_finish: true
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
- |
(
cd ../protoc
wget https://github.com/google/protobuf/releases/download/v3.2.0/protoc-3.2.0-${TRAVIS_OS_NAME}-x86_64.zip
unzip protoc-3.2.0-${TRAVIS_OS_NAME}-x86_64.zip
)
- export PATH=$(pwd)/../protoc/bin:$PATH
- go get -d -t ./...
- go get github.com/alecthomas/gometalinter
- gometalinter --install
- go get -u github.com/golang/protobuf/proto
- go get -u github.com/golang/protobuf/protoc-gen-go
- go install github.com/golang/mock/mockgen
# install vendored etcd binary
- go install ./vendor/github.com/coreos/etcd/cmd/etcd
- go install ./vendor/github.com/coreos/etcd/cmd/etcdctl
- pushd ${GOPATH}/src/github.com/google/trillian
- go get -d -t ./...
- popd
script:
- set -e
- export TRILLIAN_SQL_DRIVER=mysql
- cd $HOME/gopath/src/github.com/google/certificate-transparency-go
- ./scripts/presubmit.sh ${PRESUBMIT_OPTS}
- |
# Check re-generation didn't change anything
status=$(git status --porcelain | grep -v coverage) || :
if [[ -n ${status} ]]; then
echo "Regenerated files differ from checked-in versions: ${status}"
git status
git diff
exit 1
fi
- |
if [[ "${WITH_ETCD}" == "true" ]]; then
export ETCD_DIR="${GOPATH}/bin"
fi
- ./trillian/integration/integration_test.sh
- HAMMER_OPTS="--operations=1500" ./trillian/integration/ct_hammer_test.sh
- set +e
after_success:
- cp /tmp/coverage.txt .
- bash <(curl -s https://codecov.io/bash)

View File

@ -0,0 +1,27 @@
# This is the official list of benchmark authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
#
# Names should be added to this file as:
# Name or Organization <email address>
# The email address is not required for organizations.
#
# Please keep the list sorted.
Comodo CA Limited
Ed Maste <emaste@freebsd.org>
Fiaz Hossain <fiaz.hossain@salesforce.com>
Google Inc.
Internet Security Research Group
Jeff Trawick <trawick@gmail.com>
Katriel Cohn-Gordon <katriel.cohn-gordon@cybersecurity.ox.ac.uk>
Laël Cellier <lael.cellier@gmail.com>
Mark Schloesser <ms@mwcollect.org>
NORDUnet A/S
Nicholas Galbreath <nickg@client9.com>
Oliver Weidner <Oliver.Weidner@gmail.com>
PrimeKey Solutions AB
Ruslan Kovalov <ruslan.kovalyov@gmail.com>
Venafi, Inc.
Vladimir Rutsky <vladimir@rutsky.org>
Ximin Luo <infinity0@gmx.com>

View File

@ -0,0 +1,58 @@
# How to contribute #
We'd love to accept your patches and contributions to this project. There are
a just a few small guidelines you need to follow.
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
Once your CLA is submitted (or if you already submitted one for
another Google project), make a commit adding yourself to the
[AUTHORS][] and [CONTRIBUTORS][] files. This commit can be part
of your first [pull request][].
[AUTHORS]: AUTHORS
[CONTRIBUTORS]: CONTRIBUTORS
## Submitting a patch ##
1. It's generally best to start by opening a new issue describing the bug or
feature you're intending to fix. Even if you think it's relatively minor,
it's helpful to know what people are working on. Mention in the initial
issue that you are planning to work on that bug or feature so that it can
be assigned to you.
1. Follow the normal process of [forking][] the project, and setup a new
branch to work in. It's important that each group of changes be done in
separate branches in order to ensure that a pull request only includes the
commits related to that bug or feature.
1. Do your best to have [well-formed commit messages][] for each change.
This provides consistency throughout the project, and ensures that commit
messages are able to be formatted properly by various git tools.
1. Finally, push the commits to your fork and submit a [pull request][].
[forking]: https://help.github.com/articles/fork-a-repo
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[pull request]: https://help.github.com/articles/creating-a-pull-request

View File

@ -0,0 +1,57 @@
# People who have agreed to one of the CLAs and can contribute patches.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# https://developers.google.com/open-source/cla/individual
# https://developers.google.com/open-source/cla/corporate
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
#
# Names should be added to this file as:
# Name <email address>
#
# Please keep the list sorted.
Adam Eijdenberg <eijdenberg@google.com> <adam.eijdenberg@gmail.com>
Al Cutter <al@google.com>
Ben Laurie <benl@google.com> <ben@links.org>
Chris Kennelly <ckennelly@google.com> <ckennelly@ckennelly.com>
David Drysdale <drysdale@google.com>
Deyan Bektchiev <deyan.bektchiev@venafi.com> <deyan@bektchiev.net>
Ed Maste <emaste@freebsd.org>
Emilia Kasper <ekasper@google.com>
Eran Messeri <eranm@google.com> <eran.mes@gmail.com>
Fiaz Hossain <fiaz.hossain@salesforce.com>
Gary Belvin <gbelvin@google.com> <gdbelvin@gmail.com>
Jeff Trawick <trawick@gmail.com>
Joe Tsai <joetsai@digital-static.net>
Kat Joyce <katjoyce@google.com>
Katriel Cohn-Gordon <katriel.cohn-gordon@cybersecurity.ox.ac.uk>
Kiril Nikolov <kiril.nikolov@venafi.com>
Konrad Kraszewski <kraszewski@google.com> <laiquendir@gmail.com>
Laël Cellier <lael.cellier@gmail.com>
Linus Nordberg <linus@nordu.net>
Mark Schloesser <ms@mwcollect.org>
Nicholas Galbreath <nickg@client9.com>
Oliver Weidner <Oliver.Weidner@gmail.com>
Pascal Leroy <phl@google.com>
Paul Hadfield <hadfieldp@google.com> <paul@phad.org.uk>
Paul Lietar <lietar@google.com>
Pierre Phaneuf <pphaneuf@google.com>
Rob Percival <robpercival@google.com>
Rob Stradling <rob@comodo.com>
Roland Shoemaker <roland@letsencrypt.org>
Ruslan Kovalov <ruslan.kovalyov@gmail.com>
Samuel Lidén Borell <samuel@kodafritt.se>
Vladimir Rutsky <vladimir@rutsky.org>
Ximin Luo <infinity0@gmx.com>

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,144 @@
# Certificate Transparency: Go Code
[![Build Status](https://travis-ci.org/google/certificate-transparency-go.svg?branch=master)](https://travis-ci.org/google/certificate-transparency-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/google/certificate-transparency-go)](https://goreportcard.com/report/github.com/google/certificate-transparency-go)
[![GoDoc](https://godoc.org/github.com/google/certificate-transparency-go?status.svg)](https://godoc.org/github.com/google/certificate-transparency-go)
This repository holds Go code related to
[Certificate Transparency](https://www.certificate-transparency.org/) (CT). The
repository requires Go version 1.9.
- [Repository Structure](#repository-structure)
- [Trillian CT Personality](#trillian-ct-personality)
- [Working on the Code](#working-on-the-code)
- [Rebuilding Generated Code](#rebuilding-generated-code)
- [Updating Vendor Code](#updating-vendor-code)
- [Running Codebase Checks](#running-codebase-checks)
## Repository Structure
The main parts of the repository are:
- Encoding libraries:
- `asn1/` and `x509/` are forks of the upstream Go `encoding/asn1` and
`crypto/x509` libraries. We maintain separate forks of these packages
because CT is intended to act as an observatory of certificates across the
ecosystem; as such, we need to be able to process somewhat-malformed
certificates that the stricter upstream code would (correctly) reject.
Our `x509` fork also includes code for working with the
[pre-certificates defined in RFC 6962](https://tools.ietf.org/html/rfc6962#section-3.1).
- `tls` holds a library for processing TLS-encoded data as described in
[RFC 5246](https://tools.ietf.org/html/rfc5246).
- `x509util` provides additional utilities for dealing with
`x509.Certificate`s.
- CT client libraries:
- The top-level `ct` package (in `.`) holds types and utilities for working
with CT data structures defined in
[RFC 6962](https://tools.ietf.org/html/rfc6962).
- `client/` and `jsonclient/` hold libraries that allow access to CT Logs
via entrypoints described in
[section 4 of RFC 6962](https://tools.ietf.org/html/rfc6962#section-4).
- `scanner/` holds a library for scanning the entire contents of an existing
CT Log.
- Command line tools:
- `./client/ctclient` allows interaction with a CT Log
- `./scanner/scanlog` allows an existing CT Log to be scanned for certificates
of interest; please be polite when running this tool against a Log.
- `./x509util/certcheck` allows display and verification of certificates
- `./x509util/crlcheck` allows display and verification of certificate
revocation lists (CRLs).
- CT Personality for [Trillian](https://github.com/google/trillian):
- `trillian/` holds code that allows a Certificate Transparency Log to be
run using a Trillian Log as its back-end -- see
[below](#trillian-ct-personality).
## Trillian CT Personality
The `trillian/` subdirectory holds code and scripts for running a CT Log based
on the [Trillian](https://github.com/google/trillian) general transparency Log.
The main code for the CT personality is held in `trillian/ctfe`; this code
responds to HTTP requests on the
[CT API paths](https://tools.ietf.org/html/rfc6962#section-4) and translates
them to the equivalent gRPC API requests to the Trillian Log.
This obviously relies on the gRPC API definitions at
`github.com/google/trillian`; the code also uses common libraries from the
Trillian project for:
- exposing monitoring and statistics via an `interface` and corresponding
Prometheus implementation (`github.com/google/trillian/monitoring/...`)
- dealing with cryptographic keys (`github.com/google/trillian/crypto/...`).
The `trillian/integration/` directory holds scripts and tests for running the whole
system locally. In particular:
- `trillian/integration/ct_integration_test.sh` brings up local processes
running a Trillian Log server, signer and a CT personality, and exercises the
complete set of RFC 6962 API entrypoints.
- `trillian/integration/ct_hammer_test.sh` brings up a complete system and runs
a continuous randomized test of the CT entrypoints.
These scripts require a local database instance to be configured as described
in the [Trillian instructions](https://github.com/google/trillian#mysql-setup).
## Working on the Code
Developers who want to make changes to the codebase need some additional
dependencies and tools, described in the following sections. The
[Travis configuration](.travis.yml) for the codebase is also useful reference
for the required tools and scripts, as it may be more up-to-date than this
document.
### Rebuilding Generated Code
Some of the CT Go code is autogenerated from other files:
- [Protocol buffer](https://developers.google.com/protocol-buffers/) message
definitions are converted to `.pb.go` implementations.
- A mock implementation of the Trillian gRPC API (in `trillian/mockclient`) is
created with [GoMock](https://github.com/golang/mock).
Re-generating mock or protobuffer files is only needed if you're changing
the original files; if you do, you'll need to install the prerequisites:
- `mockgen` tool from https://github.com/golang/mock
- `protoc`, [Go support for protoc](https://github.com/golang/protobuf) (see
documentation linked from the
[protobuf site](https://github.com/google/protobuf))
and run the following:
```bash
go generate -x ./... # hunts for //go:generate comments and runs them
```
### Updating Vendor Code
The codebase includes a couple of external projects under the `vendor/`
subdirectory, to ensure that builds use a fixed version (typically because the
upstream repository does not guarantee back-compatibility between the tip
`master` branch and the current stable release). See
[instructions in the Trillian repo](https://github.com/google/trillian#updating-vendor-code)
for how to update vendored subtrees.
### Running Codebase Checks
The [`scripts/presubmit.sh`](scripts/presubmit.sh) script runs various tools
and tests over the codebase.
```bash
# Install gometalinter and all linters
go get -u github.com/alecthomas/gometalinter
gometalinter --install
# Run code generation, build, test and linters
./scripts/presubmit.sh
# Run build, test and linters but skip code generation
./scripts/presubmit.sh --no-generate
# Or just run the linters alone:
gometalinter --config=gometalinter.json ./...
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package asn1
import (
"reflect"
"strconv"
"strings"
)
// ASN.1 objects have metadata preceding them:
// the tag: the type of the object
// a flag denoting if this object is compound or not
// the class type: the namespace of the tag
// the length of the object, in bytes
// Here are some standard tags and classes
// ASN.1 tags represent the type of the following object.
const (
TagBoolean = 1
TagInteger = 2
TagBitString = 3
TagOctetString = 4
TagNull = 5
TagOID = 6
TagEnum = 10
TagUTF8String = 12
TagSequence = 16
TagSet = 17
TagNumericString = 18
TagPrintableString = 19
TagT61String = 20
TagIA5String = 22
TagUTCTime = 23
TagGeneralizedTime = 24
TagGeneralString = 27
)
// ASN.1 class types represent the namespace of the tag.
const (
ClassUniversal = 0
ClassApplication = 1
ClassContextSpecific = 2
ClassPrivate = 3
)
type tagAndLength struct {
class, tag, length int
isCompound bool
}
// ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead
// of" and "in addition to". When not specified, every primitive type has a
// default tag in the UNIVERSAL class.
//
// For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1
// doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT
// CONTEXT-SPECIFIC 42], that means that the tag is replaced by another.
//
// On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an
// /additional/ tag would wrap the default tag. This explicit tag will have the
// compound flag set.
//
// (This is used in order to remove ambiguity with optional elements.)
//
// You can layer EXPLICIT and IMPLICIT tags to an arbitrary depth, however we
// don't support that here. We support a single layer of EXPLICIT or IMPLICIT
// tagging with tag strings on the fields of a structure.
// fieldParameters is the parsed representation of tag string from a structure field.
type fieldParameters struct {
optional bool // true iff the field is OPTIONAL
explicit bool // true iff an EXPLICIT tag is in use.
application bool // true iff an APPLICATION tag is in use.
defaultValue *int64 // a default value for INTEGER typed fields (maybe nil).
tag *int // the EXPLICIT or IMPLICIT tag (maybe nil).
stringType int // the string tag to use when marshaling.
timeType int // the time tag to use when marshaling.
set bool // true iff this should be encoded as a SET
omitEmpty bool // true iff this should be omitted if empty when marshaling.
name string // name of field for better diagnostics
// Invariants:
// if explicit is set, tag is non-nil.
}
// Given a tag string with the format specified in the package comment,
// parseFieldParameters will parse it into a fieldParameters structure,
// ignoring unknown parts of the string.
func parseFieldParameters(str string) (ret fieldParameters) {
for _, part := range strings.Split(str, ",") {
switch {
case part == "optional":
ret.optional = true
case part == "explicit":
ret.explicit = true
if ret.tag == nil {
ret.tag = new(int)
}
case part == "generalized":
ret.timeType = TagGeneralizedTime
case part == "utc":
ret.timeType = TagUTCTime
case part == "ia5":
ret.stringType = TagIA5String
case part == "printable":
ret.stringType = TagPrintableString
case part == "numeric":
ret.stringType = TagNumericString
case part == "utf8":
ret.stringType = TagUTF8String
case strings.HasPrefix(part, "default:"):
i, err := strconv.ParseInt(part[8:], 10, 64)
if err == nil {
ret.defaultValue = new(int64)
*ret.defaultValue = i
}
case strings.HasPrefix(part, "tag:"):
i, err := strconv.Atoi(part[4:])
if err == nil {
ret.tag = new(int)
*ret.tag = i
}
case part == "set":
ret.set = true
case part == "application":
ret.application = true
if ret.tag == nil {
ret.tag = new(int)
}
case part == "omitempty":
ret.omitEmpty = true
}
}
return
}
// Given a reflected Go type, getUniversalType returns the default tag number
// and expected compound flag.
func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) {
switch t {
case rawValueType:
return true, -1, false, true
case objectIdentifierType:
return false, TagOID, false, true
case bitStringType:
return false, TagBitString, false, true
case timeType:
return false, TagUTCTime, false, true
case enumeratedType:
return false, TagEnum, false, true
case bigIntType:
return false, TagInteger, false, true
}
switch t.Kind() {
case reflect.Bool:
return false, TagBoolean, false, true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return false, TagInteger, false, true
case reflect.Struct:
return false, TagSequence, true, true
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
return false, TagOctetString, false, true
}
if strings.HasSuffix(t.Name(), "SET") {
return false, TagSet, true, true
}
return false, TagSequence, true, true
case reflect.String:
return false, TagPrintableString, false, true
}
return false, 0, false, false
}

View File

@ -0,0 +1,689 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package asn1
import (
"errors"
"fmt"
"math/big"
"reflect"
"time"
"unicode/utf8"
)
var (
byte00Encoder encoder = byteEncoder(0x00)
byteFFEncoder encoder = byteEncoder(0xff)
)
// encoder represents an ASN.1 element that is waiting to be marshaled.
type encoder interface {
// Len returns the number of bytes needed to marshal this element.
Len() int
// Encode encodes this element by writing Len() bytes to dst.
Encode(dst []byte)
}
type byteEncoder byte
func (c byteEncoder) Len() int {
return 1
}
func (c byteEncoder) Encode(dst []byte) {
dst[0] = byte(c)
}
type bytesEncoder []byte
func (b bytesEncoder) Len() int {
return len(b)
}
func (b bytesEncoder) Encode(dst []byte) {
if copy(dst, b) != len(b) {
panic("internal error")
}
}
type stringEncoder string
func (s stringEncoder) Len() int {
return len(s)
}
func (s stringEncoder) Encode(dst []byte) {
if copy(dst, s) != len(s) {
panic("internal error")
}
}
type multiEncoder []encoder
func (m multiEncoder) Len() int {
var size int
for _, e := range m {
size += e.Len()
}
return size
}
func (m multiEncoder) Encode(dst []byte) {
var off int
for _, e := range m {
e.Encode(dst[off:])
off += e.Len()
}
}
type taggedEncoder struct {
// scratch contains temporary space for encoding the tag and length of
// an element in order to avoid extra allocations.
scratch [8]byte
tag encoder
body encoder
}
func (t *taggedEncoder) Len() int {
return t.tag.Len() + t.body.Len()
}
func (t *taggedEncoder) Encode(dst []byte) {
t.tag.Encode(dst)
t.body.Encode(dst[t.tag.Len():])
}
type int64Encoder int64
func (i int64Encoder) Len() int {
n := 1
for i > 127 {
n++
i >>= 8
}
for i < -128 {
n++
i >>= 8
}
return n
}
func (i int64Encoder) Encode(dst []byte) {
n := i.Len()
for j := 0; j < n; j++ {
dst[j] = byte(i >> uint((n-1-j)*8))
}
}
func base128IntLength(n int64) int {
if n == 0 {
return 1
}
l := 0
for i := n; i > 0; i >>= 7 {
l++
}
return l
}
func appendBase128Int(dst []byte, n int64) []byte {
l := base128IntLength(n)
for i := l - 1; i >= 0; i-- {
o := byte(n >> uint(i*7))
o &= 0x7f
if i != 0 {
o |= 0x80
}
dst = append(dst, o)
}
return dst
}
func makeBigInt(n *big.Int, fieldName string) (encoder, error) {
if n == nil {
return nil, StructuralError{"empty integer", fieldName}
}
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement
// form. So we'll invert and subtract 1. If the
// most-significant-bit isn't set then we'll need to pad the
// beginning with 0xff in order to keep the number negative.
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bytes := nMinus1.Bytes()
for i := range bytes {
bytes[i] ^= 0xff
}
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
return multiEncoder([]encoder{byteFFEncoder, bytesEncoder(bytes)}), nil
}
return bytesEncoder(bytes), nil
} else if n.Sign() == 0 {
// Zero is written as a single 0 zero rather than no bytes.
return byte00Encoder, nil
} else {
bytes := n.Bytes()
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
// We'll have to pad this with 0x00 in order to stop it
// looking like a negative number.
return multiEncoder([]encoder{byte00Encoder, bytesEncoder(bytes)}), nil
}
return bytesEncoder(bytes), nil
}
}
func appendLength(dst []byte, i int) []byte {
n := lengthLength(i)
for ; n > 0; n-- {
dst = append(dst, byte(i>>uint((n-1)*8)))
}
return dst
}
func lengthLength(i int) (numBytes int) {
numBytes = 1
for i > 255 {
numBytes++
i >>= 8
}
return
}
func appendTagAndLength(dst []byte, t tagAndLength) []byte {
b := uint8(t.class) << 6
if t.isCompound {
b |= 0x20
}
if t.tag >= 31 {
b |= 0x1f
dst = append(dst, b)
dst = appendBase128Int(dst, int64(t.tag))
} else {
b |= uint8(t.tag)
dst = append(dst, b)
}
if t.length >= 128 {
l := lengthLength(t.length)
dst = append(dst, 0x80|byte(l))
dst = appendLength(dst, t.length)
} else {
dst = append(dst, byte(t.length))
}
return dst
}
type bitStringEncoder BitString
func (b bitStringEncoder) Len() int {
return len(b.Bytes) + 1
}
func (b bitStringEncoder) Encode(dst []byte) {
dst[0] = byte((8 - b.BitLength%8) % 8)
if copy(dst[1:], b.Bytes) != len(b.Bytes) {
panic("internal error")
}
}
type oidEncoder []int
func (oid oidEncoder) Len() int {
l := base128IntLength(int64(oid[0]*40 + oid[1]))
for i := 2; i < len(oid); i++ {
l += base128IntLength(int64(oid[i]))
}
return l
}
func (oid oidEncoder) Encode(dst []byte) {
dst = appendBase128Int(dst[:0], int64(oid[0]*40+oid[1]))
for i := 2; i < len(oid); i++ {
dst = appendBase128Int(dst, int64(oid[i]))
}
}
func makeObjectIdentifier(oid []int, fieldName string) (e encoder, err error) {
if len(oid) < 2 || oid[0] > 2 || (oid[0] < 2 && oid[1] >= 40) {
return nil, StructuralError{"invalid object identifier", fieldName}
}
return oidEncoder(oid), nil
}
func makePrintableString(s, fieldName string) (e encoder, err error) {
for i := 0; i < len(s); i++ {
// The asterisk is often used in PrintableString, even though
// it is invalid. If a PrintableString was specifically
// requested then the asterisk is permitted by this code.
// Ampersand is allowed in parsing due a handful of CA
// certificates, however when making new certificates
// it is rejected.
if !isPrintable(s[i], allowAsterisk, rejectAmpersand) {
return nil, StructuralError{"PrintableString contains invalid character", fieldName}
}
}
return stringEncoder(s), nil
}
func makeIA5String(s, fieldName string) (e encoder, err error) {
for i := 0; i < len(s); i++ {
if s[i] > 127 {
return nil, StructuralError{"IA5String contains invalid character", fieldName}
}
}
return stringEncoder(s), nil
}
func makeNumericString(s string, fieldName string) (e encoder, err error) {
for i := 0; i < len(s); i++ {
if !isNumeric(s[i]) {
return nil, StructuralError{"NumericString contains invalid character", fieldName}
}
}
return stringEncoder(s), nil
}
func makeUTF8String(s string) encoder {
return stringEncoder(s)
}
func appendTwoDigits(dst []byte, v int) []byte {
return append(dst, byte('0'+(v/10)%10), byte('0'+v%10))
}
func appendFourDigits(dst []byte, v int) []byte {
var bytes [4]byte
for i := range bytes {
bytes[3-i] = '0' + byte(v%10)
v /= 10
}
return append(dst, bytes[:]...)
}
func outsideUTCRange(t time.Time) bool {
year := t.Year()
return year < 1950 || year >= 2050
}
func makeUTCTime(t time.Time, fieldName string) (e encoder, err error) {
dst := make([]byte, 0, 18)
dst, err = appendUTCTime(dst, t, fieldName)
if err != nil {
return nil, err
}
return bytesEncoder(dst), nil
}
func makeGeneralizedTime(t time.Time, fieldName string) (e encoder, err error) {
dst := make([]byte, 0, 20)
dst, err = appendGeneralizedTime(dst, t, fieldName)
if err != nil {
return nil, err
}
return bytesEncoder(dst), nil
}
func appendUTCTime(dst []byte, t time.Time, fieldName string) (ret []byte, err error) {
year := t.Year()
switch {
case 1950 <= year && year < 2000:
dst = appendTwoDigits(dst, year-1900)
case 2000 <= year && year < 2050:
dst = appendTwoDigits(dst, year-2000)
default:
return nil, StructuralError{"cannot represent time as UTCTime", fieldName}
}
return appendTimeCommon(dst, t), nil
}
func appendGeneralizedTime(dst []byte, t time.Time, fieldName string) (ret []byte, err error) {
year := t.Year()
if year < 0 || year > 9999 {
return nil, StructuralError{"cannot represent time as GeneralizedTime", fieldName}
}
dst = appendFourDigits(dst, year)
return appendTimeCommon(dst, t), nil
}
func appendTimeCommon(dst []byte, t time.Time) []byte {
_, month, day := t.Date()
dst = appendTwoDigits(dst, int(month))
dst = appendTwoDigits(dst, day)
hour, min, sec := t.Clock()
dst = appendTwoDigits(dst, hour)
dst = appendTwoDigits(dst, min)
dst = appendTwoDigits(dst, sec)
_, offset := t.Zone()
switch {
case offset/60 == 0:
return append(dst, 'Z')
case offset > 0:
dst = append(dst, '+')
case offset < 0:
dst = append(dst, '-')
}
offsetMinutes := offset / 60
if offsetMinutes < 0 {
offsetMinutes = -offsetMinutes
}
dst = appendTwoDigits(dst, offsetMinutes/60)
dst = appendTwoDigits(dst, offsetMinutes%60)
return dst
}
func stripTagAndLength(in []byte) []byte {
_, offset, err := parseTagAndLength(in, 0, "")
if err != nil {
return in
}
return in[offset:]
}
func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error) {
switch value.Type() {
case flagType:
return bytesEncoder(nil), nil
case timeType:
t := value.Interface().(time.Time)
if params.timeType == TagGeneralizedTime || outsideUTCRange(t) {
return makeGeneralizedTime(t, params.name)
}
return makeUTCTime(t, params.name)
case bitStringType:
return bitStringEncoder(value.Interface().(BitString)), nil
case objectIdentifierType:
return makeObjectIdentifier(value.Interface().(ObjectIdentifier), params.name)
case bigIntType:
return makeBigInt(value.Interface().(*big.Int), params.name)
}
switch v := value; v.Kind() {
case reflect.Bool:
if v.Bool() {
return byteFFEncoder, nil
}
return byte00Encoder, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return int64Encoder(v.Int()), nil
case reflect.Struct:
t := v.Type()
for i := 0; i < t.NumField(); i++ {
if t.Field(i).PkgPath != "" {
return nil, StructuralError{"struct contains unexported fields", t.Field(i).Name}
}
}
startingField := 0
n := t.NumField()
if n == 0 {
return bytesEncoder(nil), nil
}
// If the first element of the structure is a non-empty
// RawContents, then we don't bother serializing the rest.
if t.Field(0).Type == rawContentsType {
s := v.Field(0)
if s.Len() > 0 {
bytes := s.Bytes()
/* The RawContents will contain the tag and
* length fields but we'll also be writing
* those ourselves, so we strip them out of
* bytes */
return bytesEncoder(stripTagAndLength(bytes)), nil
}
startingField = 1
}
switch n1 := n - startingField; n1 {
case 0:
return bytesEncoder(nil), nil
case 1:
return makeField(v.Field(startingField), parseFieldParameters(t.Field(startingField).Tag.Get("asn1")))
default:
m := make([]encoder, n1)
for i := 0; i < n1; i++ {
m[i], err = makeField(v.Field(i+startingField), parseFieldParameters(t.Field(i+startingField).Tag.Get("asn1")))
if err != nil {
return nil, err
}
}
return multiEncoder(m), nil
}
case reflect.Slice:
sliceType := v.Type()
if sliceType.Elem().Kind() == reflect.Uint8 {
return bytesEncoder(v.Bytes()), nil
}
var fp fieldParameters
switch l := v.Len(); l {
case 0:
return bytesEncoder(nil), nil
case 1:
return makeField(v.Index(0), fp)
default:
m := make([]encoder, l)
for i := 0; i < l; i++ {
m[i], err = makeField(v.Index(i), fp)
if err != nil {
return nil, err
}
}
return multiEncoder(m), nil
}
case reflect.String:
switch params.stringType {
case TagIA5String:
return makeIA5String(v.String(), params.name)
case TagPrintableString:
return makePrintableString(v.String(), params.name)
case TagNumericString:
return makeNumericString(v.String(), params.name)
default:
return makeUTF8String(v.String()), nil
}
}
return nil, StructuralError{"unknown Go type", params.name}
}
func makeField(v reflect.Value, params fieldParameters) (e encoder, err error) {
if !v.IsValid() {
return nil, fmt.Errorf("asn1: cannot marshal nil value")
}
// If the field is an interface{} then recurse into it.
if v.Kind() == reflect.Interface && v.Type().NumMethod() == 0 {
return makeField(v.Elem(), params)
}
if v.Kind() == reflect.Slice && v.Len() == 0 && params.omitEmpty {
return bytesEncoder(nil), nil
}
if params.optional && params.defaultValue != nil && canHaveDefaultValue(v.Kind()) {
defaultValue := reflect.New(v.Type()).Elem()
defaultValue.SetInt(*params.defaultValue)
if reflect.DeepEqual(v.Interface(), defaultValue.Interface()) {
return bytesEncoder(nil), nil
}
}
// If no default value is given then the zero value for the type is
// assumed to be the default value. This isn't obviously the correct
// behavior, but it's what Go has traditionally done.
if params.optional && params.defaultValue == nil {
if reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) {
return bytesEncoder(nil), nil
}
}
if v.Type() == rawValueType {
rv := v.Interface().(RawValue)
if len(rv.FullBytes) != 0 {
return bytesEncoder(rv.FullBytes), nil
}
t := new(taggedEncoder)
t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{rv.Class, rv.Tag, len(rv.Bytes), rv.IsCompound}))
t.body = bytesEncoder(rv.Bytes)
return t, nil
}
matchAny, tag, isCompound, ok := getUniversalType(v.Type())
if !ok || matchAny {
return nil, StructuralError{fmt.Sprintf("unknown Go type: %v", v.Type()), params.name}
}
if params.timeType != 0 && tag != TagUTCTime {
return nil, StructuralError{"explicit time type given to non-time member", params.name}
}
if params.stringType != 0 && tag != TagPrintableString {
return nil, StructuralError{"explicit string type given to non-string member", params.name}
}
switch tag {
case TagPrintableString:
if params.stringType == 0 {
// This is a string without an explicit string type. We'll use
// a PrintableString if the character set in the string is
// sufficiently limited, otherwise we'll use a UTF8String.
for _, r := range v.String() {
if r >= utf8.RuneSelf || !isPrintable(byte(r), rejectAsterisk, rejectAmpersand) {
if !utf8.ValidString(v.String()) {
return nil, errors.New("asn1: string not valid UTF-8")
}
tag = TagUTF8String
break
}
}
} else {
tag = params.stringType
}
case TagUTCTime:
if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) {
tag = TagGeneralizedTime
}
}
if params.set {
if tag != TagSequence {
return nil, StructuralError{"non sequence tagged as set", params.name}
}
tag = TagSet
}
t := new(taggedEncoder)
t.body, err = makeBody(v, params)
if err != nil {
return nil, err
}
bodyLen := t.body.Len()
class := ClassUniversal
if params.tag != nil {
if params.application {
class = ClassApplication
} else {
class = ClassContextSpecific
}
if params.explicit {
t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{ClassUniversal, tag, bodyLen, isCompound}))
tt := new(taggedEncoder)
tt.body = t
tt.tag = bytesEncoder(appendTagAndLength(tt.scratch[:0], tagAndLength{
class: class,
tag: *params.tag,
length: bodyLen + t.tag.Len(),
isCompound: true,
}))
return tt, nil
}
// implicit tag.
tag = *params.tag
}
t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{class, tag, bodyLen, isCompound}))
return t, nil
}
// Marshal returns the ASN.1 encoding of val.
//
// In addition to the struct tags recognised by Unmarshal, the following can be
// used:
//
// ia5: causes strings to be marshaled as ASN.1, IA5String values
// omitempty: causes empty slices to be skipped
// printable: causes strings to be marshaled as ASN.1, PrintableString values
// utf8: causes strings to be marshaled as ASN.1, UTF8String values
// utc: causes time.Time to be marshaled as ASN.1, UTCTime values
// generalized: causes time.Time to be marshaled as ASN.1, GeneralizedTime values
func Marshal(val interface{}) ([]byte, error) {
return MarshalWithParams(val, "")
}
// MarshalWithParams allows field parameters to be specified for the
// top-level element. The form of the params is the same as the field tags.
func MarshalWithParams(val interface{}, params string) ([]byte, error) {
e, err := makeField(reflect.ValueOf(val), parseFieldParameters(params))
if err != nil {
return nil, err
}
b := make([]byte, e.Len())
e.Encode(b)
return b, nil
}

View File

@ -0,0 +1,263 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package asn1
import (
"bytes"
"encoding/hex"
"math/big"
"strings"
"testing"
"time"
)
type intStruct struct {
A int
}
type twoIntStruct struct {
A int
B int
}
type bigIntStruct struct {
A *big.Int
}
type nestedStruct struct {
A intStruct
}
type rawContentsStruct struct {
Raw RawContent
A int
}
type implicitTagTest struct {
A int `asn1:"implicit,tag:5"`
}
type explicitTagTest struct {
A int `asn1:"explicit,tag:5"`
}
type flagTest struct {
A Flag `asn1:"tag:0,optional"`
}
type generalizedTimeTest struct {
A time.Time `asn1:"generalized"`
}
type ia5StringTest struct {
A string `asn1:"ia5"`
}
type printableStringTest struct {
A string `asn1:"printable"`
}
type genericStringTest struct {
A string
}
type optionalRawValueTest struct {
A RawValue `asn1:"optional"`
}
type omitEmptyTest struct {
A []string `asn1:"omitempty"`
}
type defaultTest struct {
A int `asn1:"optional,default:1"`
}
type applicationTest struct {
A int `asn1:"application,tag:0"`
B int `asn1:"application,tag:1,explicit"`
}
type numericStringTest struct {
A string `asn1:"numeric"`
}
type testAuthKeyID struct {
ID []byte `asn1:"optional,tag:0"`
Issuer RawValue `asn1:"optional,tag:1"`
SerialNumber *big.Int `asn1:"optional,tag:2"`
}
type testSET []int
var PST = time.FixedZone("PST", -8*60*60)
type marshalTest struct {
in interface{}
out string // hex encoded
}
func farFuture() time.Time {
t, err := time.Parse(time.RFC3339, "2100-04-05T12:01:01Z")
if err != nil {
panic(err)
}
return t
}
var marshalTests = []marshalTest{
{10, "02010a"},
{127, "02017f"},
{128, "02020080"},
{-128, "020180"},
{-129, "0202ff7f"},
{intStruct{64}, "3003020140"},
{bigIntStruct{big.NewInt(0x123456)}, "30050203123456"},
{twoIntStruct{64, 65}, "3006020140020141"},
{nestedStruct{intStruct{127}}, "3005300302017f"},
{[]byte{1, 2, 3}, "0403010203"},
{implicitTagTest{64}, "3003850140"},
{explicitTagTest{64}, "3005a503020140"},
{flagTest{true}, "30028000"},
{flagTest{false}, "3000"},
{time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"},
{time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"},
{time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"},
{farFuture(), "180f32313030303430353132303130315a"},
{generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"},
{BitString{[]byte{0x80}, 1}, "03020780"},
{BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"},
{ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"},
{ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"},
{ObjectIdentifier([]int{2, 100, 3}), "0603813403"},
{"test", "130474657374"},
{
"" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x'
"137f" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"78787878787878787878787878787878787878787878787878787878787878",
},
{
"" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x'
"138180" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878" +
"7878787878787878787878787878787878787878787878787878787878787878",
},
{ia5StringTest{"test"}, "3006160474657374"},
{optionalRawValueTest{}, "3000"},
{printableStringTest{"test"}, "3006130474657374"},
{printableStringTest{"test*"}, "30071305746573742a"},
{genericStringTest{"test"}, "3006130474657374"},
{genericStringTest{"test*"}, "30070c05746573742a"},
{genericStringTest{"test&"}, "30070c057465737426"},
{rawContentsStruct{nil, 64}, "3003020140"},
{rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"},
{RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"},
{testSET([]int{10}), "310302010a"},
{omitEmptyTest{[]string{}}, "3000"},
{omitEmptyTest{[]string{"1"}}, "30053003130131"},
{"Σ", "0c02cea3"},
{defaultTest{0}, "3003020100"},
{defaultTest{1}, "3000"},
{defaultTest{2}, "3003020102"},
{applicationTest{1, 2}, "30084001016103020102"},
{numericStringTest{"1 9"}, "30051203312039"},
{testAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}, SerialNumber: big.NewInt(0x12233445566)}, "300e8004010203048206012233445566"},
{testAuthKeyID{ID: []byte{0x01, 0x02, 0x03, 0x04}}, "3006800401020304"},
}
func TestMarshal(t *testing.T) {
for i, test := range marshalTests {
data, err := Marshal(test.in)
if err != nil {
t.Errorf("#%d failed: %s", i, err)
}
out, _ := hex.DecodeString(test.out)
if !bytes.Equal(out, data) {
t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out)
}
}
}
type marshalWithParamsTest struct {
in interface{}
params string
out string // hex encoded
}
var marshalWithParamsTests = []marshalWithParamsTest{
{intStruct{10}, "set", "310302010a"},
{intStruct{10}, "application", "600302010a"},
}
func TestMarshalWithParams(t *testing.T) {
for i, test := range marshalWithParamsTests {
data, err := MarshalWithParams(test.in, test.params)
if err != nil {
t.Errorf("#%d failed: %s", i, err)
}
out, _ := hex.DecodeString(test.out)
if !bytes.Equal(out, data) {
t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out)
}
}
}
type marshalErrTest struct {
in interface{}
err string
}
var marshalErrTests = []marshalErrTest{
{bigIntStruct{nil}, "empty integer"},
{numericStringTest{"a"}, "invalid character"},
{ia5StringTest{"\xb0"}, "invalid character"},
{printableStringTest{"!"}, "invalid character"},
}
func TestMarshalError(t *testing.T) {
for i, test := range marshalErrTests {
_, err := Marshal(test.in)
if err == nil {
t.Errorf("#%d should fail, but success", i)
continue
}
if !strings.Contains(err.Error(), test.err) {
t.Errorf("#%d got: %v want %v", i, err, test.err)
}
}
}
func TestInvalidUTF8(t *testing.T) {
_, err := Marshal(string([]byte{0xff, 0xff}))
if err == nil {
t.Errorf("invalid UTF8 string was accepted")
}
}
func BenchmarkMarshal(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, test := range marshalTests {
Marshal(test.in)
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2017 Google Inc. 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 configpb
//go:generate protoc -I=. -I=$GOPATH/src --go_out=:. multilog.proto

View File

@ -0,0 +1,124 @@
// 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"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
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"`
}
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) GetShard() []*LogShardConfig {
if m != nil {
return m.Shard
}
return nil
}
// LogShardConfig describes the acceptable date range for a single shard of a temporal
// log.
type LogShardConfig struct {
Uri string `protobuf:"bytes,1,opt,name=uri" json:"uri,omitempty"`
// The log's public key in DER-encoded PKIX form.
PublicKeyDer []byte `protobuf:"bytes,2,opt,name=public_key_der,json=publicKeyDer,proto3" json:"public_key_der,omitempty"`
// 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"`
// 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"`
}
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) GetUri() string {
if m != nil {
return m.Uri
}
return ""
}
func (m *LogShardConfig) GetPublicKeyDer() []byte {
if m != nil {
return m.PublicKeyDer
}
return nil
}
func (m *LogShardConfig) GetNotAfterStart() *google_protobuf.Timestamp {
if m != nil {
return m.NotAfterStart
}
return nil
}
func (m *LogShardConfig) GetNotAfterLimit() *google_protobuf.Timestamp {
if m != nil {
return m.NotAfterLimit
}
return nil
}
func init() {
proto.RegisterType((*TemporalLogConfig)(nil), "configpb.TemporalLogConfig")
proto.RegisterType((*LogShardConfig)(nil), "configpb.LogShardConfig")
}
func init() { proto.RegisterFile("multilog.proto", fileDescriptor0) }
var fileDescriptor0 = []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,
0xe5, 0x0b, 0xa0, 0x6c, 0x64, 0x4a, 0xbb, 0x47, 0x4e, 0xeb, 0x18, 0x0b, 0x3b, 0xcf, 0x72, 0x5e,
0x86, 0xfe, 0x25, 0x9f, 0x84, 0x1c, 0x2b, 0x43, 0x37, 0xb6, 0xa7, 0x77, 0xcf, 0xb9, 0xd2, 0xa5,
0xb9, 0x1b, 0x2d, 0x1a, 0x0b, 0x5a, 0xf8, 0x00, 0x08, 0xec, 0xee, 0x08, 0x7d, 0x67, 0xb4, 0x6f,
0x57, 0x2f, 0x1a, 0x40, 0x5b, 0xb5, 0x99, 0xfe, 0xed, 0xd8, 0x6d, 0xd0, 0x38, 0x35, 0xa0, 0x74,
0x3e, 0xa1, 0xeb, 0x1d, 0x7d, 0x3e, 0x28, 0xe7, 0x21, 0x48, 0x5b, 0x81, 0xde, 0x4d, 0x1e, 0x13,
0xf4, 0x66, 0xf8, 0x96, 0xe1, 0xc4, 0x49, 0x91, 0x95, 0x8b, 0x2d, 0x17, 0x73, 0x9f, 0xa8, 0x40,
0xef, 0x63, 0x92, 0xc0, 0x3a, 0x61, 0xeb, 0x5f, 0x42, 0xf3, 0xcb, 0x84, 0x3d, 0xd1, 0x6c, 0x0c,
0x86, 0x93, 0x82, 0x94, 0xf7, 0x75, 0x3c, 0xd9, 0x2b, 0xcd, 0xfd, 0xd8, 0x5a, 0x73, 0x6c, 0x7e,
0xd4, 0xb9, 0x39, 0xa9, 0xc0, 0xaf, 0x0a, 0x52, 0x2e, 0xeb, 0x65, 0xfa, 0x7e, 0xa9, 0xf3, 0xa7,
0x0a, 0xec, 0x83, 0x3e, 0xf6, 0x80, 0x8d, 0xec, 0x50, 0x85, 0x66, 0x40, 0x19, 0x90, 0x67, 0x05,
0x29, 0x17, 0xdb, 0x95, 0x48, 0x53, 0xc4, 0x3c, 0x45, 0x1c, 0xe6, 0x29, 0xf5, 0x43, 0x0f, 0xf8,
0x1e, 0x8d, 0x7d, 0x14, 0x2e, 0x3b, 0xac, 0x71, 0x06, 0xf9, 0xf5, 0xff, 0x3b, 0xaa, 0x28, 0xb4,
0xb7, 0x13, 0xf2, 0xf6, 0x17, 0x00, 0x00, 0xff, 0xff, 0xf8, 0xd9, 0x50, 0x5b, 0x5b, 0x01, 0x00,
0x00,
}

View File

@ -0,0 +1,43 @@
// Copyright 2017 Google Inc. 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.
syntax = "proto3";
package configpb;
import "google/protobuf/timestamp.proto";
// TemporalLogConfig is a set of LogShardConfig messages, whose
// time limits should be contiguous.
message TemporalLogConfig {
repeated LogShardConfig shard = 1;
}
// LogShardConfig describes the acceptable date range for a single shard of a temporal
// log.
message LogShardConfig {
string uri = 1;
// The log's public key in DER-encoded PKIX form.
bytes public_key_der = 2;
// not_after_start defines the start of the range of acceptable NotAfter
// values, inclusive.
// Leaving this unset implies no lower bound to the range.
google.protobuf.Timestamp not_after_start = 3;
// not_after_limit defines the end of the range of acceptable NotAfter values,
// exclusive.
// Leaving this unset implies no upper bound to the range.
google.protobuf.Timestamp not_after_limit = 4;
}

View File

@ -0,0 +1,75 @@
// Copyright 2016 Google Inc. 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 client
import (
"context"
"errors"
"strconv"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/x509"
)
// GetRawEntries exposes the /ct/v1/get-entries result with only the JSON parsing done.
func (c *LogClient) GetRawEntries(ctx context.Context, start, end int64) (*ct.GetEntriesResponse, error) {
if end < 0 {
return nil, errors.New("end should be >= 0")
}
if end < start {
return nil, errors.New("start should be <= end")
}
params := map[string]string{
"start": strconv.FormatInt(start, 10),
"end": strconv.FormatInt(end, 10),
}
if ctx == nil {
ctx = context.TODO()
}
var resp ct.GetEntriesResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetEntriesPath, params, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
return &resp, nil
}
// GetEntries attempts to retrieve the entries in the sequence [start, end] from the CT log server
// (RFC6962 s4.6) as parsed [pre-]certificates for convenience, held in a slice of ct.LogEntry structures.
// However, this does mean that any certificate parsing failures will cause a failure of the whole
// retrieval operation; for more robust retrieval of parsed certificates, use GetRawEntries() and invoke
// ct.LogEntryFromLeaf() on each individual entry.
func (c *LogClient) GetEntries(ctx context.Context, start, end int64) ([]ct.LogEntry, error) {
resp, err := c.GetRawEntries(ctx, start, end)
if err != nil {
return nil, err
}
entries := make([]ct.LogEntry, len(resp.Entries))
for i, entry := range resp.Entries {
index := start + int64(i)
logEntry, err := ct.LogEntryFromLeaf(index, &entry)
if _, ok := err.(x509.NonFatalErrors); !ok && err != nil {
return nil, err
}
entries[i] = *logEntry
}
return entries, nil
}

View File

@ -0,0 +1,283 @@
// Copyright 2014 Google Inc. 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 client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client
import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"strconv"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/jsonclient"
"github.com/google/certificate-transparency-go/tls"
)
// LogClient represents a client for a given CT Log instance
type LogClient struct {
jsonclient.JSONClient
}
// 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
// |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
// for signature verification.
func New(uri string, hc *http.Client, opts jsonclient.Options) (*LogClient, error) {
logClient, err := jsonclient.New(uri, hc, opts)
if err != nil {
return nil, err
}
return &LogClient{*logClient}, err
}
// RspError represents an error that occurred when processing a response from a server,
// and also includes key details from the http.Response that triggered the error.
type RspError struct {
Err error
StatusCode int
Body []byte
}
// Error formats the RspError instance, focusing on the error.
func (e RspError) Error() string {
return e.Err.Error()
}
// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
var resp ct.AddChainResponse
var req ct.AddChainRequest
for _, link := range chain {
req.Chain = append(req.Chain, link.Data)
}
httpRsp, body, err := c.PostAndParseWithRetry(ctx, path, &req, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
var ds ct.DigitallySigned
if rest, err := tls.Unmarshal(resp.Signature, &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,
}
}
exts, err := base64.StdEncoding.DecodeString(resp.Extensions)
if err != nil {
return nil, RspError{
Err: fmt.Errorf("invalid base64 data in Extensions (%q): %v", resp.Extensions, err),
StatusCode: httpRsp.StatusCode,
Body: body,
}
}
var logID ct.LogID
copy(logID.KeyID[:], resp.ID)
sct := &ct.SignedCertificateTimestamp{
SCTVersion: resp.SCTVersion,
LogID: logID,
Timestamp: resp.Timestamp,
Extensions: ct.CTExtensions(exts),
Signature: ds,
}
if err := c.VerifySCTSignature(*sct, ctype, chain); err != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return sct, nil
}
// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
return c.addChainWithRetry(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
}
// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
return c.addChainWithRetry(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
}
// AddJSON submits arbitrary data to to XJSON server.
func (c *LogClient) AddJSON(ctx context.Context, data interface{}) (*ct.SignedCertificateTimestamp, error) {
req := ct.AddJSONRequest{Data: data}
var resp ct.AddChainResponse
httpRsp, body, err := c.PostAndParse(ctx, ct.AddJSONPath, &req, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
var ds ct.DigitallySigned
if rest, err := tls.Unmarshal(resp.Signature, &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,
}
}
var logID ct.LogID
copy(logID.KeyID[:], resp.ID)
return &ct.SignedCertificateTimestamp{
SCTVersion: resp.SCTVersion,
LogID: logID,
Timestamp: resp.Timestamp,
Extensions: ct.CTExtensions(resp.Extensions),
Signature: ds,
}, nil
}
// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error (which may be of type
// RspError if a raw http.Response is available).
func (c *LogClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) {
var resp ct.GetSTHResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHPath, nil, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
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 {
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
// successful.
func (c *LogClient) VerifySTHSignature(sth ct.SignedTreeHead) error {
if c.Verifier == nil {
// Can't verify signatures without a verifier
return nil
}
return c.Verifier.VerifySTHSignature(sth)
}
// VerifySCTSignature checks the signature in sct for the given LogEntryType, with associated certificate chain.
func (c *LogClient) VerifySCTSignature(sct ct.SignedCertificateTimestamp, ctype ct.LogEntryType, certData []ct.ASN1Cert) error {
if c.Verifier == nil {
// Can't verify signatures without a verifier
return nil
}
leaf, err := ct.MerkleTreeLeafFromRawChain(certData, ctype, sct.Timestamp)
if err != nil {
return fmt.Errorf("failed to build MerkleTreeLeaf: %v", err)
}
entry := ct.LogEntry{Leaf: *leaf}
return c.Verifier.VerifySCTSignature(sct, entry)
}
// GetSTHConsistency retrieves the consistency proof between two snapshots.
func (c *LogClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) {
base10 := 10
params := map[string]string{
"first": strconv.FormatUint(first, base10),
"second": strconv.FormatUint(second, base10),
}
var resp ct.GetSTHConsistencyResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetSTHConsistencyPath, params, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
return resp.Consistency, nil
}
// GetProofByHash returns an audit path for the hash of an SCT.
func (c *LogClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) {
b64Hash := base64.StdEncoding.EncodeToString(hash)
base10 := 10
params := map[string]string{
"tree_size": strconv.FormatUint(treeSize, base10),
"hash": b64Hash,
}
var resp ct.GetProofByHashResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetProofByHashPath, params, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
return &resp, nil
}
// GetAcceptedRoots retrieves the set of acceptable root certificates for a log.
func (c *LogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
var resp ct.GetRootsResponse
httpRsp, body, err := c.GetAndParse(ctx, ct.GetRootsPath, nil, &resp)
if err != nil {
if httpRsp != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
return nil, err
}
var roots []ct.ASN1Cert
for _, cert64 := range resp.Certificates {
cert, err := base64.StdEncoding.DecodeString(cert64)
if err != nil {
return nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
}
roots = append(roots, ct.ASN1Cert{Data: cert})
}
return roots, nil
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,221 @@
// Copyright 2017 Google Inc. 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 client
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/gogo/protobuf/proto"
"github.com/golang/protobuf/ptypes"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/client/configpb"
"github.com/google/certificate-transparency-go/jsonclient"
"github.com/google/certificate-transparency-go/x509"
)
type interval struct {
lower *time.Time // nil => no lower bound
upper *time.Time // nil => no upper bound
}
// TemporalLogConfigFromFile creates a TemporalLogConfig object from the given
// filename, which should contain text-protobuf encoded configuration data.
func TemporalLogConfigFromFile(filename string) (*configpb.TemporalLogConfig, error) {
if len(filename) == 0 {
return nil, errors.New("log config filename empty")
}
cfgText, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read log config: %v", err)
}
var cfg configpb.TemporalLogConfig
if err := proto.UnmarshalText(string(cfgText), &cfg); err != nil {
return nil, fmt.Errorf("failed to parse log config: %v", err)
}
if len(cfg.Shard) == 0 {
return nil, errors.New("empty log config found")
}
return &cfg, nil
}
// AddLogClient is an interface that allows adding certificates and pre-certificates to a log.
// Both LogClient and TemporalLogClient implement this interface, which allows users to
// commonize code for adding certs to normal/temporal logs.
type AddLogClient interface {
AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error)
AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error)
GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error)
}
// TemporalLogClient allows [pre-]certificates to be uploaded to a temporal log.
type TemporalLogClient struct {
Clients []*LogClient
intervals []interval
}
// NewTemporalLogClient builds a new client for interacting with a temporal log.
// The provided config should be contiguous and chronological.
func NewTemporalLogClient(cfg configpb.TemporalLogConfig, hc *http.Client) (*TemporalLogClient, error) {
if len(cfg.Shard) == 0 {
return nil, errors.New("empty config")
}
overall, err := shardInterval(cfg.Shard[0])
if err != nil {
return nil, fmt.Errorf("cfg.Shard[0] invalid: %v", err)
}
intervals := make([]interval, 0, len(cfg.Shard))
intervals = append(intervals, overall)
for i := 1; i < len(cfg.Shard); i++ {
interval, err := shardInterval(cfg.Shard[i])
if err != nil {
return nil, fmt.Errorf("cfg.Shard[%d] invalid: %v", i, err)
}
if overall.upper == nil {
return nil, fmt.Errorf("cfg.Shard[%d] extends an interval with no upper bound", i)
}
if interval.lower == nil {
return nil, fmt.Errorf("cfg.Shard[%d] has no lower bound but extends an interval", i)
}
if !interval.lower.Equal(*overall.upper) {
return nil, fmt.Errorf("cfg.Shard[%d] starts at %v but previous interval ended at %v", i, interval.lower, overall.upper)
}
overall.upper = interval.upper
intervals = append(intervals, interval)
}
clients := make([]*LogClient, 0, len(cfg.Shard))
for i, shard := range cfg.Shard {
opts := jsonclient.Options{}
opts.PublicKeyDER = shard.GetPublicKeyDer()
c, err := New(shard.Uri, hc, opts)
if err != nil {
return nil, fmt.Errorf("failed to create client for cfg.Shard[%d]: %v", i, err)
}
clients = append(clients, c)
}
tlc := TemporalLogClient{
Clients: clients,
intervals: intervals,
}
return &tlc, nil
}
// GetAcceptedRoots retrieves the set of acceptable root certificates for all
// of the shards of a temporal log (i.e. the union).
func (tlc *TemporalLogClient) GetAcceptedRoots(ctx context.Context) ([]ct.ASN1Cert, error) {
type result struct {
roots []ct.ASN1Cert
err error
}
results := make(chan result, len(tlc.Clients))
for _, c := range tlc.Clients {
go func(c *LogClient) {
var r result
r.roots, r.err = c.GetAcceptedRoots(ctx)
results <- r
}(c)
}
var allRoots []ct.ASN1Cert
seen := make(map[[sha256.Size]byte]bool)
for range tlc.Clients {
r := <-results
if r.err != nil {
return nil, r.err
}
for _, root := range r.roots {
h := sha256.Sum256(root.Data)
if seen[h] {
continue
}
seen[h] = true
allRoots = append(allRoots, root)
}
}
return allRoots, nil
}
// AddChain adds the (DER represented) X509 chain to the appropriate log.
func (tlc *TemporalLogClient) AddChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
return tlc.addChain(ctx, ct.X509LogEntryType, ct.AddChainPath, chain)
}
// AddPreChain adds the (DER represented) Precertificate chain to the appropriate log.
func (tlc *TemporalLogClient) AddPreChain(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
return tlc.addChain(ctx, ct.PrecertLogEntryType, ct.AddPreChainPath, chain)
}
func (tlc *TemporalLogClient) addChain(ctx context.Context, ctype ct.LogEntryType, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
// Parse the first entry in the chain
if len(chain) == 0 {
return nil, errors.New("missing chain")
}
cert, err := x509.ParseCertificate(chain[0].Data)
if err != nil {
return nil, fmt.Errorf("failed to parse initial chain entry: %v", err)
}
cidx, err := tlc.IndexByDate(cert.NotAfter)
if err != nil {
return nil, fmt.Errorf("failed to find log to process cert: %v", err)
}
return tlc.Clients[cidx].addChainWithRetry(ctx, ctype, path, chain)
}
// IndexByDate returns the index of the Clients entry that is appropriate for the given
// date.
func (tlc *TemporalLogClient) IndexByDate(when time.Time) (int, error) {
for i, interval := range tlc.intervals {
if (interval.lower != nil) && when.Before(*interval.lower) {
continue
}
if (interval.upper != nil) && !when.Before(*interval.upper) {
continue
}
return i, nil
}
return -1, fmt.Errorf("no log found encompassing date %v", when)
}
func shardInterval(cfg *configpb.LogShardConfig) (interval, error) {
var interval interval
if cfg.NotAfterStart != nil {
t, err := ptypes.Timestamp(cfg.NotAfterStart)
if err != nil {
return interval, fmt.Errorf("failed to parse NotAfterStart: %v", err)
}
interval.lower = &t
}
if cfg.NotAfterLimit != nil {
t, err := ptypes.Timestamp(cfg.NotAfterLimit)
if err != nil {
return interval, fmt.Errorf("failed to parse NotAfterLimit: %v", err)
}
interval.upper = &t
}
if interval.lower != nil && interval.upper != nil && !(*interval.lower).Before(*interval.upper) {
return interval, errors.New("inverted interval")
}
return interval, nil
}

View File

@ -0,0 +1,481 @@
// Copyright 2017 Google Inc. 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 client
import (
"context"
"encoding/pem"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"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/configpb"
"github.com/google/certificate-transparency-go/testdata"
"github.com/google/certificate-transparency-go/x509util"
)
func TestNewTemporalLogClient(t *testing.T) {
ts0, _ := ptypes.TimestampProto(time.Date(2010, 9, 19, 11, 00, 00, 00, time.UTC))
ts1, _ := ptypes.TimestampProto(time.Date(2011, 9, 19, 11, 00, 00, 00, time.UTC))
ts2, _ := ptypes.TimestampProto(time.Date(2012, 9, 19, 11, 00, 00, 00, time.UTC))
ts2_5, _ := ptypes.TimestampProto(time.Date(2013, 3, 19, 11, 00, 00, 00, time.UTC))
ts3, _ := ptypes.TimestampProto(time.Date(2013, 9, 19, 11, 00, 00, 00, time.UTC))
ts4, _ := ptypes.TimestampProto(time.Date(2014, 9, 19, 11, 00, 00, 00, time.UTC))
tests := []struct {
cfg configpb.TemporalLogConfig
wantErr string
}{
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: nil, NotAfterLimit: nil},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "threeA", NotAfterStart: ts2_5, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
},
},
wantErr: "previous interval ended at",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil},
},
},
wantErr: "previous interval ended at",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: nil, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts1},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
},
},
wantErr: "inverted",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: nil},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil},
},
},
wantErr: "no upper bound",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: nil, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: nil},
},
},
wantErr: "has no lower bound",
},
{
wantErr: "empty",
},
{
cfg: configpb.TemporalLogConfig{Shard: []*configpb.LogShardConfig{}},
wantErr: "empty",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts1},
},
},
wantErr: "inverted",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts2, NotAfterLimit: ts1},
},
},
wantErr: "inverted",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: &tspb.Timestamp{Seconds: -1, Nanos: -1}, NotAfterLimit: ts2},
},
},
wantErr: "failed to parse",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "one", NotAfterStart: ts1, NotAfterLimit: &tspb.Timestamp{Seconds: -1, Nanos: -1}},
},
},
wantErr: "failed to parse",
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{
Uri: "one",
NotAfterStart: nil,
NotAfterLimit: nil,
PublicKeyDer: []byte{0x01, 0x02},
},
},
},
wantErr: "invalid public key",
},
}
for _, test := range tests {
_, err := NewTemporalLogClient(test.cfg, nil)
if err != nil {
if test.wantErr == "" {
t.Errorf("NewTemporalLogClient(%+v)=nil,%v; want _,nil", test.cfg, err)
} else if !strings.Contains(err.Error(), test.wantErr) {
t.Errorf("NewTemporalLogClient(%+v)=nil,%v; want _,%q", test.cfg, err, test.wantErr)
}
continue
}
if test.wantErr != "" {
t.Errorf("NewTemporalLogClient(%+v)=_, nil; want _,%q", test.cfg, test.wantErr)
}
}
}
func TestIndexByDate(t *testing.T) {
time0 := time.Date(2010, 9, 19, 11, 00, 00, 00, time.UTC)
time1 := time.Date(2011, 9, 19, 11, 00, 00, 00, time.UTC)
time1_9 := time.Date(2012, 9, 19, 10, 59, 59, 00, time.UTC)
time2 := time.Date(2012, 9, 19, 11, 00, 00, 00, time.UTC)
time2_5 := time.Date(2013, 3, 19, 11, 00, 00, 00, time.UTC)
time3 := time.Date(2013, 9, 19, 11, 00, 00, 00, time.UTC)
time4 := time.Date(2014, 9, 19, 11, 00, 00, 00, time.UTC)
ts0, _ := ptypes.TimestampProto(time0)
ts1, _ := ptypes.TimestampProto(time1)
ts2, _ := ptypes.TimestampProto(time2)
ts3, _ := ptypes.TimestampProto(time3)
ts4, _ := ptypes.TimestampProto(time4)
allCfg := configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "zero", NotAfterStart: nil, NotAfterLimit: ts0},
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
{Uri: "five", NotAfterStart: ts4, NotAfterLimit: nil},
},
}
uptoCfg := configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "zero", NotAfterStart: nil, NotAfterLimit: ts0},
{Uri: "one", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "two", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "three", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "four", NotAfterStart: ts3, NotAfterLimit: ts4},
},
}
fromCfg :=
configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "zero", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "two", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "three", NotAfterStart: ts3, NotAfterLimit: ts4},
{Uri: "four", NotAfterStart: ts4, NotAfterLimit: nil},
},
}
boundedCfg :=
configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: "zero", NotAfterStart: ts0, NotAfterLimit: ts1},
{Uri: "one", NotAfterStart: ts1, NotAfterLimit: ts2},
{Uri: "two", NotAfterStart: ts2, NotAfterLimit: ts3},
{Uri: "three", NotAfterStart: ts3, NotAfterLimit: ts4},
},
}
tests := []struct {
cfg configpb.TemporalLogConfig
when time.Time
want int
wantErr bool
}{
{cfg: allCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), want: 0},
{cfg: allCfg, when: time0, want: 1},
{cfg: allCfg, when: time1, want: 2},
{cfg: allCfg, when: time1_9, want: 2},
{cfg: allCfg, when: time2, want: 3},
{cfg: allCfg, when: time2_5, want: 3},
{cfg: allCfg, when: time3, want: 4},
{cfg: allCfg, when: time4, want: 5},
{cfg: allCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), want: 5},
{cfg: uptoCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), want: 0},
{cfg: uptoCfg, when: time0, want: 1},
{cfg: uptoCfg, when: time1, want: 2},
{cfg: uptoCfg, when: time2, want: 3},
{cfg: uptoCfg, when: time2_5, want: 3},
{cfg: uptoCfg, when: time3, want: 4},
{cfg: uptoCfg, when: time4, wantErr: true},
{cfg: uptoCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true},
{cfg: fromCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true},
{cfg: fromCfg, when: time0, want: 0},
{cfg: fromCfg, when: time1, want: 1},
{cfg: fromCfg, when: time2, want: 2},
{cfg: fromCfg, when: time2_5, want: 2},
{cfg: fromCfg, when: time3, want: 3},
{cfg: fromCfg, when: time4, want: 4},
{cfg: fromCfg, when: time.Date(2015, 9, 19, 11, 00, 00, 00, time.UTC), want: 4},
{cfg: boundedCfg, when: time.Date(2000, 9, 19, 11, 00, 00, 00, time.UTC), wantErr: true},
{cfg: boundedCfg, when: time0, want: 0},
{cfg: boundedCfg, when: time1, want: 1},
{cfg: boundedCfg, when: time2, want: 2},
{cfg: boundedCfg, when: time2_5, want: 2},
{cfg: boundedCfg, when: time3, want: 3},
{cfg: boundedCfg, when: time4, wantErr: true},
{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)
if err != nil {
t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err)
continue
}
got, err := tlc.IndexByDate(test.when)
if err != nil {
if !test.wantErr {
t.Errorf("NewTemporalLogClient(%+v).idxByDate()=%d,%v; want %d,nil", test.cfg, got, err, test.want)
}
continue
}
if test.wantErr {
t.Errorf("NewTemporalLogClient(%+v).idxByDate(%v)=%d, nil; want _, 'no log found'", test.cfg, test.when, got)
}
if got != test.want {
t.Errorf("NewTemporalLogClient(%+v).idxByDate(%v)=%d, nil; want %d, nil", test.cfg, test.when, got, test.want)
}
}
}
func TestTemporalAddChain(t *testing.T) {
hs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/ct/v1/add-chain":
data, _ := sctToJSON(testdata.TestCertProof)
w.Write(data)
case "/ct/v1/add-pre-chain":
data, _ := sctToJSON(testdata.TestPreCertProof)
w.Write(data)
default:
t.Fatalf("Incorrect URL path: %s", r.URL.Path)
}
}))
defer hs.Close()
cert, err := x509util.CertificateFromPEM(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)
if err != nil {
t.Fatalf("Failed to parse pre-certificate from PEM: %v", err)
}
issuer, err := x509util.CertificateFromPEM(testdata.CACertPEM)
if err != nil {
t.Fatalf("Failed to parse issuer certificate from PEM: %v", err)
}
precertChain := []ct.ASN1Cert{{Data: precert.Raw}, {Data: issuer.Raw}}
// Both have Not After = Jun 1 00:00:00 2022 GMT
ts1, _ := ptypes.TimestampProto(time.Date(2022, 5, 19, 11, 00, 00, 00, time.UTC))
ts2, _ := ptypes.TimestampProto(time.Date(2022, 6, 19, 11, 00, 00, 00, time.UTC))
p, _ := pem.Decode([]byte(testdata.LogPublicKeyPEM))
if p == nil {
t.Fatalf("Failed to parse public key from PEM: %v", err)
}
tests := []struct {
cfg configpb.TemporalLogConfig
wantErr bool
}{
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: nil, PublicKeyDer: p.Bytes},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: ts2, PublicKeyDer: p.Bytes},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: ts1, NotAfterLimit: nil, PublicKeyDer: p.Bytes},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: ts1, NotAfterLimit: ts2, PublicKeyDer: p.Bytes},
},
},
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: nil, NotAfterLimit: ts1, PublicKeyDer: p.Bytes},
},
},
wantErr: true,
},
{
cfg: configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{Uri: hs.URL, NotAfterStart: ts2, NotAfterLimit: nil, PublicKeyDer: p.Bytes},
},
},
wantErr: true,
},
}
ctx := context.Background()
for _, test := range tests {
tlc, err := NewTemporalLogClient(test.cfg, nil)
if err != nil {
t.Errorf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", test.cfg, err)
continue
}
_, err = tlc.AddChain(ctx, certChain)
if err != nil {
if !test.wantErr {
t.Errorf("AddChain()=nil,%v; want sct,nil", err)
}
} else if test.wantErr {
t.Errorf("AddChain()=sct,nil; want nil,_")
}
_, err = tlc.AddPreChain(ctx, precertChain)
if err != nil {
if !test.wantErr {
t.Errorf("AddPreChain()=nil,%v; want sct,nil", err)
}
} else if test.wantErr {
t.Errorf("AddPreChain()=sct,nil; want nil,_")
}
}
}
func TestTemporalAddChainErrors(t *testing.T) {
hs := serveSCTAt(t, "/ct/v1/add-chain", testdata.TestCertProof)
defer hs.Close()
cfg := configpb.TemporalLogConfig{
Shard: []*configpb.LogShardConfig{
{
Uri: hs.URL,
NotAfterStart: nil,
NotAfterLimit: nil,
},
},
}
ctx := context.Background()
tlc, err := NewTemporalLogClient(cfg, nil)
if err != nil {
t.Fatalf("NewTemporalLogClient(%+v)=nil, %v; want _,nil", cfg, err)
}
_, err = tlc.AddChain(ctx, nil)
if err == nil {
t.Errorf("AddChain(nil)=sct,nil; want nil, 'missing chain'")
}
_, err = tlc.AddChain(ctx, []ct.ASN1Cert{{Data: []byte{0x01, 0x02}}})
if err == nil {
t.Errorf("AddChain(nil)=sct,nil; want nil, 'failed to parse'")
}
}

View File

@ -0,0 +1,25 @@
{
"Linters": {
"license": "./scripts/check_license.sh:PATH:LINE:MESSAGE",
"forked": "./scripts/check_forked.sh:PATH:LINE:MESSAGE"
},
"Enable": [
"forked",
"gocyclo",
"gofmt",
"goimports",
"golint",
"license",
"misspell",
"vet"
],
"Exclude": [
"x509/",
"asn1/",
".+\\.pb\\.go",
".+\\.pb\\.gw\\.go",
"mock_.+\\.go"
],
"Cyclo": 40,
"Vendor": true
}

View File

@ -0,0 +1,72 @@
// Copyright 2017 Google Inc. 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 jsonclient
import (
"sync"
"time"
)
type backoff struct {
mu sync.RWMutex
multiplier uint
notBefore time.Time
}
const (
// maximum backoff is 2^(maxMultiplier-1) = 128 seconds
maxMultiplier = 8
)
func (b *backoff) set(override *time.Duration) time.Duration {
b.mu.Lock()
defer b.mu.Unlock()
if b.notBefore.After(time.Now()) {
if override != nil {
// If existing backoff is set but override would be longer than
// it then set it to that.
notBefore := time.Now().Add(*override)
if notBefore.After(b.notBefore) {
b.notBefore = notBefore
}
}
return time.Until(b.notBefore)
}
var wait time.Duration
if override != nil {
wait = *override
} else {
if b.multiplier < maxMultiplier {
b.multiplier++
}
wait = time.Second * time.Duration(1<<(b.multiplier-1))
}
b.notBefore = time.Now().Add(wait)
return wait
}
func (b *backoff) decreaseMultiplier() {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiplier > 0 {
b.multiplier--
}
}
func (b *backoff) until() time.Time {
b.mu.RLock()
defer b.mu.RUnlock()
return b.notBefore
}

View File

@ -0,0 +1,117 @@
// Copyright 2017 Google Inc. 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 jsonclient
import (
"math"
"testing"
"time"
)
const testLeeway = 25 * time.Microsecond
func fuzzyTimeEquals(a, b time.Time, leeway time.Duration) bool {
diff := math.Abs(float64(a.Sub(b).Nanoseconds()))
if diff < float64(leeway.Nanoseconds()) {
return true
}
return false
}
func fuzzyDurationEquals(a, b time.Duration, leeway time.Duration) bool {
diff := math.Abs(float64(a.Nanoseconds() - b.Nanoseconds()))
if diff < float64(leeway.Nanoseconds()) {
return true
}
return false
}
func TestBackoff(t *testing.T) {
b := backoff{}
// Test that the interval increases as expected
for i := uint(0); i < maxMultiplier; i++ {
n := time.Now()
interval := b.set(nil)
if interval != time.Second*(1<<i) {
t.Fatalf("backoff.set(nil)=%v; want %v", interval, time.Second*(1<<i))
}
expected := n.Add(interval)
until := b.until()
if !fuzzyTimeEquals(expected, until, time.Millisecond) {
t.Fatalf("backoff.until()=%v; want %v (+ 0-250ms)", expected, until)
}
// reset notBefore
b.notBefore = time.Time{}
}
// Test that multiplier doesn't go above maxMultiplier
b.multiplier = maxMultiplier
b.notBefore = time.Time{}
interval := b.set(nil)
if b.multiplier > maxMultiplier {
t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, maxMultiplier)
}
if interval > time.Second*(1<<(maxMultiplier-1)) {
t.Fatalf("backoff.set(nil)=%v; want %v", interval, 1<<(maxMultiplier-1)*time.Second)
}
// Test decreaseMultiplier properly decreases the multiplier
b.multiplier = 1
b.notBefore = time.Time{}
b.decreaseMultiplier()
if b.multiplier != 0 {
t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, 0)
}
// Test decreaseMultiplier doesn't reduce multiplier below 0
b.decreaseMultiplier()
if b.multiplier != 0 {
t.Fatalf("backoff.multiplier=%v; want %v", b.multiplier, 0)
}
}
func TestBackoffOverride(t *testing.T) {
b := backoff{}
for _, tc := range []struct {
notBefore time.Time
override time.Duration
expectedInterval time.Duration
}{
{
notBefore: time.Now().Add(time.Hour),
override: time.Second * 1800,
expectedInterval: time.Hour,
},
{
notBefore: time.Now().Add(time.Hour),
override: time.Second * 7200,
expectedInterval: 2 * time.Hour,
},
{
notBefore: time.Time{},
override: time.Second * 7200,
expectedInterval: 2 * time.Hour,
},
} {
b.multiplier = 0
b.notBefore = tc.notBefore
interval := b.set(&tc.override)
if !fuzzyDurationEquals(tc.expectedInterval, interval, testLeeway) {
t.Fatalf("backoff.set(%v)=%v; want %v", tc.override, interval, tc.expectedInterval)
}
}
}

View File

@ -0,0 +1,289 @@
// Copyright 2016 Google Inc. 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 jsonclient
import (
"bytes"
"context"
"crypto"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"time"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/x509"
"golang.org/x/net/context/ctxhttp"
)
const maxJitter = 250 * time.Millisecond
type backoffer interface {
// set adjusts/increases the current backoff interval (typically on retryable failure);
// if the optional parameter is provided, this will be used as the interval if it is greater
// than the currently set interval. Returns the current wait period so that it can be
// logged along with any error message.
set(*time.Duration) time.Duration
// decreaseMultiplier reduces the current backoff multiplier, typically on success.
decreaseMultiplier()
// until returns the time until which the client should wait before making a request,
// it may be in the past in which case it should be ignored.
until() time.Time
}
// 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
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
backoff backoffer // object used to store and calculate backoff information
}
// Logger is a simple logging interface used to log internal errors and warnings
type Logger interface {
// Printf formats and logs a message
Printf(string, ...interface{})
}
// Options are the options for creating a new JSONClient.
type Options struct {
// Interface to use for logging warnings and errors, if nil the
// standard library log package will be used.
Logger Logger
// PEM format public key to use for signature verification.
PublicKey string
// DER format public key to use for signature verification.
PublicKeyDER []byte
}
// ParsePublicKey parses and returns the public key contained in opts.
// If both opts.PublicKey and opts.PublicKeyDER are set, PublicKeyDER is used.
// If neither is set, nil will be returned.
func (opts *Options) ParsePublicKey() (crypto.PublicKey, error) {
if len(opts.PublicKeyDER) > 0 {
return x509.ParsePKIXPublicKey(opts.PublicKeyDER)
}
if opts.PublicKey != "" {
pubkey, _ /* keyhash */, rest, err := ct.PublicKeyFromPEM([]byte(opts.PublicKey))
if err != nil {
return nil, err
}
if len(rest) > 0 {
return nil, errors.New("extra data found after PEM key decoded")
}
return pubkey, nil
}
return nil, nil
}
type basicLogger struct{}
func (bl *basicLogger) Printf(msg string, args ...interface{}) {
log.Printf(msg, args...)
}
// New constructs a new JSONClient instance, for the given base URI, using the
// given http.Client object (if provided) and the Options object.
// If opts does not specify a public key, signatures will not be verified.
func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) {
pubkey, err := opts.ParsePublicKey()
if err != nil {
return nil, fmt.Errorf("invalid public key: %v", err)
}
var verifier *ct.SignatureVerifier
if pubkey != nil {
var err error
verifier, err = ct.NewSignatureVerifier(pubkey)
if err != nil {
return nil, err
}
}
if hc == nil {
hc = new(http.Client)
}
logger := opts.Logger
if logger == nil {
logger = &basicLogger{}
}
return &JSONClient{
uri: strings.TrimRight(uri, "/"),
httpClient: hc,
Verifier: verifier,
logger: logger,
backoff: &backoff{},
}, nil
}
// 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
// returned http.Response can be non-nil even when an error is returned,
// in particular when the HTTP status is not OK or when the JSON parsing fails.
func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[string]string, rsp interface{}) (*http.Response, []byte, error) {
if ctx == nil {
return nil, nil, errors.New("context.Context required")
}
// Build a GET request with URL-encoded parameters.
vals := url.Values{}
for k, v := range params {
vals.Add(k, v)
}
fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode())
httpReq, err := http.NewRequest(http.MethodGet, fullURI, nil)
if err != nil {
return nil, nil, err
}
httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
if err != nil {
return nil, nil, err
}
// Read everything now so http.Client can reuse the connection.
body, err := ioutil.ReadAll(httpRsp.Body)
httpRsp.Body.Close()
if err != nil {
return httpRsp, body, fmt.Errorf("failed to read response body: %v", err)
}
if httpRsp.StatusCode != http.StatusOK {
return httpRsp, body, fmt.Errorf("got HTTP Status %q", httpRsp.Status)
}
if err := json.NewDecoder(bytes.NewReader(body)).Decode(rsp); err != nil {
return httpRsp, body, err
}
return httpRsp, body, nil
}
// PostAndParse makes a HTTP POST call to the given path, including the request
// parameters, and attempts 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 returned http.Response can be non-nil even when an
// error is returned, in particular when the HTTP status is not OK or when the
// JSON parsing fails.
func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
if ctx == nil {
return nil, nil, errors.New("context.Context required")
}
// Build a POST request with JSON body.
postBody, err := json.Marshal(req)
if err != nil {
return nil, nil, err
}
fullURI := fmt.Sprintf("%s%s", c.uri, path)
httpReq, err := http.NewRequest(http.MethodPost, fullURI, bytes.NewReader(postBody))
if err != nil {
return nil, nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
// Read all of the body, if there is one, so that the http.Client can do Keep-Alive.
var body []byte
if httpRsp != nil {
body, err = ioutil.ReadAll(httpRsp.Body)
httpRsp.Body.Close()
}
if err != nil {
return httpRsp, body, err
}
if httpRsp.StatusCode == http.StatusOK {
if err = json.Unmarshal(body, &rsp); err != nil {
return httpRsp, body, err
}
}
return httpRsp, body, nil
}
// waitForBackoff blocks until the defined backoff interval or context has expired, if the returned
// not before time is in the past it returns immediately.
func (c *JSONClient) waitForBackoff(ctx context.Context) error {
dur := time.Until(c.backoff.until().Add(time.Millisecond * time.Duration(rand.Intn(int(maxJitter.Seconds()*1000)))))
if dur < 0 {
dur = 0
}
backoffTimer := time.NewTimer(dur)
select {
case <-ctx.Done():
return ctx.Err()
case <-backoffTimer.C:
}
return nil
}
// PostAndParseWithRetry makes a HTTP POST call, but retries (with backoff) on
// retriable errors; the caller should set a deadline on the provided context
// to prevent infinite retries. Return values are as for PostAndParse.
func (c *JSONClient) PostAndParseWithRetry(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
if ctx == nil {
return nil, nil, errors.New("context.Context required")
}
for {
httpRsp, body, err := c.PostAndParse(ctx, path, req, rsp)
if err != nil {
// Don't retry context errors.
if err == context.Canceled || err == context.DeadlineExceeded {
return nil, nil, err
}
wait := c.backoff.set(nil)
c.logger.Printf("Request failed, backing-off for %s: %s", wait, err)
} else {
switch {
case httpRsp.StatusCode == http.StatusOK:
return httpRsp, body, nil
case httpRsp.StatusCode == http.StatusRequestTimeout:
// Request timeout, retry immediately
c.logger.Printf("Request timed out, retrying immediately")
case httpRsp.StatusCode == http.StatusServiceUnavailable:
var backoff *time.Duration
// Retry-After may be either a number of seconds as a int or a RFC 1123
// date string (RFC 7231 Section 7.1.3)
if retryAfter := httpRsp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, err := strconv.Atoi(retryAfter); err == nil {
b := time.Duration(seconds) * time.Second
backoff = &b
} else if date, err := time.Parse(time.RFC1123, retryAfter); err == nil {
b := date.Sub(time.Now())
backoff = &b
}
}
wait := c.backoff.set(backoff)
c.logger.Printf("Request failed, backing-off for %s: got HTTP status %s", wait, httpRsp.Status)
default:
return httpRsp, body, fmt.Errorf("got HTTP Status %q", httpRsp.Status)
}
}
if err := c.waitForBackoff(ctx); err != nil {
return nil, nil, err
}
}
}

View File

@ -0,0 +1,446 @@
// Copyright 2016 Google Inc. 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 jsonclient
import (
"context"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"testing"
"time"
"github.com/google/certificate-transparency-go/testdata"
)
func publicKeyPEMToDER(key string) []byte {
block, _ := pem.Decode([]byte(key))
if block == nil {
panic("failed to decode public key PEM")
}
if block.Type != "PUBLIC KEY" {
panic("PEM does not have type 'PUBLIC KEY'")
}
return block.Bytes
}
func TestNewJSONClient(t *testing.T) {
tests := []struct {
name string
opts Options
errstr string
}{
{
name: "invalid PublicKey",
opts: Options{PublicKey: "bogus"},
errstr: "no PEM block",
},
{
name: "invalid PublicKeyDER",
opts: Options{PublicKeyDER: []byte("bogus")},
errstr: "asn1: structure error",
},
{
name: "RSA PublicKey",
opts: Options{PublicKey: testdata.RsaPublicKeyPEM},
},
{
name: "RSA PublicKeyDER",
opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.RsaPublicKeyPEM)},
},
{
name: "ECDSA PublicKey",
opts: Options{PublicKey: testdata.EcdsaPublicKeyPEM},
},
{
name: "ECDSA PublicKeyDER",
opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.EcdsaPublicKeyPEM)},
},
{
name: "DSA PublicKey",
opts: Options{PublicKey: testdata.DsaPublicKeyPEM},
errstr: "Unsupported public key type",
},
{
name: "DSA PublicKeyDER",
opts: Options{PublicKeyDER: publicKeyPEMToDER(testdata.DsaPublicKeyPEM)},
errstr: "Unsupported public key type",
},
{
name: "PublicKey contains trailing garbage",
opts: Options{PublicKey: testdata.RsaPublicKeyPEM + "bogus"},
errstr: "extra data found",
},
{
name: "PublicKeyDER contains trailing garbage",
opts: Options{PublicKeyDER: append(publicKeyPEMToDER(testdata.RsaPublicKeyPEM), []byte("deadbeef")...)},
errstr: "trailing data",
},
}
for _, test := range tests {
client, err := New("http://127.0.0.1", nil, test.opts)
if test.errstr != "" {
if err == nil {
t.Errorf("%v: New()=%p,nil; want error %q", test.name, client, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("%v: New()=nil,%q; want error %q", test.name, err, test.errstr)
}
continue
}
if err != nil {
t.Errorf("%v: New()=nil,%q; want no error", test.name, err)
} else if client == nil {
t.Errorf("%v: New()=nil,nil; want client", test.name)
}
}
}
type TestStruct struct {
TreeSize int `json:"tree_size"`
Timestamp int `json:"timestamp"`
Data string `json:"data"`
}
type TestParams struct {
RespCode int `json:"rc"`
}
func MockServer(t *testing.T, failCount int, retryAfter int) *httptest.Server {
t.Helper()
mu := sync.Mutex{}
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
switch r.URL.Path {
case "/struct/path":
fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`)
case "/struct/params":
var s TestStruct
if r.Method == http.MethodGet {
s.TreeSize, _ = strconv.Atoi(r.FormValue("tree_size"))
s.Timestamp, _ = strconv.Atoi(r.FormValue("timestamp"))
s.Data = r.FormValue("data")
} else {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&s)
if err != nil {
panic("Failed to decode: " + err.Error())
}
defer r.Body.Close()
}
fmt.Fprintf(w, `{"tree_size": %d, "timestamp": %d, "data": "%s"}`, s.TreeSize, s.Timestamp, s.Data)
case "/error":
var params TestParams
if r.Method == http.MethodGet {
params.RespCode, _ = strconv.Atoi(r.FormValue("rc"))
} else {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&params)
if err != nil {
panic("Failed to decode: " + err.Error())
}
defer r.Body.Close()
}
http.Error(w, "error page", params.RespCode)
case "/malformed":
fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99`) // no closing }
case "/retry":
if failCount > 0 {
failCount--
if retryAfter != 0 {
if retryAfter > 0 {
w.Header().Add("Retry-After", strconv.Itoa(retryAfter))
}
w.WriteHeader(http.StatusServiceUnavailable)
} else {
w.WriteHeader(http.StatusRequestTimeout)
}
} else {
fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`)
}
case "/retry-rfc1123":
if failCount > 0 {
failCount--
w.Header().Add("Retry-After", time.Now().Add(time.Duration(retryAfter)*time.Second).Format(time.RFC1123))
w.WriteHeader(http.StatusServiceUnavailable)
} else {
fmt.Fprintf(w, `{"tree_size": 11, "timestamp": 99}`)
}
default:
t.Fatalf("Unhandled URL path: %s", r.URL.Path)
}
}))
}
func TestGetAndParse(t *testing.T) {
rc := regexp.MustCompile
tests := []struct {
uri string
params map[string]string
status int
result TestStruct
errstr *regexp.Regexp
wantBody bool
}{
{uri: "/short%", errstr: rc("invalid URL escape")},
{uri: "/malformed", status: http.StatusOK, errstr: rc("unexpected EOF"), wantBody: true},
{uri: "/error", params: map[string]string{"rc": "404"}, status: http.StatusNotFound, wantBody: true},
{uri: "/error", params: map[string]string{"rc": "403"}, status: http.StatusForbidden, wantBody: true},
{uri: "/struct/path", status: http.StatusOK, result: TestStruct{11, 99, ""}, wantBody: true},
{
uri: "/struct/params",
status: http.StatusOK,
params: map[string]string{"tree_size": "42", "timestamp": "88", "data": "abcd"},
result: TestStruct{42, 88, "abcd"},
wantBody: true,
},
}
ts := MockServer(t, -1, 0)
defer ts.Close()
logClient, err := New(ts.URL, &http.Client{}, Options{})
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
for _, test := range tests {
var result TestStruct
httpRsp, body, err := logClient.GetAndParse(ctx, test.uri, test.params, &result)
if gotBody := (body != nil); gotBody != test.wantBody {
t.Errorf("GetAndParse(%q) got body? %v, want? %v", test.uri, gotBody, test.wantBody)
}
if test.errstr != nil {
if err == nil {
t.Errorf("GetAndParse(%q)=%+v,_,nil; want error matching %q", test.uri, result, test.errstr)
} else if !test.errstr.MatchString(err.Error()) {
t.Errorf("GetAndParse(%q)=nil,_,%q; want error matching %q", test.uri, err.Error(), test.errstr)
}
continue
}
if httpRsp.StatusCode != test.status {
t.Errorf("GetAndParse('%s') got status %d; want %d", test.uri, httpRsp.StatusCode, test.status)
}
if test.status == http.StatusOK {
if err != nil {
t.Errorf("GetAndParse(%q)=nil,_,%q; want %+v", test.uri, err.Error(), result)
}
if !reflect.DeepEqual(result, test.result) {
t.Errorf("GetAndParse(%q)=%+v,_,nil; want %+v", test.uri, result, test.result)
}
}
}
}
func TestPostAndParse(t *testing.T) {
rc := regexp.MustCompile
tests := []struct {
uri string
request interface{}
status int
result TestStruct
errstr *regexp.Regexp
wantBody bool
}{
{uri: "/short%", errstr: rc("invalid URL escape")},
{uri: "/struct/params", request: json.Number(`invalid`), errstr: rc("invalid number literal")},
{uri: "/malformed", status: http.StatusOK, errstr: rc("unexpected end of JSON"), wantBody: true},
{uri: "/error", request: TestParams{RespCode: 404}, status: http.StatusNotFound, wantBody: true},
{uri: "/error", request: TestParams{RespCode: 403}, status: http.StatusForbidden, wantBody: true},
{uri: "/struct/path", status: http.StatusOK, result: TestStruct{11, 99, ""}, wantBody: true},
{
uri: "/struct/params",
status: http.StatusOK,
request: TestStruct{42, 88, "abcd"},
result: TestStruct{42, 88, "abcd"},
wantBody: true,
},
}
ts := MockServer(t, -1, 0)
defer ts.Close()
logClient, err := New(ts.URL, &http.Client{}, Options{})
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
for _, test := range tests {
var result TestStruct
httpRsp, body, err := logClient.PostAndParse(ctx, test.uri, test.request, &result)
if gotBody := (body != nil); gotBody != test.wantBody {
t.Errorf("GetAndParse(%q) returned body %v, wanted %v", test.uri, gotBody, test.wantBody)
}
if test.errstr != nil {
if err == nil {
t.Errorf("PostAndParse(%q)=%+v,nil; want error matching %q", test.uri, result, test.errstr)
} else if !test.errstr.MatchString(err.Error()) {
t.Errorf("PostAndParse(%q)=nil,%q; want error matching %q", test.uri, err.Error(), test.errstr)
}
continue
}
if httpRsp.StatusCode != test.status {
t.Errorf("PostAndParse(%q) got status %d; want %d", test.uri, httpRsp.StatusCode, test.status)
}
if test.status == http.StatusOK {
if err != nil {
t.Errorf("PostAndParse(%q)=nil,%q; want %+v", test.uri, err.Error(), test.result)
}
if !reflect.DeepEqual(result, test.result) {
t.Errorf("PostAndParse(%q)=%+v,nil; want %+v", test.uri, result, test.result)
}
}
}
}
// mockBackoff is not safe for concurrent usage
type mockBackoff struct {
override time.Duration
}
func (mb *mockBackoff) set(o *time.Duration) time.Duration {
if o != nil {
mb.override = *o
}
return 0
}
func (mb *mockBackoff) decreaseMultiplier() {}
func (mb *mockBackoff) until() time.Time { return time.Time{} }
func TestPostAndParseWithRetry(t *testing.T) {
tests := []struct {
uri string
request interface{}
deadlineSecs int // -1 indicates no deadline
retryAfter int // -1 indicates generate 503 with no Retry-After
failCount int
errstr string
expectedBackoff time.Duration // 0 indicates no expected backoff override set
}{
{
uri: "/error",
request: TestParams{RespCode: 418},
deadlineSecs: -1,
retryAfter: 0,
failCount: 0,
errstr: "teapot",
expectedBackoff: 0,
},
{
uri: "/short%",
request: nil,
deadlineSecs: 0,
retryAfter: 0,
failCount: 0,
errstr: "deadline exceeded",
expectedBackoff: 0,
},
{
uri: "/retry",
request: nil,
deadlineSecs: -1,
retryAfter: 0,
failCount: 1,
errstr: "",
expectedBackoff: 0,
},
{
uri: "/retry",
request: nil,
deadlineSecs: -1,
retryAfter: 5,
failCount: 1,
errstr: "",
expectedBackoff: 5 * time.Second,
},
{
uri: "/retry-rfc1123",
request: nil,
deadlineSecs: -1,
retryAfter: 5,
failCount: 1,
errstr: "",
expectedBackoff: 5 * time.Second,
},
}
for _, test := range tests {
ts := MockServer(t, test.failCount, test.retryAfter)
defer ts.Close()
logClient, err := New(ts.URL, &http.Client{}, Options{})
if err != nil {
t.Fatal(err)
}
mb := mockBackoff{}
logClient.backoff = &mb
ctx := context.Background()
if test.deadlineSecs >= 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Duration(test.deadlineSecs)*time.Second))
defer cancel()
}
var result TestStruct
httpRsp, _, err := logClient.PostAndParseWithRetry(ctx, test.uri, test.request, &result)
if test.errstr != "" {
if err == nil {
t.Errorf("PostAndParseWithRetry()=%+v,nil; want error %q", result, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("PostAndParseWithRetry()=nil,%q; want error %q", err.Error(), test.errstr)
}
continue
}
if err != nil {
t.Errorf("PostAndParseWithRetry()=nil,%q; want no error", err.Error())
} else if httpRsp.StatusCode != http.StatusOK {
t.Errorf("PostAndParseWithRetry() got status %d; want OK(404)", httpRsp.StatusCode)
}
if test.expectedBackoff > 0 && !fuzzyDurationEquals(test.expectedBackoff, mb.override, time.Second) {
t.Errorf("Unexpected backoff override set: got: %s, wanted: %s", mb.override, test.expectedBackoff)
}
}
}
func TestContextRequired(t *testing.T) {
ts := MockServer(t, -1, 0)
defer ts.Close()
logClient, err := New(ts.URL, &http.Client{}, Options{})
if err != nil {
t.Fatal(err)
}
var result TestStruct
_, _, err = logClient.GetAndParse(nil, "/struct/path", nil, &result)
if err == nil {
t.Errorf("GetAndParse() succeeded with empty Context")
}
_, _, err = logClient.PostAndParse(nil, "/struct/path", nil, &result)
if err == nil {
t.Errorf("PostAndParse() succeeded with empty Context")
}
_, _, err = logClient.PostAndParseWithRetry(nil, "/struct/path", nil, &result)
if err == nil {
t.Errorf("PostAndParseWithRetry() succeeded with empty Context")
}
}

View File

@ -0,0 +1,255 @@
// Copyright 2015 Google Inc. 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 ct
import (
"crypto"
"crypto/sha256"
"encoding/json"
"fmt"
"strings"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
)
// SerializeSCTSignatureInput serializes the passed in sct and log entry into
// the correct format for signing.
func SerializeSCTSignatureInput(sct SignedCertificateTimestamp, entry LogEntry) ([]byte, error) {
switch sct.SCTVersion {
case V1:
input := CertificateTimestamp{
SCTVersion: sct.SCTVersion,
SignatureType: CertificateTimestampSignatureType,
Timestamp: sct.Timestamp,
EntryType: entry.Leaf.TimestampedEntry.EntryType,
Extensions: sct.Extensions,
}
switch entry.Leaf.TimestampedEntry.EntryType {
case X509LogEntryType:
input.X509Entry = entry.Leaf.TimestampedEntry.X509Entry
case PrecertLogEntryType:
input.PrecertEntry = &PreCert{
IssuerKeyHash: entry.Leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash,
TBSCertificate: entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate,
}
case XJSONLogEntryType:
input.JSONEntry = entry.Leaf.TimestampedEntry.JSONEntry
default:
return nil, fmt.Errorf("unsupported entry type %s", entry.Leaf.TimestampedEntry.EntryType)
}
return tls.Marshal(input)
default:
return nil, fmt.Errorf("unknown SCT version %d", sct.SCTVersion)
}
}
// SerializeSTHSignatureInput serializes the passed in STH into the correct
// format for signing.
func SerializeSTHSignatureInput(sth SignedTreeHead) ([]byte, error) {
switch sth.Version {
case V1:
if len(sth.SHA256RootHash) != crypto.SHA256.Size() {
return nil, fmt.Errorf("invalid TreeHash length, got %d expected %d", len(sth.SHA256RootHash), crypto.SHA256.Size())
}
input := TreeHeadSignature{
Version: sth.Version,
SignatureType: TreeHashSignatureType,
Timestamp: sth.Timestamp,
TreeSize: sth.TreeSize,
SHA256RootHash: sth.SHA256RootHash,
}
return tls.Marshal(input)
default:
return nil, fmt.Errorf("unsupported STH version %d", sth.Version)
}
}
// CreateX509MerkleTreeLeaf generates a MerkleTreeLeaf for an X509 cert
func CreateX509MerkleTreeLeaf(cert ASN1Cert, timestamp uint64) *MerkleTreeLeaf {
return &MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
Timestamp: timestamp,
EntryType: X509LogEntryType,
X509Entry: &cert,
},
}
}
// CreateJSONMerkleTreeLeaf creates the merkle tree leaf for json data.
func CreateJSONMerkleTreeLeaf(data interface{}, timestamp uint64) *MerkleTreeLeaf {
jsonData, err := json.Marshal(AddJSONRequest{Data: data})
if err != nil {
return nil
}
// Match the JSON serialization implemented by json-c
jsonStr := strings.Replace(string(jsonData), ":", ": ", -1)
jsonStr = strings.Replace(jsonStr, ",", ", ", -1)
jsonStr = strings.Replace(jsonStr, "{", "{ ", -1)
jsonStr = strings.Replace(jsonStr, "}", " }", -1)
jsonStr = strings.Replace(jsonStr, "/", `\/`, -1)
// TODO: Pending google/certificate-transparency#1243, replace with
// ObjectHash once supported by CT server.
return &MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
Timestamp: timestamp,
EntryType: XJSONLogEntryType,
JSONEntry: &JSONDataEntry{Data: []byte(jsonStr)},
},
}
}
// MerkleTreeLeafFromRawChain generates a MerkleTreeLeaf from a chain (in DER-encoded form) and timestamp.
func MerkleTreeLeafFromRawChain(rawChain []ASN1Cert, etype LogEntryType, timestamp uint64) (*MerkleTreeLeaf, error) {
// Need at most 3 of the chain
count := 3
if count > len(rawChain) {
count = len(rawChain)
}
chain := make([]*x509.Certificate, count)
for i := range chain {
cert, err := x509.ParseCertificate(rawChain[i].Data)
if err != nil {
return nil, fmt.Errorf("failed to parse chain[%d] cert: %v", i, err)
}
chain[i] = cert
}
return MerkleTreeLeafFromChain(chain, etype, timestamp)
}
// MerkleTreeLeafFromChain generates a MerkleTreeLeaf from a chain and timestamp.
func MerkleTreeLeafFromChain(chain []*x509.Certificate, etype LogEntryType, timestamp uint64) (*MerkleTreeLeaf, error) {
leaf := MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
EntryType: etype,
Timestamp: timestamp,
},
}
if etype == X509LogEntryType {
leaf.TimestampedEntry.X509Entry = &ASN1Cert{Data: chain[0].Raw}
return &leaf, nil
}
if etype != PrecertLogEntryType {
return nil, fmt.Errorf("unknown LogEntryType %d", etype)
}
// Pre-certs are more complicated. First, parse the leaf pre-cert and its
// putative issuer.
if len(chain) < 2 {
return nil, fmt.Errorf("no issuer cert available for precert leaf building")
}
issuer := chain[1]
cert := chain[0]
var preIssuer *x509.Certificate
if IsPreIssuer(issuer) {
// Replace the cert's issuance information with details from the pre-issuer.
preIssuer = issuer
// The issuer of the pre-cert is not going to be the issuer of the final
// cert. Change to use the final issuer's key hash.
if len(chain) < 3 {
return nil, fmt.Errorf("no issuer cert available for pre-issuer")
}
issuer = chain[2]
}
// Next, post-process the DER-encoded TBSCertificate, to remove the CT poison
// extension and possibly update the issuer field.
defangedTBS, err := x509.BuildPrecertTBS(cert.RawTBSCertificate, preIssuer)
if err != nil {
return nil, fmt.Errorf("failed to remove poison extension: %v", err)
}
leaf.TimestampedEntry.EntryType = PrecertLogEntryType
leaf.TimestampedEntry.PrecertEntry = &PreCert{
IssuerKeyHash: sha256.Sum256(issuer.RawSubjectPublicKeyInfo),
TBSCertificate: defangedTBS,
}
return &leaf, 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 {
for _, eku := range issuer.ExtKeyUsage {
if eku == x509.ExtKeyUsageCertificateTransparency {
return true
}
}
return false
}
// LogEntryFromLeaf converts a LeafEntry object (which has the raw leaf data after JSON parsing)
// into a LogEntry object (which includes x509.Certificate objects, after TLS and ASN.1 parsing).
// Note that this function may return a valid LogEntry object and a non-nil error value, when
// the error indicates a non-fatal parsing error (of type x509.NonFatalErrors).
func LogEntryFromLeaf(index int64, leafEntry *LeafEntry) (*LogEntry, error) {
var leaf MerkleTreeLeaf
if rest, err := tls.Unmarshal(leafEntry.LeafInput, &leaf); err != nil {
return nil, fmt.Errorf("failed to unmarshal MerkleTreeLeaf for index %d: %v", index, err)
} else if len(rest) > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after MerkleTreeLeaf for index %d", len(rest), index)
}
var err error
entry := LogEntry{Index: index, Leaf: leaf}
switch leaf.TimestampedEntry.EntryType {
case X509LogEntryType:
var certChain CertificateChain
if rest, err := tls.Unmarshal(leafEntry.ExtraData, &certChain); err != nil {
return nil, fmt.Errorf("failed to unmarshal ExtraData for index %d: %v", index, err)
} else if len(rest) > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after CertificateChain for index %d", len(rest), index)
}
entry.Chain = certChain.Entries
entry.X509Cert, err = leaf.X509Certificate()
if _, ok := err.(x509.NonFatalErrors); !ok && err != nil {
return nil, fmt.Errorf("failed to parse certificate in MerkleTreeLeaf for index %d: %v", index, err)
}
case PrecertLogEntryType:
var precertChain PrecertChainEntry
if rest, err := tls.Unmarshal(leafEntry.ExtraData, &precertChain); err != nil {
return nil, fmt.Errorf("failed to unmarshal PrecertChainEntry for index %d: %v", index, err)
} else if len(rest) > 0 {
return nil, fmt.Errorf("trailing data (%d bytes) after PrecertChainEntry for index %d", len(rest), index)
}
entry.Chain = precertChain.CertificateChain
var tbsCert *x509.Certificate
tbsCert, err = leaf.Precertificate()
if _, ok := err.(x509.NonFatalErrors); !ok && err != nil {
return nil, fmt.Errorf("failed to parse precertificate in MerkleTreeLeaf for index %d: %v", index, err)
}
entry.Precert = &Precertificate{
Submitted: precertChain.PreCertificate,
IssuerKeyHash: leaf.TimestampedEntry.PrecertEntry.IssuerKeyHash,
TBSCertificate: tbsCert,
}
default:
return nil, fmt.Errorf("saw unknown entry type at index %d: %v", index, leaf.TimestampedEntry.EntryType)
}
// err may hold a x509.NonFatalErrors object.
return &entry, err
}

View File

@ -0,0 +1,513 @@
// Copyright 2015 Google Inc. 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 ct
import (
"bytes"
"encoding/hex"
"encoding/pem"
"io/ioutil"
"reflect"
"strings"
"testing"
"github.com/google/certificate-transparency-go/tls"
)
func dh(h string) []byte {
r, err := hex.DecodeString(h)
if err != nil {
panic(err)
}
return r
}
const (
defaultSCTLogIDString string = "iamapublickeyshatwofivesixdigest"
defaultSCTTimestamp uint64 = 1234
defaultSCTSignatureString string = "\x04\x03\x00\x09signature"
defaultCertifictateString string = "certificate"
defaultPrecertString string = "precert"
defaultPrecertIssuerHashString string = "iamapublickeyshatwofivesixdigest"
defaultPrecertTBSString string = "tbs"
defaultCertificateSCTSignatureInputHexString string =
// version, 1 byte
"00" +
// signature type, 1 byte
"00" +
// timestamp, 8 bytes
"00000000000004d2" +
// entry type, 2 bytes
"0000" +
// leaf certificate length, 3 bytes
"00000b" +
// leaf certificate, 11 bytes
"6365727469666963617465" +
// extensions length, 2 bytes
"0000" +
// extensions, 0 bytes
""
defaultPrecertSCTSignatureInputHexString string =
// version, 1 byte
"00" +
// signature type, 1 byte
"00" +
// timestamp, 8 bytes
"00000000000004d2" +
// entry type, 2 bytes
"0001" +
// issuer key hash, 32 bytes
"69616d617075626c69636b657973686174776f66697665736978646967657374" +
// tbs certificate length, 3 bytes
"000003" +
// tbs certificate, 3 bytes
"746273" +
// extensions length, 2 bytes
"0000" +
// extensions, 0 bytes
""
defaultSTHSignedHexString string =
// version, 1 byte
"00" +
// signature type, 1 byte
"01" +
// timestamp, 8 bytes
"0000000000000929" +
// tree size, 8 bytes
"0000000000000006" +
// root hash, 32 bytes
"696d757374626565786163746c7974686972747974776f62797465736c6f6e67"
defaultSCTHexString string =
// version, 1 byte
"00" +
// keyid, 32 bytes
"69616d617075626c69636b657973686174776f66697665736978646967657374" +
// timestamp, 8 bytes
"00000000000004d2" +
// extensions length, 2 bytes
"0000" +
// extensions, 0 bytes
// hash algo, sig algo, 2 bytes
"0403" +
// signature length, 2 bytes
"0009" +
// signature, 9 bytes
"7369676e6174757265"
defaultSCTListHexString string = "0476007400380069616d617075626c69636b657973686174776f6669766573697864696765737400000000000004d20000040300097369676e617475726500380069616d617075626c69636b657973686174776f6669766573697864696765737400000000000004d20000040300097369676e6174757265"
)
func defaultSCTLogID() LogID {
var id LogID
copy(id.KeyID[:], defaultSCTLogIDString)
return id
}
func defaultSCTSignature() DigitallySigned {
var ds DigitallySigned
if _, err := tls.Unmarshal([]byte(defaultSCTSignatureString), &ds); err != nil {
panic(err)
}
return ds
}
func defaultSCT() SignedCertificateTimestamp {
return SignedCertificateTimestamp{
SCTVersion: V1,
LogID: defaultSCTLogID(),
Timestamp: defaultSCTTimestamp,
Extensions: []byte{},
Signature: defaultSCTSignature()}
}
func defaultCertificate() []byte {
return []byte(defaultCertifictateString)
}
func defaultExtensions() []byte {
return []byte{}
}
func defaultCertificateSCTSignatureInput(t *testing.T) []byte {
t.Helper()
r, err := hex.DecodeString(defaultCertificateSCTSignatureInputHexString)
if err != nil {
t.Fatalf("failed to decode defaultCertificateSCTSignatureInputHexString: %v", err)
}
return r
}
func defaultCertificateLogEntry() LogEntry {
return LogEntry{
Index: 1,
Leaf: MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
Timestamp: defaultSCTTimestamp,
EntryType: X509LogEntryType,
X509Entry: &ASN1Cert{Data: defaultCertificate()},
},
},
}
}
func defaultPrecertSCTSignatureInput(t *testing.T) []byte {
t.Helper()
r, err := hex.DecodeString(defaultPrecertSCTSignatureInputHexString)
if err != nil {
t.Fatalf("failed to decode defaultPrecertSCTSignatureInputHexString: %v", err)
}
return r
}
func defaultPrecertTBS() []byte {
return []byte(defaultPrecertTBSString)
}
func defaultPrecertIssuerHash() [32]byte {
var b [32]byte
copy(b[:], []byte(defaultPrecertIssuerHashString))
return b
}
func defaultPrecertLogEntry() LogEntry {
return LogEntry{
Index: 1,
Leaf: MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
Timestamp: defaultSCTTimestamp,
EntryType: PrecertLogEntryType,
PrecertEntry: &PreCert{
IssuerKeyHash: defaultPrecertIssuerHash(),
TBSCertificate: defaultPrecertTBS(),
},
},
},
}
}
func defaultSTH() SignedTreeHead {
var root SHA256Hash
copy(root[:], "imustbeexactlythirtytwobyteslong")
return SignedTreeHead{
TreeSize: 6,
Timestamp: 2345,
SHA256RootHash: root,
TreeHeadSignature: DigitallySigned{
Algorithm: tls.SignatureAndHashAlgorithm{
Hash: tls.SHA256,
Signature: tls.ECDSA},
Signature: []byte("tree_signature"),
},
}
}
//////////////////////////////////////////////////////////////////////////////////
// Tests start here:
//////////////////////////////////////////////////////////////////////////////////
func TestSerializeV1SCTSignatureInputForCertificateKAT(t *testing.T) {
serialized, err := SerializeSCTSignatureInput(defaultSCT(), defaultCertificateLogEntry())
if err != nil {
t.Fatalf("Failed to serialize SCT for signing: %v", err)
}
if bytes.Compare(serialized, defaultCertificateSCTSignatureInput(t)) != 0 {
t.Fatalf("Serialized certificate signature input doesn't match expected answer:\n%v\n%v", serialized, defaultCertificateSCTSignatureInput(t))
}
}
func TestSerializeV1SCTSignatureInputForPrecertKAT(t *testing.T) {
serialized, err := SerializeSCTSignatureInput(defaultSCT(), defaultPrecertLogEntry())
if err != nil {
t.Fatalf("Failed to serialize SCT for signing: %v", err)
}
if bytes.Compare(serialized, defaultPrecertSCTSignatureInput(t)) != 0 {
t.Fatalf("Serialized precertificate signature input doesn't match expected answer:\n%v\n%v", serialized, defaultPrecertSCTSignatureInput(t))
}
}
func TestSerializeV1SCTJSONSignature(t *testing.T) {
entry := LogEntry{Leaf: *CreateJSONMerkleTreeLeaf("data", defaultSCT().Timestamp)}
expected := dh(
// version, 1 byte
"00" +
// signature type, 1 byte
"00" +
// timestamp, 8 bytes
"00000000000004d2" +
// entry type, 2 bytes
"8000" +
// tbs certificate length, 18 bytes
"000012" +
// { "data": "data" }, 3 bytes
"7b202264617461223a20226461746122207d" +
// extensions length, 2 bytes
"0000" +
// extensions, 0 bytes
"")
serialized, err := SerializeSCTSignatureInput(defaultSCT(), entry)
if err != nil {
t.Fatalf("Failed to serialize SCT for signing: %v", err)
}
if !bytes.Equal(serialized, expected) {
t.Fatalf("Serialized JSON signature :\n%x, want\n%x", serialized, expected)
}
}
func TestSerializeV1STHSignatureKAT(t *testing.T) {
b, err := SerializeSTHSignatureInput(defaultSTH())
if err != nil {
t.Fatalf("Failed to serialize defaultSTH: %v", err)
}
if bytes.Compare(b, mustDehex(t, defaultSTHSignedHexString)) != 0 {
t.Fatalf("defaultSTH incorrectly serialized, expected:\n%v\ngot:\n%v", mustDehex(t, defaultSTHSignedHexString), b)
}
}
func TestMarshalDigitallySigned(t *testing.T) {
b, err := tls.Marshal(
DigitallySigned{
Algorithm: tls.SignatureAndHashAlgorithm{
Hash: tls.SHA512,
Signature: tls.ECDSA},
Signature: []byte("signature")})
if err != nil {
t.Fatalf("Failed to marshal DigitallySigned struct: %v", err)
}
if b[0] != byte(tls.SHA512) {
t.Fatalf("Expected b[0] == SHA512, but found %v", tls.HashAlgorithm(b[0]))
}
if b[1] != byte(tls.ECDSA) {
t.Fatalf("Expected b[1] == ECDSA, but found %v", tls.SignatureAlgorithm(b[1]))
}
if b[2] != 0x00 || b[3] != 0x09 {
t.Fatalf("Found incorrect length bytes, expected (0x00, 0x09) found %v", b[2:3])
}
if string(b[4:]) != "signature" {
t.Fatalf("Found incorrect signature bytes, expected %v, found %v", []byte("signature"), b[4:])
}
}
func TestUnmarshalDigitallySigned(t *testing.T) {
var ds DigitallySigned
if _, err := tls.Unmarshal([]byte("\x01\x02\x00\x0aSiGnAtUrE!"), &ds); err != nil {
t.Fatalf("Failed to unmarshal DigitallySigned: %v", err)
}
if ds.Algorithm.Hash != tls.MD5 {
t.Fatalf("Expected HashAlgorithm %v, but got %v", tls.MD5, ds.Algorithm.Hash)
}
if ds.Algorithm.Signature != tls.DSA {
t.Fatalf("Expected SignatureAlgorithm %v, but got %v", tls.DSA, ds.Algorithm.Signature)
}
if string(ds.Signature) != "SiGnAtUrE!" {
t.Fatalf("Expected Signature %v, but got %v", []byte("SiGnAtUrE!"), ds.Signature)
}
}
func TestMarshalUnmarshalSCTRoundTrip(t *testing.T) {
sctIn := defaultSCT()
b, err := tls.Marshal(sctIn)
if err != nil {
t.Fatalf("tls.Marshal(SCT)=nil,%v; want no error", err)
}
var sctOut SignedCertificateTimestamp
if _, err := tls.Unmarshal(b, &sctOut); err != nil {
t.Errorf("tls.Unmarshal(%s)=nil,%v; want %+v,nil", hex.EncodeToString(b), err, sctIn)
} else if !reflect.DeepEqual(sctIn, sctOut) {
t.Errorf("tls.Unmarshal(%s)=%v,nil; want %+v,nil", hex.EncodeToString(b), sctOut, sctIn)
}
}
func TestMarshalSCT(t *testing.T) {
b, err := tls.Marshal(defaultSCT())
if err != nil {
t.Errorf("tls.Marshal(defaultSCT)=nil,%v; want %s", err, defaultSCTHexString)
} else if !bytes.Equal(dh(defaultSCTHexString), b) {
t.Errorf("tls.Marshal(defaultSCT)=%s,nil; want %s", hex.EncodeToString(b), defaultSCTHexString)
}
}
func TestUnmarshalSCT(t *testing.T) {
want := defaultSCT()
var got SignedCertificateTimestamp
if _, err := tls.Unmarshal(dh(defaultSCTHexString), &got); err != nil {
t.Errorf("tls.Unmarshal(%s)=nil,%v; want %+v,nil", defaultSCTHexString, err, want)
} else if !reflect.DeepEqual(got, want) {
t.Errorf("tls.Unmarshal(%s)=%+v,nil; want %+v,nil", defaultSCTHexString, got, want)
}
}
func TestX509MerkleTreeLeafHash(t *testing.T) {
certFile := "./testdata/test-cert.pem"
sctFile := "./testdata/test-cert.proof"
certB, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatalf("Failed to read file %s: %v", certFile, err)
}
certDER, _ := pem.Decode(certB)
sctB, err := ioutil.ReadFile(sctFile)
if err != nil {
t.Fatalf("Failed to read file %s: %v", sctFile, err)
}
var sct SignedCertificateTimestamp
if _, err := tls.Unmarshal(sctB, &sct); err != nil {
t.Fatalf("Failed to deserialize SCT: %v", err)
}
leaf := CreateX509MerkleTreeLeaf(ASN1Cert{Data: certDER.Bytes}, sct.Timestamp)
b, err := tls.Marshal(*leaf)
if err != nil {
t.Fatalf("Failed to Serialize x509 leaf: %v", err)
}
leafBytes := dh("00000000013ddb27ded900000002ce308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b300906035504061302474231243022060355040a131b4365727469666963617465205472616e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040713074572772057656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052310b30090603550406130247423121301f060355040a13184365727469666963617465205472616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d0603551d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d230476307480145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b300906035504061302474231243022060355040a131b4365727469666963617465205472616e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040713074572772057656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171cd84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cbf667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c69727d24c4fccd249295795814d1dac0e60000")
if !bytes.Equal(b, leafBytes) {
t.Errorf("CreateX509MerkleTreeLeaf(): got\n %x, want\n%x", b, sctB)
}
}
func TestJSONMerkleTreeLeaf(t *testing.T) {
data := `CioaINV25GV8X4a6M6Q10avSLP9PYd5N8MwWxQvWU7E2CzZ8IgYI0KnavAUSWAoIZDc1NjMzMzMSTAgEEAMaRjBEAiBQlnp6Q3di86g8M3l5gz+9qls/Cz1+KJ+tK/jpaBtUCgIgXaJ94uLsnChA1NY7ocGwKrQwPU688hwaZ5L/DboV4mQ=2`
timestamp := uint64(1469664866615)
leaf := CreateJSONMerkleTreeLeaf(data, timestamp)
b, err := tls.Marshal(*leaf)
if err != nil {
t.Fatalf("Failed to Serialize x509 leaf: %v", err)
}
leafBytes := dh("0000000001562eda313780000000c67b202264617461223a202243696f61494e563235475638583461364d365131306176534c5039505964354e384d77577851765755374532437a5a3849675949304b6e617641555357416f495a4463314e6a4d7a4d7a4d535441674545414d61526a4245416942516c6e703651336469383667384d336c35677a2b39716c735c2f437a312b4b4a2b744b5c2f6a70614274554367496758614a3934754c736e436841314e59376f6347774b72517750553638386877615a354c5c2f44626f56346d513d3222207d0000")
if !bytes.Equal(b, leafBytes) {
t.Errorf("CreateJSONMerkleTreeLeaf(): got\n%x, want\n%x", b, leafBytes)
}
}
func TestLogEntryFromLeaf(t *testing.T) {
const (
// Cert example taken from entry #1 in argon2018 log
leafDER = "308204ef308202d7a00302010202070556658a503cca300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303132343331355a170d3138303333313038333231375a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233363839393537353331363230820122300d06092a864886f70d01010105000382010f003082010a0282010100a2fb53365dfbcefea77e1d65bc40f34f7919fcae85d82d3003428199f0c893fca95ba139156fd5e9a3bd84dc6dab8e74151fde6dd25b31526c85719bbf8990f3d6b21bb7f321306f6ddc50b96e8917fa103b388a00e1e954ee0232a9f9fb2efa8c9f9196a7fe84dad1f66b5d36127c71c9dcf25a04acd7bfda7866dfb77498c63a7ae9e7d0772fe9ba938a9ff6c0209196988158e6ea055fe967dd7599ef4bd7f306ded231cca10d89b4d6de40916e615d1d4cc6032585822a650743e34735d464fc0d544d1fad8c293df22f4a55ce3fbfb55d90cdc5ab84695a5a13d46f3176f143d9d28f60dca841eac603d30cec830a62feec091c927e6c781df330f14ca10203010001a3818b30818830130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e041604142f3948061fe546939f5e8dbc3fe4c0a1fbaab6b7300d06092a864886f70d01010b0500038202010052a2480c754c51cfdc9f99a82a8eb7c34e5e2bdcdfdd7543fadadb578083416d34ebb87fea3c90baf97f06be5baf5c41101ad1bdd2a2f554de6a3e8cd5ff3d78354badad01032a007d2eaf03590ee5397e223b2936f0c8b59c0407079c8975ffb34eb1cfe784cf3bc45e198a601473537f1ef382e0b5311d2ddf430ade7cfd28900ec9d91c1db49a6b2fb1b9e13b94135fed978d646e048b2fa9dc36ef5821cea8ebbed38d4c2d7811e9660f23d7636b295caccdc945a010a4c364fd7e7480aa5282d28fc46ce7f4f636ef2cc57c8bb1aee5da79bc6107205d4abcd3fb09a1db023ba4e8e9f34ae36ff5b2672fc2a14af8d23d67a437b3eb507ca90f73121841ab1498ab712d18063244dc3514bfffbaf6d45acdfc5316a248589a04b79b2abbca454e2e21f9b487e21eea21565c99ebc1013b87253c91f43ac6d2d2dea7090877c2a7404bce2545662ce005dc12eb57b1efe7145af8070b5dfa86736664a644a9c0f7e7c38d715cf874b818d519927eddc69b55c781b6e0a6eef8f3e46b9e059105b7932a978704e924904dbaa9583f3dd606467f4cc41589b702f1a02d517d3cd93b55d67d0b379e2527fded951be9dfb86d473613e6d9b8399ef5174d3e03185bd3cb4ea859b465c6c49010d4119d613a60878c0e453f17cfa3ce925e10f6e0a5adb745cebe218c3c82627a120e2907eeb9ec5307664474093cbc92d65fc7"
leafCA = "308205c8308203b0a00302010202021001300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132323633305a170d3139303731363132323633305a307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d656469617465203130820222300d06092a864886f70d01010105000382020f003082020a0282020100c1e874feff9aeef303bbfa63453881faaf8dc1c22c09641daf430381f33bc157bf6c4c8a8d57b1abc792859d20f2191509c597c437b14673dea5af4bea14396dd436dc620555d7953e0ede01f7ffb44f3ff7cde64ed245634e0df0aafce9c3ac5eb63d8de1d969cac8854195403a9f9d1d4c3dceedf1351edd945743bc54ab745b204fb5259fe3edf695c2cf90b886c48ff680b744fec2691b4345242b31c36f3118b727c5de5c25ec1aa30a4c2461c5119ef6bb90d816e6e44c5b9955bfa2ed3416bf6e4a53c92fafac0d1f0b8bf3d35be0d4f61ca05d28fc662dfa588fba3ee0380470c012ded9e51bbf1e7a25efa745784c49d05eaf8dcee0527361ec913126005e972cf4b863914f8361582ed24563ff9e03c52e8a9ca3264c56c186b4ec52b7e695ce42ae17ec7ae0257131e1dbf48f2dde242e6e91ea304988135a15482b05fc091355328b39e586e8dd3a4a3a14cb97eef68f9f69728c291f2195d2cce73d4ae90845b1bfc5fae040b94fc359a29511981b9966aeb56d3a7c5e48f8eca815e5be86b3d36e6a27e0e2c4dee6e30f12a7c936b8c98cad5928aca238dfc39cf9f2c5246cbbbb280cb6f99eb49bfd1d78089539072c164c7083371746dedbc4dec1cb9439073af3f2e60f8c505f067961a8c539454fc5341158eccc78532f3e39c3187c9439fc0ff88ee957131d478df063dd50b2ad3fe7a070e905e3868b0203010001a350304e301d0603551d0e04160414e93c04e1802fc284132d26709ef2fd1acfaafec6301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d010105050003820201000858cbd545f2e92e09906ac39f3d55c13607a651436386c6e90f128773a0eb3f4725d8af35fb7f880436b481f2cf47801825de54f13f8f5920bf3d916e753141de5e59d2debbfc3fc226721f15a16d3b7a4618ea0551639f1d2cb7b9faad1b7e070f23a6e8197c3d7549bba6553fd5db419ce399477f6a0481b90f51c9d307d82cb05cf967828a1ace65207cf86b6d16792245dcf24b4c179f91184736e7e2fcb863a4b5c89b0ac2f368390a10594b95c856e259c77564316898cf87a6817d18585fc976d681d9d510ef2ad37e8ad0e49f5bd499c9ec7fe8f43b17dffb9b7d0dfd8300c1c5389c9ea0be4370dcbf78bd3efc2308d250b866bbca031c0c49ff77a7a5420daa1f1b6a444d366653974c2d179c3009871ee6c89140fca9efdf23bd4b88c6ebaeb9286f58f3cfc21e4874f182d1ecd6058919b03b18db7b795be9cb25fc5166a945ef8e1133cd60312a3234f4649df6166407cbb5ecc838e9e118c05dee7c896a9987655ae7e349cd8166e68d34dea3b4ae892a9f2385053271e860b542be3650503974f3bb6f2688375ab28487da6751d0f2c3a35e78efe30b19d57808ebea2c4453990ad81eb96289c0f99c5080f82092bc6123a340c63a617f3bc4adc1298d88a278a693ef93688611d3b4eded0b6d023ed9f6c2ea8836483197525b1b1bce70a90c3403b094d5f412aa1141b9965ab8314c52f772deffc1008c"
rootCA = "308205cd308203b5a0030201020209009ed3ccb1d12ca272300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132303534335a170d3431313230323132303534335a307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f7430820222300d06092a864886f70d01010105000382020f003082020a0282020100aa161cf2205ed81ac565483cda426a3db2e588fdb758b17b93ea8d68495d534a01ba4f6cd1c0fc0a128af79c066dc54c3f437e05ba275ee61dbf9cbdb2928183738139397b6189ae738fef2b9b609a6dd8e0b0d0e20b243db936c029cdc2220af2c0e1a5e4aa41a006af458957e2b1178d27156ef0cb717e16d54025d97f43e9916fb240fb85f7d579462fa0ac76c76256843750bf1ccdfeb76c8c47886477644d5ec3235628adf6a09c8488bfa5036de717908151a6b585f273dd9fb5332b9af76e8fbfa91eaf4311816dde27c5c44f2fd06cc2204d7147f77ba6b16a2a5fca470023614729538bee6b3cb07264713832aec161550eb501906802215223acc2564ad1f98bb5934924eb56d383fc7598be45c89d995281c0efb0d206d29a6d25a10a48fe235332379c5ca69e83599faa677dd20823f5c84a961255eca5d4871d54ca1df0774aa117b0f42cd6e9fda7e8a48a53923c5f94043353544e644b5a6562e5cef9fc2bd2fcfcce3323335cf7fe7c4d83c1b7f839c4790192d3ba9aa9f32093aa8ee7cbe708059d538dc663cca1b825331aa836754a0d13de63bf65b6e2044dcdf041f1a0c5a9c3c38fe74cf576d451c23eaa519db32ef9e039bd848a194c3b5e41a55642dc283ddbd73d1dd97ae6951de18ad89d005007fae7e88bc7a3cce8b7ccc49603a0db67c76d58a28d4b77aa7460801e34377d0c5e4606c2e25b0203010001a350304e301d0603551d0e04160414f35f7b7549e37841396a20b67c6b4c5cc93d5841301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d01010505000382020100771cfea34579a97520d8c2423d68ecd07891f8c7f1c38bf3cd30ea9d3637bfc5d373532ec7656558bef4950646750c6fe085c52d9ffc09e66ebfa2b067de7727cb381d2b25db58b9c2197fd5eb53e020f429b86a3b1f37a07a761a66a5b3ecd797c46695a37ff2d47c54126be6bd28a2a103357227c6b73f7f689b09b48927e6e9a52267a728a115d4bcbb477533dc28f3fc57da735a3ec54fbc36990b17febb7e46b324208c1fa7425a0cba48bdc0381ea8285215261c3c483f2fa6d1da0dba4949107189f32d728a7ff395d43430af3b8ce4be5075bcf67d66661941dc8be37340f8f9282b2d2aadd69065932ad49769f8bc7fc9e5f6e79ff392420ba78de3172778e2b67e4df18440dd561e5a7844c8efa06c0e5f5b8695fa069114a00518fb4c19f9d7855831b5eeebced14b8598daffa49f2dcf505bff6417d84b28e83599d4e0371ef64b2d82ffa068a31044f7322fee2f654ec357c9c121f3458a509728c37f5673412ad0d5e76aa6b4eb1582181a8be404d3dc35e71edd83ee388087d6147c4d86f1cacacface0104df1f4b100c2ceb1be4d1851c4f31e7c4409262185878f23cceb30790143f0d5bd80d2c0ed4260aaa312597d950aaf3c8bcfc812d9a56e8d160dd772a494743710a7e478a046d8a5d505ee6b8cc37fec09dfd4cb57c6c4d8e8ef2a22e1d9e50957852633138d722253e51ba77b0069d38812207a"
noExts = "0000"
// Precert example taken from entry #2 in argon2018 log
issuerKeyHash = "e37689003073a0c649cc656de946c03174d25c566fe3c3805b846f5236943798"
precertTBS = "0002db308202d7a0030201020207055667377e8bcc300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303134343331365a170d3138303432363134323430315a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233373631393632313337303830820122300d06092a864886f70d01010105000382010f003082010a0282010100dea100ff02f31ae6f76f9c26525afcdd0ef6eef780d72b4b1c0ff14fc7ac021852d6f34af20713d05fea2c2e1a4b488b3849c2511cf30fcd2e61d9e7392557498a4ff600c8fdc912f05c8a583a5f2b6a3d3320c6cc10b7eed502de392d11b3c4d57fa6e3ddc69d3f73305bb6441a0359bd526272784523ae5319cffd2993abba54d26c4c1b760c8660b65161a349e415207a6fbb20d02ce13054e6ffa7776bccfd26c3e4220e0e504f102f352260aaa9864411f7eeae8f6071c9ba54b83d11af43ab58dcb6a7d9053654b98b165fb84a27c78361d957c70a064f45bf4501ef744302e497839a34222e94b3018e587c70c130208976d9f80cf6741a95abfa6b810203010001a3818b30818830130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e04160414df25c220250d548e08341c26cadc5effc177841c"
precertDER = "30820504308202eca0030201020207055667377e8bcc300d06092a864886f70d01010b0500307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d6564696174652031301e170d3137303831303134343331365a170d3138303432363134323430315a3063310b3009060355040613024742310f300d06035504070c064c6f6e646f6e31283026060355040a0c1f476f6f676c65204365727469666963617465205472616e73706172656e637931193017060355040513103135303233373631393632313337303830820122300d06092a864886f70d01010105000382010f003082010a0282010100dea100ff02f31ae6f76f9c26525afcdd0ef6eef780d72b4b1c0ff14fc7ac021852d6f34af20713d05fea2c2e1a4b488b3849c2511cf30fcd2e61d9e7392557498a4ff600c8fdc912f05c8a583a5f2b6a3d3320c6cc10b7eed502de392d11b3c4d57fa6e3ddc69d3f73305bb6441a0359bd526272784523ae5319cffd2993abba54d26c4c1b760c8660b65161a349e415207a6fbb20d02ce13054e6ffa7776bccfd26c3e4220e0e504f102f352260aaa9864411f7eeae8f6071c9ba54b83d11af43ab58dcb6a7d9053654b98b165fb84a27c78361d957c70a064f45bf4501ef744302e497839a34222e94b3018e587c70c130208976d9f80cf6741a95abfa6b810203010001a381a030819d30130603551d25040c300a06082b0601050507030130230603551d11041c301a8218666c6f776572732d746f2d7468652d776f726c642e636f6d300c0603551d130101ff04023000301f0603551d23041830168014e93c04e1802fc284132d26709ef2fd1acfaafec6301d0603551d0e04160414df25c220250d548e08341c26cadc5effc177841c3013060a2b06010401d6790204030101ff04020500300d06092a864886f70d01010b05000382020100ae9ca16ec19bb469d08628b1296f50e3e15b362e2b18c691b11eef3af9ce655bf74e0c21c84f6091132851ba78465c3ae97a1409ee7505395d4e7e0318189029a12bf1c3ba2b6f3231c7aac13dbbfaade8d56f0fbe91d32440ad0ab816184c72392154275ead8418cc62e4e2b08de1b14acb6b27c0f36fa586feb875666f46d232a32ef022440d52cdd8bd31a42de55bfa77de8742816f086830b07eedbde545af5a2b9dd17bd49ded508589a0673f6e0d55f210818422093fd10939f0c81521ca654958e6e01b76ef8c7380bdb331e67d44ccb18a83ed04d97d463c37c7cbc592768e2373e198a1d64be3bd22d1833994706797461d05a85e779cd6e2b4b2b14e81d1eca454f29780c47a7366041ace1a48319eff3f1f04bbd471d5125774ef050e47bf664a98101b7be3337bb786b760a92be46488c6a15f72972a4b7c932c736311f0ac1d40920580329657f00e26cfb6d3b1db1eb7a95952fbcbfcbaf9d17587f03aeb9c3b403d1dccad895316658d35fe385fc5a62b60db36e3f07c4798314936aeb3f40094aee9ec1350ea8f68d1aeb41b211ecd0c9e29c5fa2d6576bcb2ad5ec8cc936e1f5a127afa0de3ae490b914adaa733f18ed9348d497e10ba4aa3008f84deec6976292dc0d3c2aa523602188916dd468b47f3d571e71fe51cd293c805d1280a53ab9f519a616d889303be461354edfc29dc3cc85d8570264cf4"
precertCA = "308205c8308203b0a00302010202021001300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132323633305a170d3139303731363132323633305a307f310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793123302106035504030c1a4d657267652044656c617920496e7465726d656469617465203130820222300d06092a864886f70d01010105000382020f003082020a0282020100c1e874feff9aeef303bbfa63453881faaf8dc1c22c09641daf430381f33bc157bf6c4c8a8d57b1abc792859d20f2191509c597c437b14673dea5af4bea14396dd436dc620555d7953e0ede01f7ffb44f3ff7cde64ed245634e0df0aafce9c3ac5eb63d8de1d969cac8854195403a9f9d1d4c3dceedf1351edd945743bc54ab745b204fb5259fe3edf695c2cf90b886c48ff680b744fec2691b4345242b31c36f3118b727c5de5c25ec1aa30a4c2461c5119ef6bb90d816e6e44c5b9955bfa2ed3416bf6e4a53c92fafac0d1f0b8bf3d35be0d4f61ca05d28fc662dfa588fba3ee0380470c012ded9e51bbf1e7a25efa745784c49d05eaf8dcee0527361ec913126005e972cf4b863914f8361582ed24563ff9e03c52e8a9ca3264c56c186b4ec52b7e695ce42ae17ec7ae0257131e1dbf48f2dde242e6e91ea304988135a15482b05fc091355328b39e586e8dd3a4a3a14cb97eef68f9f69728c291f2195d2cce73d4ae90845b1bfc5fae040b94fc359a29511981b9966aeb56d3a7c5e48f8eca815e5be86b3d36e6a27e0e2c4dee6e30f12a7c936b8c98cad5928aca238dfc39cf9f2c5246cbbbb280cb6f99eb49bfd1d78089539072c164c7083371746dedbc4dec1cb9439073af3f2e60f8c505f067961a8c539454fc5341158eccc78532f3e39c3187c9439fc0ff88ee957131d478df063dd50b2ad3fe7a070e905e3868b0203010001a350304e301d0603551d0e04160414e93c04e1802fc284132d26709ef2fd1acfaafec6301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d010105050003820201000858cbd545f2e92e09906ac39f3d55c13607a651436386c6e90f128773a0eb3f4725d8af35fb7f880436b481f2cf47801825de54f13f8f5920bf3d916e753141de5e59d2debbfc3fc226721f15a16d3b7a4618ea0551639f1d2cb7b9faad1b7e070f23a6e8197c3d7549bba6553fd5db419ce399477f6a0481b90f51c9d307d82cb05cf967828a1ace65207cf86b6d16792245dcf24b4c179f91184736e7e2fcb863a4b5c89b0ac2f368390a10594b95c856e259c77564316898cf87a6817d18585fc976d681d9d510ef2ad37e8ad0e49f5bd499c9ec7fe8f43b17dffb9b7d0dfd8300c1c5389c9ea0be4370dcbf78bd3efc2308d250b866bbca031c0c49ff77a7a5420daa1f1b6a444d366653974c2d179c3009871ee6c89140fca9efdf23bd4b88c6ebaeb9286f58f3cfc21e4874f182d1ecd6058919b03b18db7b795be9cb25fc5166a945ef8e1133cd60312a3234f4649df6166407cbb5ecc838e9e118c05dee7c896a9987655ae7e349cd8166e68d34dea3b4ae892a9f2385053271e860b542be3650503974f3bb6f2688375ab28487da6751d0f2c3a35e78efe30b19d57808ebea2c4453990ad81eb96289c0f99c5080f82092bc6123a340c63a617f3bc4adc1298d88a278a693ef93688611d3b4eded0b6d023ed9f6c2ea8836483197525b1b1bce70a90c3403b094d5f412aa1141b9965ab8314c52f772deffc1008c"
precertRoot = "308205cd308203b5a0030201020209009ed3ccb1d12ca272300d06092a864886f70d0101050500307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f74301e170d3134303731373132303534335a170d3431313230323132303534335a307d310b3009060355040613024742310f300d06035504080c064c6f6e646f6e31173015060355040a0c0e476f6f676c6520554b204c74642e3121301f060355040b0c184365727469666963617465205472616e73706172656e63793121301f06035504030c184d657267652044656c6179204d6f6e69746f7220526f6f7430820222300d06092a864886f70d01010105000382020f003082020a0282020100aa161cf2205ed81ac565483cda426a3db2e588fdb758b17b93ea8d68495d534a01ba4f6cd1c0fc0a128af79c066dc54c3f437e05ba275ee61dbf9cbdb2928183738139397b6189ae738fef2b9b609a6dd8e0b0d0e20b243db936c029cdc2220af2c0e1a5e4aa41a006af458957e2b1178d27156ef0cb717e16d54025d97f43e9916fb240fb85f7d579462fa0ac76c76256843750bf1ccdfeb76c8c47886477644d5ec3235628adf6a09c8488bfa5036de717908151a6b585f273dd9fb5332b9af76e8fbfa91eaf4311816dde27c5c44f2fd06cc2204d7147f77ba6b16a2a5fca470023614729538bee6b3cb07264713832aec161550eb501906802215223acc2564ad1f98bb5934924eb56d383fc7598be45c89d995281c0efb0d206d29a6d25a10a48fe235332379c5ca69e83599faa677dd20823f5c84a961255eca5d4871d54ca1df0774aa117b0f42cd6e9fda7e8a48a53923c5f94043353544e644b5a6562e5cef9fc2bd2fcfcce3323335cf7fe7c4d83c1b7f839c4790192d3ba9aa9f32093aa8ee7cbe708059d538dc663cca1b825331aa836754a0d13de63bf65b6e2044dcdf041f1a0c5a9c3c38fe74cf576d451c23eaa519db32ef9e039bd848a194c3b5e41a55642dc283ddbd73d1dd97ae6951de18ad89d005007fae7e88bc7a3cce8b7ccc49603a0db67c76d58a28d4b77aa7460801e34377d0c5e4606c2e25b0203010001a350304e301d0603551d0e04160414f35f7b7549e37841396a20b67c6b4c5cc93d5841301f0603551d23041830168014f35f7b7549e37841396a20b67c6b4c5cc93d5841300c0603551d13040530030101ff300d06092a864886f70d01010505000382020100771cfea34579a97520d8c2423d68ecd07891f8c7f1c38bf3cd30ea9d3637bfc5d373532ec7656558bef4950646750c6fe085c52d9ffc09e66ebfa2b067de7727cb381d2b25db58b9c2197fd5eb53e020f429b86a3b1f37a07a761a66a5b3ecd797c46695a37ff2d47c54126be6bd28a2a103357227c6b73f7f689b09b48927e6e9a52267a728a115d4bcbb477533dc28f3fc57da735a3ec54fbc36990b17febb7e46b324208c1fa7425a0cba48bdc0381ea8285215261c3c483f2fa6d1da0dba4949107189f32d728a7ff395d43430af3b8ce4be5075bcf67d66661941dc8be37340f8f9282b2d2aadd69065932ad49769f8bc7fc9e5f6e79ff392420ba78de3172778e2b67e4df18440dd561e5a7844c8efa06c0e5f5b8695fa069114a00518fb4c19f9d7855831b5eeebced14b8598daffa49f2dcf505bff6417d84b28e83599d4e0371ef64b2d82ffa068a31044f7322fee2f654ec357c9c121f3458a509728c37f5673412ad0d5e76aa6b4eb1582181a8be404d3dc35e71edd83ee388087d6147c4d86f1cacacface0104df1f4b100c2ceb1be4d1851c4f31e7c4409262185878f23cceb30790143f0d5bd80d2c0ed4260aaa312597d950aaf3c8bcfc812d9a56e8d160dd772a494743710a7e478a046d8a5d505ee6b8cc37fec09dfd4cb57c6c4d8e8ef2a22e1d9e50957852633138d722253e51ba77b0069d38812207a"
)
var tests = []struct {
leaf LeafEntry
wantCert bool
wantPrecert bool
wantErr string
}{
{
leaf: LeafEntry{},
wantErr: "failed to unmarshal",
},
{
leaf: LeafEntry{
// {version + leaf_type + timestamp + entry_type + len + cert + exts}
LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts),
ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA),
},
wantCert: true,
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts + "ff"),
ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA),
},
wantErr: "trailing data",
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts),
ExtraData: dh("000ba3" + "0005cc" + leafCA + "0005d1" + rootCA + "00"),
},
wantErr: "trailing data",
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "0000" + "0004f3" + leafDER + noExts),
},
wantErr: "failed to unmarshal",
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc2b99c8" + "8000" + "0004f3" + leafDER + noExts),
},
wantErr: "unknown entry type",
},
{
leaf: LeafEntry{
// version + leaf_type + timestamp + entry_type + key_hash + tbs + exts
LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts),
ExtraData: dh("000508" + precertDER +
("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot)),
},
wantPrecert: true,
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts),
ExtraData: dh("000508" + precertDER +
("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot) + "ff"),
},
wantErr: "trailing data",
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts + "ff"),
ExtraData: dh("000508" + precertDER +
("000ba3" + "0005cc" + precertCA + "0005d1" + precertRoot)),
},
wantErr: "trailing data",
},
{
leaf: LeafEntry{
LeafInput: dh("00" + "00" + "0000015dcc997890" + "0001" + issuerKeyHash + precertTBS + noExts),
},
wantErr: "failed to unmarshal",
},
}
for i, test := range tests {
got, err := LogEntryFromLeaf(int64(i), &test.leaf)
if err != nil {
if test.wantErr == "" {
t.Errorf("LogEntryFromLeaf(%d) = _, %v; want _, nil", i, err)
} else if !strings.Contains(err.Error(), test.wantErr) {
t.Errorf("LogEntryFromLeaf(%d) = _, %v; want _, err containing %q", i, err, test.wantErr)
}
} else if test.wantErr != "" {
t.Errorf("LogEntryFromLeaf(%d) = _, nil; want _, err containing %q", i, test.wantErr)
}
if gotCert := (got != nil && got.X509Cert != nil); gotCert != test.wantCert {
t.Errorf("LogEntryFromLeaf(%d).X509Cert = %v; want %v", i, gotCert, test.wantCert)
}
if gotPrecert := (got != nil && got.Precert != nil); gotPrecert != test.wantPrecert {
t.Errorf("LogEntryFromLeaf(%d).Precert = %v; want %v", i, gotPrecert, test.wantPrecert)
}
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2015 Google Inc. 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 ct
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha256"
"encoding/pem"
"flag"
"fmt"
"log"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
)
var allowVerificationWithNonCompliantKeys = flag.Bool("allow_verification_with_non_compliant_keys", false,
"Allow a SignatureVerifier to use keys which are technically non-compliant with RFC6962.")
// PublicKeyFromPEM parses a PEM formatted block and returns the public key contained within and any remaining unread bytes, or an error.
func PublicKeyFromPEM(b []byte) (crypto.PublicKey, SHA256Hash, []byte, error) {
p, rest := pem.Decode(b)
if p == nil {
return nil, [sha256.Size]byte{}, rest, fmt.Errorf("no PEM block found in %s", string(b))
}
k, err := x509.ParsePKIXPublicKey(p.Bytes)
return k, sha256.Sum256(p.Bytes), rest, err
}
// SignatureVerifier can verify signatures on SCTs and STHs
type SignatureVerifier struct {
pubKey crypto.PublicKey
}
// NewSignatureVerifier creates a new SignatureVerifier using the passed in PublicKey.
func NewSignatureVerifier(pk crypto.PublicKey) (*SignatureVerifier, error) {
switch pkType := pk.(type) {
case *rsa.PublicKey:
if pkType.N.BitLen() < 2048 {
e := fmt.Errorf("public key is RSA with < 2048 bits (size:%d)", pkType.N.BitLen())
if !(*allowVerificationWithNonCompliantKeys) {
return nil, e
}
log.Printf("WARNING: %v", e)
}
case *ecdsa.PublicKey:
params := *(pkType.Params())
if params != *elliptic.P256().Params() {
e := fmt.Errorf("public is ECDSA, but not on the P256 curve")
if !(*allowVerificationWithNonCompliantKeys) {
return nil, e
}
log.Printf("WARNING: %v", e)
}
default:
return nil, fmt.Errorf("Unsupported public key type %v", pkType)
}
return &SignatureVerifier{
pubKey: pk,
}, nil
}
// VerifySignature verifies the given signature sig matches the data.
func (s SignatureVerifier) VerifySignature(data []byte, sig tls.DigitallySigned) error {
return tls.VerifySignature(s.pubKey, data, sig)
}
// VerifySCTSignature verifies that the SCT's signature is valid for the given LogEntry.
func (s SignatureVerifier) VerifySCTSignature(sct SignedCertificateTimestamp, entry LogEntry) error {
sctData, err := SerializeSCTSignatureInput(sct, entry)
if err != nil {
return err
}
return s.VerifySignature(sctData, tls.DigitallySigned(sct.Signature))
}
// VerifySTHSignature verifies that the STH's signature is valid.
func (s SignatureVerifier) VerifySTHSignature(sth SignedTreeHead) error {
sthData, err := SerializeSTHSignatureInput(sth)
if err != nil {
return err
}
return s.VerifySignature(sthData, tls.DigitallySigned(sth.TreeHeadSignature))
}

View File

@ -0,0 +1,493 @@
// Copyright 2015 Google Inc. 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 ct
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/hex"
mrand "math/rand"
"testing"
"github.com/google/certificate-transparency-go/tls"
)
const (
sigTestDERCertString = "308202ca30820233a003020102020102300d06092a864886f70d01010505003055310b300" +
"906035504061302474231243022060355040a131b4365727469666963617465205472616e" +
"73706172656e6379204341310e300c0603550408130557616c65733110300e06035504071" +
"3074572772057656e301e170d3132303630313030303030305a170d323230363031303030" +
"3030305a3052310b30090603550406130247423121301f060355040a13184365727469666" +
"963617465205472616e73706172656e6379310e300c0603550408130557616c6573311030" +
"0e060355040713074572772057656e30819f300d06092a864886f70d010101050003818d0" +
"030818902818100b8742267898b99ba6bfd6e6f7ada8e54337f58feb7227c46248437ba5f" +
"89b007cbe1ecb4545b38ed23fddbf6b9742cafb638157f68184776a1b38ab39318ddd7344" +
"89b4d750117cd83a220a7b52f295d1e18571469a581c23c68c57d973761d9787a091fb586" +
"4936b166535e21b427e3c6d690b2e91a87f36b7ec26f59ce53b50203010001a381ac3081a" +
"9301d0603551d0e041604141184e1187c87956dffc31dd0521ff564efbeae8d307d060355" +
"1d23047630748014a3b8d89ba2690dfb48bbbf87c1039ddce56256c6a159a4573055310b3" +
"00906035504061302474231243022060355040a131b436572746966696361746520547261" +
"6e73706172656e6379204341310e300c0603550408130557616c65733110300e060355040" +
"713074572772057656e82010030090603551d1304023000300d06092a864886f70d010105" +
"050003818100292ecf6e46c7a0bcd69051739277710385363341c0a9049637279707ae23c" +
"c5128a4bdea0d480ed0206b39e3a77a2b0c49b0271f4140ab75c1de57aba498e09459b479" +
"cf92a4d5d5dd5cbe3f0a11e25f04078df88fc388b61b867a8de46216c0e17c31fc7d8003e" +
"cc37be22292f84242ab87fb08bd4dfa3c1b9ce4d3ee6667da"
sigTestSCTTimestamp = 1348589665525
sigTestCertSCTSignatureEC = "0403" + "0048" +
"3046022100d3f7690e7ee80d9988a54a3821056393e9eb0c686ad67fbae3686c888fb1a3c" +
"e022100f9a51c6065bbba7ad7116a31bea1c31dbed6a921e1df02e4b403757fae3254ae"
sigTestEC256PrivateKeyPEM = "-----BEGIN EC PRIVATE KEY-----\n" +
"MHcCAQEEIG8QAquNnarN6Ik2cMIZtPBugh9wNRe0e309MCmDfBGuoAoGCCqGSM49\n" +
"AwEHoUQDQgAES0AfBkjr7b8b19p5Gk8plSAN16wWXZyhYsH6FMCEUK60t7pem/ck\n" +
"oPX8hupuaiJzJS0ZQ0SEoJGlFxkUFwft5g==\n" +
"-----END EC PRIVATE KEY-----\n"
sigTestEC256PublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES0AfBkjr7b8b19p5Gk8plSAN16wW\n" +
"XZyhYsH6FMCEUK60t7pem/ckoPX8hupuaiJzJS0ZQ0SEoJGlFxkUFwft5g==\n" +
"-----END PUBLIC KEY-----\n"
sigTestEC256PublicKey2PEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHT\n" +
"DM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==\n" +
"-----END PUBLIC KEY-----\n"
sigTestRSAPrivateKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEpAIBAAKCAQEAxy7llbig9kL0wo5AyV1FhmJLvWTWxzAMwGdhG1h1CqQpaWut\n" +
"XGI9WKRDJSZ/9dr9vgvqdRX2QsnUdJbJ3cz5Z1ie/RdT/mSVO7ZEqvJS93PIHnqu\n" +
"FZXxNnIerGnQ7guC+Zm9BlQ2DIhYpnvVRRVyD/D8KT92R7qOu3JACduoMrF1synk\n" +
"nL8rb8lZvCej8tbhJ38yibMWTmkxsFS+a29Xqk8pkhgwIwvUZqcMaqZo+4/iCuKL\n" +
"bVc85V98SvbcnmsX3gqeQnyRtxlctlclcbvHmJt5U+3yF1UtcuiyZf1gjcAqnOgv\n" +
"ZZYzsodXi0KGV7NRQhTPvwH0C8In2qL+v4qWAQIDAQABAoIBAQCdyqsaNw9cx6I6\n" +
"1pLAcuF3GjvCKDZ1ybzwV3V4QlVGPtKHr0PBIhpTNJ30ulE4pWnKuoncg695LYbf\n" +
"be0xhwY1NuGMwoRJzcjjavtvKVVMry5j5vAuLYDPjwx5rcJUMk5qCb7TWrcOqp0A\n" +
"Fq3XcqvPsSsyShIbtNEJ8fKFXLwcm07bGDgOacrXieP/nL2Hh6joeAJLgnKAOtU5\n" +
"qw6fdweYGThfhdCwaBq0WSaxj6nMG3Q40bHurvdOAtU1GF2a27BGsnfKFyKlvk8+\n" +
"K7tCc4oXo4WWEUuOwu6SmB1kYIZLf258B0PFQJwN8OA3Mbek+F4Bm3DzWe1aLS5L\n" +
"wpOYxrq5AoGBAO0sGq+ic+9K81FvBBUYXiFtt9rv3nU9jZhjqpfvdqRs2KXt8ldz\n" +
"2M+JCRFHt8rLDEutK/NZuZcq3wAXS3EeMIp33QZ7Yuj5LeG9eD0asX8yq51Toua3\n" +
"gRDbiR00Vz/3BINM8JufN/sPLoUiuAV5mlOTktZ8+z7ixO4ravMB1Z9HAoGBANb+\n" +
"w+1Hre8+4JEnl3yRh1UNDmbhCc2tRxCD4QJyb9qaOl2IK1QXuDcwD8owdenwOrAi\n" +
"I5yKx7y4oKNfdSrP2wlAGS/GAEL5f+JhLtv2cNoKNxMXNRgYfJAQeMKBjINdECia\n" +
"G89lbPVCm+F3guzrO70giA4617GFSEA31rRC1BR3AoGBAKcQLiwRrsCcdxChtqp1\n" +
"Y7kAZEXgOT80gI0bh4tGrrfbxC/9kHtxqwNlb/GwJxK+PIcCELd2OHj3ReX2grnH\n" +
"nkGrdRGf0GhzPZKJuCyypN0IgEJuK42BLXUGb2sW926jPZaPl9zHJtO+OfKmJiIV\n" +
"KlQ8224i04fUjQuHoepTHHr5AoGAS8AZ4lmWFCywTRSJEG/qIfJmt6LkpF5AIraE\n" +
"qisN9BTRKbFXqtpsoq1BcvjeIt3sn7B3oalYNMtMdiOlEb+Iqlq2RRnbb72e7HFX\n" +
"ZFMRchGVVBmiMGo4QT48fjPNAV/h2Jxr3ggbetLMP4WvULCVLM7wgSsEYlzWlyHV\n" +
"eU/uj4MCgYADGpY3Q3ueB23eTTnAMtESwmAQvjTBOPKVpaV/ohHb8BdzAjwcgEUA\n" +
"wB1be/bHCrLbW09Pi3HVp0I0x0mBAYoUP2NRYKCYlhs28cu+ygB4nsy+YZPg00+E\n" +
"ByqqrQ0zuN82ytXzRFHmh2Hb2O+HOj6aJjgVuj/rR7aifIt8scSAhg==\n" +
"-----END RSA PRIVATE KEY-----\n"
sigTestRSAPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxy7llbig9kL0wo5AyV1F\n" +
"hmJLvWTWxzAMwGdhG1h1CqQpaWutXGI9WKRDJSZ/9dr9vgvqdRX2QsnUdJbJ3cz5\n" +
"Z1ie/RdT/mSVO7ZEqvJS93PIHnquFZXxNnIerGnQ7guC+Zm9BlQ2DIhYpnvVRRVy\n" +
"D/D8KT92R7qOu3JACduoMrF1synknL8rb8lZvCej8tbhJ38yibMWTmkxsFS+a29X\n" +
"qk8pkhgwIwvUZqcMaqZo+4/iCuKLbVc85V98SvbcnmsX3gqeQnyRtxlctlclcbvH\n" +
"mJt5U+3yF1UtcuiyZf1gjcAqnOgvZZYzsodXi0KGV7NRQhTPvwH0C8In2qL+v4qW\n" +
"AQIDAQAB\n" +
"-----END PUBLIC KEY-----\n"
sigTestCertSCTSignatureRSA = "0401" + "0100" +
"6bc1fecfe9052036e31278cd7eded90d000b127f2b657831baf5ecb31ee3" +
"c17497abd9562df6319928a36df0ab1a1a917b3f4530e1ca0000ae6c4a0c" +
"0efada7df83beb95da8eea98f1a27c70afa1ccaa7a0245e1db785b1c0d9f" +
"ee307e926e14bed1eac0d01c34939e659360432a9552c02b89c3ef3c44aa" +
"22fc31f2444522975ee83989dd7af1ab05b91bbf0985ca4d04245b68a683" +
"01d300f0c976ce13d58618dad1b49c0ec5cdc4352016823fc88c479ef214" +
"76c5f19923af207dbb1b2cff72d4e1e5ee77dd420b85d0f9dcc30a0f617c" +
"2d3c916eb77f167323500d1b53dc4253321a106e441af343cf2f68630873" +
"abd43ca52629c586107eb7eb85f2c3ee"
sigTestCertSCTSignatureUnsupportedSignatureAlgorithm = "0402" + "0000"
sigTestCertSCTSignatureUnsupportedHashAlgorithm = "0303" + "0000"
// Some time in September 2012.
sigTestDefaultSTHTimestamp = 1348589667204
sigTestDefaultTreeSize = 42
// *Some* hash that we pretend is a valid root hash.
sigTestDefaultRootHash = "18041bd4665083001fba8c5411d2d748e8abbfdcdfd9218cb02b68a78e7d4c23"
sigTestDefaultSTHSerialized = "000100000139fe354384000000000000002a18041bd4665083001fba8c5411d2d748e8abb" +
"fdcdfd9218cb02b68a78e7d4c23"
sigTestDefaultSTHSignature = "0403" + "0048" +
"3046022100befd8060563763a5e49ba53e6443c13f7624fd6403178113736e16012aca983" +
"e022100f572568dbfe9a86490eb915c4ee16ad5ecd708fed35ed4e5cd1b2c3f087b4130"
sigTestKeyIDEC = "b69d879e3f2c4402556dcda2f6b2e02ff6b6df4789c53000e14f4b125ae847aa"
sigTestKeyIDRSA = "b853f84c71a7aa5f23905ba5340f183af927c330c7ce590ba1524981c4ec4358"
)
func mustDehex(t *testing.T, h string) []byte {
t.Helper()
r, err := hex.DecodeString(h)
if err != nil {
t.Fatalf("Failed to decode hex string (%s): %v", h, err)
}
return r
}
func sigTestSCTWithSignature(t *testing.T, sig, keyID string) SignedCertificateTimestamp {
t.Helper()
var ds DigitallySigned
if _, err := tls.Unmarshal(mustDehex(t, sig), &ds); err != nil {
t.Fatalf("Failed to unmarshal sigTestCertSCTSignatureEC: %v", err)
}
var id LogID
copy(id.KeyID[:], mustDehex(t, keyID))
return SignedCertificateTimestamp{
SCTVersion: V1,
LogID: id,
Timestamp: sigTestSCTTimestamp,
Signature: ds,
}
}
func sigTestSCTEC(t *testing.T) SignedCertificateTimestamp {
t.Helper()
return sigTestSCTWithSignature(t, sigTestCertSCTSignatureEC, sigTestKeyIDEC)
}
func sigTestSCTRSA(t *testing.T) SignedCertificateTimestamp {
t.Helper()
return sigTestSCTWithSignature(t, sigTestCertSCTSignatureRSA, sigTestKeyIDEC)
}
func sigTestECPublicKey(t *testing.T) crypto.PublicKey {
t.Helper()
pk, _, _, err := PublicKeyFromPEM([]byte(sigTestEC256PublicKeyPEM))
if err != nil {
t.Fatalf("Failed to parse sigTestEC256PublicKey: %v", err)
}
return pk
}
func sigTestECPublicKey2(t *testing.T) crypto.PublicKey {
t.Helper()
pk, _, _, err := PublicKeyFromPEM([]byte(sigTestEC256PublicKey2PEM))
if err != nil {
t.Fatalf("Failed to parse sigTestEC256PublicKey2: %v", err)
}
return pk
}
func sigTestRSAPublicKey(t *testing.T) crypto.PublicKey {
t.Helper()
pk, _, _, err := PublicKeyFromPEM([]byte(sigTestRSAPublicKeyPEM))
if err != nil {
t.Fatalf("Failed to parse sigTestRSAPublicKey: %v", err)
}
return pk
}
func sigTestCertLogEntry(t *testing.T) LogEntry {
t.Helper()
return LogEntry{
Index: 0,
Leaf: MerkleTreeLeaf{
Version: V1,
LeafType: TimestampedEntryLeafType,
TimestampedEntry: &TimestampedEntry{
Timestamp: sigTestSCTTimestamp,
EntryType: X509LogEntryType,
X509Entry: &ASN1Cert{Data: mustDehex(t, sigTestDERCertString)},
},
},
}
}
func sigTestDefaultSTH(t *testing.T) SignedTreeHead {
t.Helper()
var ds DigitallySigned
if _, err := tls.Unmarshal(mustDehex(t, sigTestDefaultSTHSignature), &ds); err != nil {
t.Fatalf("Failed to unmarshal sigTestCertSCTSignatureEC: %v", err)
}
var rootHash SHA256Hash
copy(rootHash[:], mustDehex(t, sigTestDefaultRootHash))
return SignedTreeHead{
Version: V1,
Timestamp: sigTestDefaultSTHTimestamp,
TreeSize: sigTestDefaultTreeSize,
SHA256RootHash: rootHash,
TreeHeadSignature: ds,
}
}
func mustCreateSignatureVerifier(t *testing.T, pk crypto.PublicKey) SignatureVerifier {
t.Helper()
sv, err := NewSignatureVerifier(pk)
if err != nil {
t.Fatalf("Failed to create SignatureVerifier: %v", err)
}
return *sv
}
func corruptByteAt(b []byte, pos int) {
b[pos] ^= byte(mrand.Intn(255) + 1)
}
func corruptBytes(b []byte) {
corruptByteAt(b, mrand.Intn(len(b)))
}
func expectVerifySCTToFail(t *testing.T, sv SignatureVerifier, sct SignedCertificateTimestamp, msg string) {
t.Helper()
if err := sv.VerifySCTSignature(sct, sigTestCertLogEntry(t)); err == nil {
t.Fatal(msg)
}
}
func TestVerifySCTSignatureEC(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
if err := v.VerifySCTSignature(sigTestSCTEC(t), sigTestCertLogEntry(t)); err != nil {
t.Fatalf("Failed to verify signature on SCT: %v", err)
}
}
func TestVerifySCTSignatureRSA(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t))
if err := v.VerifySCTSignature(sigTestSCTRSA(t), sigTestCertLogEntry(t)); err != nil {
t.Fatalf("Failed to verify signature on SCT: %v", err)
}
}
func TestVerifySCTSignatureFailsForMismatchedSignatureAlgorithm(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
expectVerifySCTToFail(t, v, sigTestSCTRSA(t), "Successfully verified with mismatched signature algorithm")
}
func TestVerifySCTSignatureFailsForUnknownSignatureAlgorithm(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
expectVerifySCTToFail(t, v, sigTestSCTWithSignature(t, sigTestCertSCTSignatureUnsupportedSignatureAlgorithm, sigTestKeyIDEC),
"Successfully verified signature with unsupported signature algorithm")
}
func TestVerifySCTSignatureFailsForUnknownHashAlgorithm(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
expectVerifySCTToFail(t, v, sigTestSCTWithSignature(t, sigTestCertSCTSignatureUnsupportedHashAlgorithm, sigTestKeyIDEC),
"Successfully verified signature with unsupported hash algorithm")
}
func testVerifySCTSignatureFailsForIncorrectLeafBytes(t *testing.T, sct SignedCertificateTimestamp, sv SignatureVerifier) {
t.Helper()
entry := sigTestCertLogEntry(t)
for i := range entry.Leaf.TimestampedEntry.X509Entry.Data {
old := entry.Leaf.TimestampedEntry.X509Entry.Data[i]
corruptByteAt(entry.Leaf.TimestampedEntry.X509Entry.Data, i)
if err := sv.VerifySCTSignature(sct, entry); err == nil {
t.Fatalf("Incorrectly verfied signature over corrupted leaf data, uncovered byte at %d?", i)
}
entry.Leaf.TimestampedEntry.X509Entry.Data[i] = old
}
// Ensure we were only corrupting one byte at a time, should be correct again now.
if err := sv.VerifySCTSignature(sct, entry); err != nil {
t.Fatalf("Input data appears to still be corrupt, bug? %v", err)
}
}
func testVerifySCTSignatureFailsForIncorrectSignature(t *testing.T, sct SignedCertificateTimestamp, sv SignatureVerifier) {
t.Helper()
corruptBytes(sct.Signature.Signature)
expectVerifySCTToFail(t, sv, sct, "Incorrectly verified corrupt signature")
}
func TestVerifySCTSignatureECFailsForIncorrectLeafBytes(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
testVerifySCTSignatureFailsForIncorrectLeafBytes(t, sigTestSCTEC(t), v)
}
func TestVerifySCTSignatureECFailsForIncorrectTimestamp(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sct := sigTestSCTEC(t)
sct.Timestamp++
expectVerifySCTToFail(t, v, sct, "Incorrectly verified signature with incorrect SCT timestamp.")
}
func TestVerifySCTSignatureECFailsForIncorrectVersion(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sct := sigTestSCTEC(t)
sct.SCTVersion++
expectVerifySCTToFail(t, v, sct, "Incorrectly verified signature with incorrect SCT Version.")
}
func TestVerifySCTSignatureECFailsForIncorrectSignature(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v)
}
func TestVerifySCTSignatureRSAFailsForIncorrectLeafBytes(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t))
testVerifySCTSignatureFailsForIncorrectLeafBytes(t, sigTestSCTRSA(t), v)
}
func TestVerifySCTSignatureRSAFailsForIncorrectSignature(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t))
testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTRSA(t), v)
}
func TestVerifySCTSignatureFailsForSignatureCreatedWithDifferentAlgorithm(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t))
testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v)
}
func TestVerifySCTSignatureFailsForSignatureCreatedWithDifferentKey(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey2(t))
testVerifySCTSignatureFailsForIncorrectSignature(t, sigTestSCTEC(t), v)
}
func expectVerifySTHToPass(t *testing.T, v SignatureVerifier, sth SignedTreeHead) {
t.Helper()
if err := v.VerifySTHSignature(sth); err != nil {
t.Fatalf("Incorrectly failed to verify STH signature: %v", err)
}
}
func expectVerifySTHToFail(t *testing.T, v SignatureVerifier, sth SignedTreeHead) {
t.Helper()
if err := v.VerifySTHSignature(sth); err == nil {
t.Fatal("Incorrectly verified STH signature")
}
}
func TestVerifyValidSTH(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
expectVerifySTHToPass(t, v, sth)
}
func TestVerifySTHCatchesCorruptSignature(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
corruptBytes(sth.TreeHeadSignature.Signature)
expectVerifySTHToFail(t, v, sth)
}
func TestVerifySTHCatchesCorruptRootHash(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
for i := range sth.SHA256RootHash {
old := sth.SHA256RootHash[i]
corruptByteAt(sth.SHA256RootHash[:], i)
expectVerifySTHToFail(t, v, sth)
sth.SHA256RootHash[i] = old
}
// ensure we were only testing one corrupt byte at a time - should be correct again now.
expectVerifySTHToPass(t, v, sth)
}
func TestVerifySTHCatchesCorruptTimestamp(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
sth.Timestamp++
expectVerifySTHToFail(t, v, sth)
}
func TestVerifySTHCatchesCorruptVersion(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
sth.Version++
expectVerifySTHToFail(t, v, sth)
}
func TestVerifySTHCatchesCorruptTreeSize(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey(t))
sth := sigTestDefaultSTH(t)
sth.TreeSize++
expectVerifySTHToFail(t, v, sth)
}
func TestVerifySTHFailsToVerifyForKeyWithDifferentAlgorithm(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestRSAPublicKey(t))
sth := sigTestDefaultSTH(t)
expectVerifySTHToFail(t, v, sth)
}
func TestVerifySTHFailsToVerifyForDifferentKey(t *testing.T) {
v := mustCreateSignatureVerifier(t, sigTestECPublicKey2(t))
sth := sigTestDefaultSTH(t)
expectVerifySTHToFail(t, v, sth)
}
func TestNewSignatureVerifierFailsWithUnsupportedKeyType(t *testing.T) {
var k dsa.PrivateKey
if err := dsa.GenerateParameters(&k.Parameters, rand.Reader, dsa.L1024N160); err != nil {
t.Fatalf("Failed to generate DSA key parameters: %v", err)
}
if err := dsa.GenerateKey(&k, rand.Reader); err != nil {
t.Fatalf("Failed to generate DSA key: %v", err)
}
if _, err := NewSignatureVerifier(k); err == nil {
t.Fatal("Creating a SignatureVerifier with a DSA key unexpectedly succeeded")
}
}
func TestNewSignatureVerifierFailsWithBadKeyParametersForEC(t *testing.T) {
k, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("Failed to generate ECDSA key on P224: %v", err)
}
if _, err := NewSignatureVerifier(k); err == nil {
t.Fatal("Incorrectly created new SignatureVerifier with EC P224 key.")
}
}
func TestNewSignatureVerifierFailsWithBadKeyParametersForRSA(t *testing.T) {
k, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatalf("Failed to generate 1024 bit RSA key: %v", err)
}
if _, err := NewSignatureVerifier(k); err == nil {
t.Fatal("Incorrectly created new SignatureVerifier with 1024 bit RSA key.")
}
}
func TestWillAllowNonCompliantECKeyWithOverride(t *testing.T) {
*allowVerificationWithNonCompliantKeys = true
k, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
t.Fatalf("Failed to generate EC key on P224: %v", err)
}
if _, err := NewSignatureVerifier(k.Public()); err != nil {
t.Fatalf("Incorrectly disallowed P224 EC key with override set: %v", err)
}
}
func TestWillAllowNonCompliantRSAKeyWithOverride(t *testing.T) {
*allowVerificationWithNonCompliantKeys = true
k, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatalf("Failed to generate 1024 bit RSA key: %v", err)
}
if _, err := NewSignatureVerifier(k.Public()); err != nil {
t.Fatalf("Incorrectly disallowed 1024 bit RSA key with override set: %v", err)
}
}

View File

@ -0,0 +1,30 @@
shard {
uri: "https://ct.googleapis.com/logs/argon2017"
public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004Tm|\211\335\352\235\360\272_\364m`z7O\002%\277\034\366o\205\256\257\025\337in\355\333\251\232)\227\362\231v\036\3463F\036'\364\276p\335Y\327\272\317\376\320r\216\260W\017\2357\211b\243"
not_after_start:<seconds:1483228800>
not_after_limit:<seconds:1514764800>
}
shard {
uri: "https://ct.googleapis.com/logs/argon2018"
public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\322\000U\005\255\325G\264\031\273\315\225\373)\327X=x$\315\316F\235\3732\324qN`\002%^Y>\327\324\003\270mChh~\350\240e\013>nqY\2227\276\251\350\361\243+\344\331\rUh"
not_after_start:<seconds:1514764800>
not_after_limit:<seconds:1546300800>
}
shard {
uri: "https://ct.googleapis.com/logs/argon2019"
public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004#s\020\233\341\363^\366\230ki\225\226\020x\316I\333\264\004\374q,Z\222`h%\300J\032\241\260a-\033\207\024\251\272\360\0013Y\035\0050\351B\025\347U\327*\370\264\242\272E\311F\221\207V"
not_after_start:<seconds:1546300800>
not_after_limit:<seconds:1577836800>
}
shard {
uri: "https://ct.googleapis.com/logs/argon2020"
public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004\351<v\247\\\212c\2155\344\334\210b\367k\223~\236\263K\200s\\\300\340\364>LdX\373vcQ2\030c\325\262\273\355\352\377^;$n/5R\213\2645\232\255\234\025\250i \352P\030\314"
not_after_start:<seconds:1577836800>
not_after_limit:<seconds:1609459200>
}
shard {
uri: "https://ct.googleapis.com/logs/argon2021"
public_key_der:"0Y0\023\006\007*\206H\316=\002\001\006\010*\206H\316=\003\001\007\003B\000\004M\340fd\352\363d\2528\305\211-\307\330\010\331\310Dq\355\334\303\373[\257\234d\241\tf\204\035|h\247\354\304?\214\234\202\340\030\331t\024\351\264y\201\242\224Ub\363\234\013D\203\241+\311q+"
not_after_start:<seconds:1609459200>
not_after_limit:<seconds:1640995200>
}

View File

@ -0,0 +1,235 @@
// Copyright 2016 Google Inc. 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 testdata
import (
"encoding/hex"
)
const (
// CACertPEM is a CA cert:
// Certificate:
// Data:
// Version: 3 (0x2)
// Serial Number: 0 (0x0)
// 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 CA, ST=Wales, L=Erw Wen
// Subject Public Key Info:
// Public Key Algorithm: rsaEncryption
// Public-Key: (1024 bit)
// Modulus:
// 00:d5:8a:68:53:62:10:a2:71:19:93:6e:77:83:21:
// 18:1c:2a:40:13:c6:d0:7b:8c:76:eb:91:57:d3:d0:
// fb:4b:3b:51:6e:ce:cb:d1:c9:8d:91:c5:2f:74:3f:
// ab:63:5d:55:09:9c:d1:3a:ba:f3:1a:e5:41:44:24:
// 51:a7:4c:78:16:f2:24:3c:f8:48:cf:28:31:cc:e6:
// 7b:a0:4a:5a:23:81:9f:3c:ba:37:e6:24:d9:c3:bd:
// b2:99:b8:39:dd:fe:26:31:d2:cb:3a:84:fc:7b:b2:
// b5:c5:2f:cf:c1:4f:ff:40:6f:5c:d4:46:69:cb:b2:
// f7:cf:df:86:fb:6a:b9:d1:b1
// Exponent: 65537 (0x10001)
// X509v3 extensions:
// X509v3 Subject Key Identifier:
// 5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55
// 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:TRUE
// Signature Algorithm: sha1WithRSAEncryption
// 06:08:cc:4a:6d:64:f2:20:5e:14:6c:04:b2:76:f9:2b:0e:fa:
// 94:a5:da:f2:3a:fc:38:06:60:6d:39:90:d0:a1:ea:23:3d:40:
// 29:57:69:46:3b:04:66:61:e7:fa:1d:17:99:15:20:9a:ea:2e:
// 0a:77:51:76:41:12:27:d7:c0:03:07:c7:47:0e:61:58:4f:d7:
// 33:42:24:72:7f:51:d6:90:bc:47:a9:df:35:4d:b0:f6:eb:25:
// 95:5d:e1:89:3c:4d:d5:20:2b:24:a2:f3:e4:40:d2:74:b5:4e:
// 1b:d3:76:26:9c:a9:62:89:b7:6e:ca:a4:10:90:e1:4f:3b:0a:
// 94:2e
CACertPEM = "-----BEGIN CERTIFICATE-----\n" +
"MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" +
"MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" +
"YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" +
"MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu\n" +
"c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf\n" +
"MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7\n" +
"jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP\n" +
"KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL\n" +
"svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk\n" +
"tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG\n" +
"A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO\n" +
"MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB\n" +
"/zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt\n" +
"OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy\n" +
"f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP\n" +
"OwqULg==\n" +
"-----END CERTIFICATE-----"
// TestCertPEM is a leaf certificate signed by CACertPEM.
// Certificate:
// Data:
// Version: 3 (0x2)
// Serial Number: 6 (0x6)
// 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:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4:
// 19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04:
// 16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34:
// 1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56:
// 46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc:
// a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02:
// ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d:
// d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65:
// b1:a3:7a:8c:67:62:53:dc:c1
// Exponent: 65537 (0x10001)
// X509v3 extensions:
// X509v3 Subject Key Identifier:
// 6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2
// 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
// Signature Algorithm: sha1WithRSAEncryption
// 17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2:
// 70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae:
// fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f:
// 71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5:
// 06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58:
// 2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71:
// 90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da:
// c0:e6
TestCertPEM = "-----BEGIN CERTIFICATE-----\n" +
"MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" +
"MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" +
"YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" +
"MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" +
"c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" +
"CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ\n" +
"4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx\n" +
"/KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj\n" +
"eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY\n" +
"4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE\n" +
"BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG\n" +
"A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq\n" +
"hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6\n" +
"n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah\n" +
"ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g==\n" +
"-----END CERTIFICATE-----\n"
// TestPreCertPEM is a pre-certificate signed by CACertPEM.
// 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
// 1.3.6.1.4.1.11129.2.4.3: critical
// ..
// Signature Algorithm: sha1WithRSAEncryption
// 02:a1:c3:9e:01:5a:f5:4d:ff:02:3c:33:60:87:5f:ff:34:37:
// 55:2f:1f:09:01:bd:c2:54:31:5f:33:72:b7:23:fb:15:fb:ce:
// cc:4d:f4:71:a0:ce:4d:8c:54:65:5d:84:87:97:fb:28:1e:3d:
// fa:bb:46:2d:2c:68:4b:05:6f:ea:7b:63:b4:70:ff:16:6e:32:
// d4:46:06:35:b3:d2:bc:6d:a8:24:9b:26:30:e7:1f:c3:4f:08:
// f2:3d:d4:ee:22:8f:8f:74:f6:3d:78:63:11:dd:0a:58:11:40:
// 5f:90:6c:ca:2c:2d:3e:eb:fc:81:99:64:eb:d8:cf:7c:08:86:
// 3f:be
TestPreCertPEM = "-----BEGIN CERTIFICATE-----\n" +
"MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" +
"MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" +
"YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw\n" +
"MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu\n" +
"c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G\n" +
"CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/\n" +
"BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk\n" +
"EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw\n" +
"FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3\n" +
"tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE\n" +
"BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG\n" +
"A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor\n" +
"BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg\n" +
"h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL\n" +
"BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf\n" +
"kGzKLC0+6/yBmWTr2M98CIY/vg==\n" +
"-----END CERTIFICATE-----\n"
)
var (
// TestCertProof is a TLS-encoded ct.SignedCertificateTimestamp corresponding
// to TestCertPEM.
TestCertProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" +
"640000013ddb27ded900000403004730450220606e10ae5c2d5a1b0aed49dc49" +
"37f48de71a4e9784e9c208dfbfe9ef536cf7f2022100beb29c72d7d06d61d06b" +
"db38a069469aa86fe12e18bb7cc45689a2c0187ef5a5")
// TestPreCertProof is a TLS-encoded ct.SignedCertificateTimestamp
// corresponding to TestPreCertPEM
TestPreCertProof = dh("00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7" +
"640000013ddb27df9300000403004730450220482f6751af35dba65436be1fd6" +
"640f3dbf9a41429495924530288fa3e5e23e06022100e4edc0db3ac572b1e2f5" +
"e8ab6a680653987dcf41027dfeffa105519d89edbf08")
)
func dh(h string) []byte {
r, err := hex.DecodeString(h)
if err != nil {
panic(err)
}
return r
}

View File

@ -0,0 +1,218 @@
// Copyright 2016 Google Inc. 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 testdata holds data and utility functions needed for various Go tests.
package testdata
import "encoding/hex"
// Hashes of text string 'abcd' with various algorithms; check with either "<alg>sum input" or "openssl dgst -<alg> input"
const (
// AbcdMD5 is 'abcd' hashed with MD5
AbcdMD5 = "e2fc714c4727ee9395f324cd2e7f331f"
// AbcdSHA1 is 'abcd' hashed with SHA1
AbcdSHA1 = "81fe8bfe87576c3ecb22426f8e57847382917acf"
// AbcdSHA224 is 'abcd' hashed with SHA224
AbcdSHA224 = "a76654d8e3550e9a2d67a0eeb6c67b220e5885eddd3fde135806e601"
// AbcdSHA256 is 'abcd' hashed with SHA256
AbcdSHA256 = "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589"
// AbcdSHA384 is 'abcd' hashed with SHA384
AbcdSHA384 = "1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b"
// AbcdSHA512 is 'abcd' hashed with SHA512
AbcdSHA512 = "d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f"
)
const (
// LogPublicKeyPEM is an ECDSA key copied from test/testdata/ct-server-key-public.pem
LogPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U\n" +
"yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==\n" +
"-----END PUBLIC KEY-----\n"
// RsaPrivateKeyPEM was generated with:
// openssl genpkey -algorithm RSA -out rsa.privkey.pem -pkeyopt rsa_keygen_bits:2048
RsaPrivateKeyPEM = "-----BEGIN PRIVATE KEY-----\n" +
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP8SJM5SBIbFpF\n" +
"dRAUEERo35E4iFXfT5SVAzBIqATkCpf3LfdFZgssPeliBdTtgdvT/CWC2m6aVXYZ\n" +
"eScAJOauTpGDyJ7OXj7Yhx1q6u28NuQleU3UQK5QYb7VOjeIse7oTh+THf5IK5T5\n" +
"esyh+ioybEYBXXMQetHc4YbXAq06crNT2BP+AxQ4z27xu4X5CKLwwVMa2xwMCFDC\n" +
"Y7mxQzEhFv8+HbOtM0IQMSSyh6+o0w4Yzt6i/TUOytZb1D7WHOEMpVTd1CMbXjN0\n" +
"tFZvoO0V+n0OV3oT1E5AvwujVguUJblQiapbL890/nZbBtP3xMoFLLhc7sX7N2vR\n" +
"1wLWsjOVAgMBAAECggEBAL+frUZDV86l20JqsFhs7T3f2MnKCahyg7AWciZif69O\n" +
"e+BTQa14bg9lNm8YhLIim1vs3vyJIqei3eR3mxMs7k/vI3XYKVBv1WZgjSF8QXzS\n" +
"8Mf/01MoD/sPOHby4T5dCpaVd89xMmV7lBubqHwUN1KkKJcVcPXc2Qy94C6/zrcu\n" +
"Vb7Q91ugTvvhyalWEN02M3tzHEn4cXrnMLWPmTgxb7YtTMRIbJFx2um7/W9G5D+1\n" +
"ZtKMjwt+P/yXynx3Sa555MjjJ1/LUyMSbQZoLE0SGOigz1VQnP8nFlpvXUHzktiA\n" +
"IBPBBQSsEoS8H3z6WThwY72O795/i2l5mf7EUUAa2/kCgYEA+W7tJPg69KWHKn6E\n" +
"gGZKIDTxvp3vRxjLedp7D7cu6bVPCBVIHrPQdmp/mneY6x7Air0a9gCVbh32iCrm\n" +
"UrSBCio4i6jtqtna2T1Gc0z63K3UrVQPR0Z6Swq1XDOI6/I23ibxPr12/XXWWYlA\n" +
"wkPwvewJVrjtMoP1dkAKVbyzImcCgYEA1WqS0xg7BL4iNGFPrrSme2blZOgga83x\n" +
"CtY9dnWAs0IvdxsuItCDaFVJm3oU18ohirEMzqZvbRlwMlUawpd75hCNWyVF4cRo\n" +
"+l1RvzOlC/7QXX/E+Kv7sTEUYH7hJyS46QhOtC1/ndAyObk9c2VNJmERfMwIHNm9\n" +
"BfSN8yrq1KMCgYEA94fCZPbGIvSFj4EgYv+fvhhscvrucsLDYniTuUPTlXAtLttX\n" +
"x8gwLuN/ID5hjarl7oi90bVAlZe8iOLx0M96YykFFmuc9/jcOsuZN2EEbq0/KocJ\n" +
"5nSldgT5d7dYwLWNB6bjr5x8Egm3nwEbN+4OYZt0pRA9q+zSUfg5iV4K8y8CgYA7\n" +
"S9YposTbJ3zXcuYx022iQc+gvsIrUdgUO7xuCm3M4KnRfRLPh4HLXk8KTNw3rKiv\n" +
"IUw+qo2xEW1T/sNlp7M8FANCfNOyy+CjF4ScDFxiPdVk9RgkQ5y1+b4ApaAnQRPD\n" +
"Y5SCiVW44lziHu7M/it2a2fxdbsXUQQtAGrkUltW4wKBgAPZyEGi4V2wryOswmkC\n" +
"I29TbC6ChnUOvqJfSfz536eIi41D1Ua2Y/VlAK1ud7K8ilr/M4VoRjHqbPivR1Uk\n" +
"RU7nO3cWRBuDBjgZWeAq5WFIMPbm9gRlaTECI6EFtAOI1GcHOsOBecd3PBiQNXvj\n" +
"YyGD1wSrHQwDcnxtP1rqEQDV\n" +
"-----END PRIVATE KEY-----\n"
// RsaPublicKeyPEM was generated from above with:
// openssl rsa -pubout -in rsa.privkey.pem -out rsa.pubkey.pem
RsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/EiTOUgSGxaRXUQFBBE\n" +
"aN+ROIhV30+UlQMwSKgE5AqX9y33RWYLLD3pYgXU7YHb0/wlgtpumlV2GXknACTm\n" +
"rk6Rg8iezl4+2IcdaurtvDbkJXlN1ECuUGG+1To3iLHu6E4fkx3+SCuU+XrMofoq\n" +
"MmxGAV1zEHrR3OGG1wKtOnKzU9gT/gMUOM9u8buF+Qii8MFTGtscDAhQwmO5sUMx\n" +
"IRb/Ph2zrTNCEDEksoevqNMOGM7eov01DsrWW9Q+1hzhDKVU3dQjG14zdLRWb6Dt\n" +
"Ffp9Dld6E9ROQL8Lo1YLlCW5UImqWy/PdP52WwbT98TKBSy4XO7F+zdr0dcC1rIz\n" +
"lQIDAQAB\n" +
"-----END PUBLIC KEY-----\n"
// RsaSignedAbcdHex was generated with:
// echo -n "abcd" > sign.input
// openssl sha256 -sign rsa.privkey.pem -out sign.rsa sign.input
// Verify with:
// openssl sha256 -verify rsa.pubkey.pem -signature sign.rsa sign.input
RsaSignedAbcdHex = "4692f17adad62324a38a786d9b05eb7facf95c277729bc23e8d4f4fe25c0ac0e" +
"9115f14ffd4dc6c00e6b2717677e96ed4da9e2226745db5c2d14ec8b4f96959e" +
"a43ca73e3ab801d4e76b0c0599c741b9701dd0c22b20cc7a294fccce97d2eda2" +
"72448830a19b0b1075f962d3bcf65922f81f4747dde1a2883ffce12941927af6" +
"25549ea7b4f745a95f5558822af434cc7471ea5a21cce656a7667d29bff3e6f2" +
"fb3d48bbee1a60116f0dd429d5691b008482e9ec42538bcf2692e1a685e836c8" +
"49316fb3c49b58bd5a0c33f0256b09d59af08a018fe64cd31051cb9e9a1d3fde" +
"e186bd80bfe304abbbec2ac18d3bee09465cb69f2638728aba4b9886a252768a"
// DsaPrivateKeyPEM was generated with:
// openssl dsaparam -out dsa.param.pem 2048; openssl gendsa dsa.param.pem -out dsa.privkey.pem
DsaPrivateKeyPEM = "-----BEGIN DSA PRIVATE KEY-----\n" +
"MIIDVwIBAAKCAQEA3Q72qQc0ZM2eUjvZ/XOROQTbhmjzuZdzY7kd+82n54NPeqVz\n" +
"+FWmk26INd7pUNbn/TjniN5+pC2pK+p9alFLE19bOvsY4x7Ugwrpd9sQIea3W4Q7\n" +
"H9PaEW+BooFzT/feC9TmnU0ynwoNhZB33Cr3k3PFt/69lmyrkd91wTpXrstrQurU\n" +
"h4baL5l6t2/JT3TxpKx6xe19hWqqFsBLu/j+kE2o2Aw1TVLHg4/74TJLfqAkWnCI\n" +
"u9QL0uGQnW80kRfJnT+tUJXrXMpg1HxK7CsDEW5xsAWF370Is0UPaP7jRru6FiSN\n" +
"ObcVhSqCIs+QJIWie9cRtwniwhoi4z6AfGeEYQIhAOkSzAsWja1fwB486/LCW+zv\n" +
"DJKiCGLTalimfawzHQHFAoIBAQDJvCujEZ/WeQFLlP/0zS+2DjqdaxnKj1xuXfoL\n" +
"LOocvMw06dnrQNmDu/nBQiYSfzf7zjwpnx+AVjBQssK0jXs0l7gw52FyCIztDejT\n" +
"/ybMVudwKUGudxAEjOclOSQ7XVpPd4p3UMy+E6pkJ8VRiuNFDibYTD+O3XhFTj2l\n" +
"6LqV/KcB2fWV1fUqfxax2vpcwe4clTJbJXUgJC3mYmUXv1U6z0hliDz2WMhbOyea\n" +
"1fs31zYvIMyk2wJFJl7+EbXb8BgYcQt1FE4c14fuFnincAW0So+xBZ5DtiTja4lm\n" +
"DE0OAI2hhLslSQ7rGwWt2zIKSFLtsWf78CP1wigJvWcUVhazAoIBAFAhxgtOaV8G\n" +
"FJxs6A3TtXEmmrq3i5/w3/8l7skWamcHvcwrvw37g5KQY+N+85JZszHPjmUzEq/o\n" +
"NOLjF6zq7+oRtWbOR3Mvc2hHsHg86tE/HAkBIu2G73c9UuskzGSMec4F6TyWKHvC\n" +
"26F2cvu9aA42v/8q1d/QQs3+LrtARGhyFCqnBVNSZmZj5ngR0kLGUfs/LQklpGFe\n" +
"VciXK57RxHSPo15lnKiHW9kCte7tGEg9Eber8paMz67dQDuj1yRkS5H7JXqRZjIP\n" +
"UrICWGGk1vluQsuoyI/Nu8/tL7Im24xZH6XHBm4F+aijl9XlAJiKXfkCLIaxYSdc\n" +
"4PXquTwtRLICIQCijrpD+UhidGFLPZCCLzBC239PrP7PabSVurWXHh5iYw==\n" +
"-----END DSA PRIVATE KEY-----\n"
// DsaPrivateKeyPKCS8PEM is a copy of the private key above, encoded in PKCS#8 format.
// Generated from above with:
// openssl pkcs8 -topk8 -nocrypt -in dsa.privkey.pem -out dsa.privkey.pkcs8.pem
DsaPrivateKeyPKCS8PEM = "-----BEGIN PRIVATE KEY-----\n" +
"MIICZgIBADCCAjoGByqGSM44BAEwggItAoIBAQDdDvapBzRkzZ5SO9n9c5E5BNuG\n" +
"aPO5l3NjuR37zafng096pXP4VaaTbog13ulQ1uf9OOeI3n6kLakr6n1qUUsTX1s6\n" +
"+xjjHtSDCul32xAh5rdbhDsf09oRb4GigXNP994L1OadTTKfCg2FkHfcKveTc8W3\n" +
"/r2WbKuR33XBOleuy2tC6tSHhtovmXq3b8lPdPGkrHrF7X2FaqoWwEu7+P6QTajY\n" +
"DDVNUseDj/vhMkt+oCRacIi71AvS4ZCdbzSRF8mdP61QletcymDUfErsKwMRbnGw\n" +
"BYXfvQizRQ9o/uNGu7oWJI05txWFKoIiz5AkhaJ71xG3CeLCGiLjPoB8Z4RhAiEA\n" +
"6RLMCxaNrV/AHjzr8sJb7O8MkqIIYtNqWKZ9rDMdAcUCggEBAMm8K6MRn9Z5AUuU\n" +
"//TNL7YOOp1rGcqPXG5d+gss6hy8zDTp2etA2YO7+cFCJhJ/N/vOPCmfH4BWMFCy\n" +
"wrSNezSXuDDnYXIIjO0N6NP/JsxW53ApQa53EASM5yU5JDtdWk93indQzL4TqmQn\n" +
"xVGK40UOJthMP47deEVOPaXoupX8pwHZ9ZXV9Sp/FrHa+lzB7hyVMlsldSAkLeZi\n" +
"ZRe/VTrPSGWIPPZYyFs7J5rV+zfXNi8gzKTbAkUmXv4RtdvwGBhxC3UUThzXh+4W\n" +
"eKdwBbRKj7EFnkO2JONriWYMTQ4AjaGEuyVJDusbBa3bMgpIUu2xZ/vwI/XCKAm9\n" +
"ZxRWFrMEIwIhAKKOukP5SGJ0YUs9kIIvMELbf0+s/s9ptJW6tZceHmJj\n" +
"-----END PRIVATE KEY-----\n"
// DsaPublicKeyPEM was generated from above with:
// openssl dsa -in dsa.privkey.pem -pubout -out dsa.pubkey.pem
DsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MIIDRzCCAjoGByqGSM44BAEwggItAoIBAQDdDvapBzRkzZ5SO9n9c5E5BNuGaPO5\n" +
"l3NjuR37zafng096pXP4VaaTbog13ulQ1uf9OOeI3n6kLakr6n1qUUsTX1s6+xjj\n" +
"HtSDCul32xAh5rdbhDsf09oRb4GigXNP994L1OadTTKfCg2FkHfcKveTc8W3/r2W\n" +
"bKuR33XBOleuy2tC6tSHhtovmXq3b8lPdPGkrHrF7X2FaqoWwEu7+P6QTajYDDVN\n" +
"UseDj/vhMkt+oCRacIi71AvS4ZCdbzSRF8mdP61QletcymDUfErsKwMRbnGwBYXf\n" +
"vQizRQ9o/uNGu7oWJI05txWFKoIiz5AkhaJ71xG3CeLCGiLjPoB8Z4RhAiEA6RLM\n" +
"CxaNrV/AHjzr8sJb7O8MkqIIYtNqWKZ9rDMdAcUCggEBAMm8K6MRn9Z5AUuU//TN\n" +
"L7YOOp1rGcqPXG5d+gss6hy8zDTp2etA2YO7+cFCJhJ/N/vOPCmfH4BWMFCywrSN\n" +
"ezSXuDDnYXIIjO0N6NP/JsxW53ApQa53EASM5yU5JDtdWk93indQzL4TqmQnxVGK\n" +
"40UOJthMP47deEVOPaXoupX8pwHZ9ZXV9Sp/FrHa+lzB7hyVMlsldSAkLeZiZRe/\n" +
"VTrPSGWIPPZYyFs7J5rV+zfXNi8gzKTbAkUmXv4RtdvwGBhxC3UUThzXh+4WeKdw\n" +
"BbRKj7EFnkO2JONriWYMTQ4AjaGEuyVJDusbBa3bMgpIUu2xZ/vwI/XCKAm9ZxRW\n" +
"FrMDggEFAAKCAQBQIcYLTmlfBhScbOgN07VxJpq6t4uf8N//Je7JFmpnB73MK78N\n" +
"+4OSkGPjfvOSWbMxz45lMxKv6DTi4xes6u/qEbVmzkdzL3NoR7B4POrRPxwJASLt\n" +
"hu93PVLrJMxkjHnOBek8lih7wtuhdnL7vWgONr//KtXf0ELN/i67QERochQqpwVT\n" +
"UmZmY+Z4EdJCxlH7Py0JJaRhXlXIlyue0cR0j6NeZZyoh1vZArXu7RhIPRG3q/KW\n" +
"jM+u3UA7o9ckZEuR+yV6kWYyD1KyAlhhpNb5bkLLqMiPzbvP7S+yJtuMWR+lxwZu\n" +
"Bfmoo5fV5QCYil35AiyGsWEnXOD16rk8LUSy\n" +
"-----END PUBLIC KEY-----\n"
// DsaSignedAbcdHex was generated with:
// echo -n "abcd" > sign.input
// openssl dgst -dss1 -sign dsa.privkey.pem -out sign.dsa sign.input
// Note that this includes randomization so will give different results each time.
// Verify with:
// openssl dgst -dss1 -verify dsa.pubkey.pem -signature sign.dsa sign.input
DsaSignedAbcdHex = "3045022100c287211dec54eab597ed5264f6fdf57faf651909914c533d42f0e3" +
"2809e9141402205f34cc4b8ca12ebdf025bf36bbe1ff7d807d4cfe951ac1329a" +
"35a39f5e971f10"
// EcdsaPrivateKeyPEM was generated with:
// openssl ecparam -genkey -name prime256v1 -noout -out ecdsa.privkey.pem
EcdsaPrivateKeyPEM = "-----BEGIN EC PRIVATE KEY-----\n" +
"MHcCAQEEIHg0hg3vLqLVI7wv2y0cCk+kmwuwoKsMZqzqbjqP1AtjoAoGCCqGSM49\n" +
"AwEHoUQDQgAEjHs6Rw7KFt8Wd2ZcioZi7eZY5nodUXMnCWUhZzsGVsPaexqUyPSr\n" +
"9cQgrCe7MPRLJ524AO6rREqfs7FKt85++A==\n" +
"-----END EC PRIVATE KEY-----\n"
// EcdsaPrivateKeyPKCS8PEM is a copy of the private key above, encoded in PKCS#8 format.
// Generated from above with:
// openssl pkcs8 -topk8 -nocrypt -in ecdsa.privkey.pem -out ecdsa.privkey.pkcs8.pem
EcdsaPrivateKeyPKCS8PEM = "-----BEGIN PRIVATE KEY-----\n" +
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeDSGDe8uotUjvC/b\n" +
"LRwKT6SbC7CgqwxmrOpuOo/UC2OhRANCAASMezpHDsoW3xZ3ZlyKhmLt5ljmeh1R\n" +
"cycJZSFnOwZWw9p7GpTI9Kv1xCCsJ7sw9EsnnbgA7qtESp+zsUq3zn74\n" +
"-----END PRIVATE KEY-----\n"
// EcdsaPublicKeyPEM was generated from above with:
// openssl ec -in ecdsa.privkey.pem -pubout -out ecdsa.pubkey.pem
EcdsaPublicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjHs6Rw7KFt8Wd2ZcioZi7eZY5nod\n" +
"UXMnCWUhZzsGVsPaexqUyPSr9cQgrCe7MPRLJ524AO6rREqfs7FKt85++A==\n" +
"-----END PUBLIC KEY-----\n"
// EcdsaSignedAbcdHex was generated with:
// echo -n "abcd" > sign.input
// openssl dgst -sha256 -sign ecdsa.privkey.pem -out sign.ecdsa sign.input
// Note that this includes randomization so will give different results each time.
// Verify with:
// openssl dgst -sha256 -verify ecdsa.pubkey.pem -signature sign.ecdsa sign.input
EcdsaSignedAbcdHex = "304502202e0204dadf2baa35e426b66ab8cd32a9ba33421187645a9110512e3e" +
"d1b8007b022100ae83f5f993ab3af6f46bb09b4f15d07cc582b03e1353879ada" +
"ce68796fe537e5"
)
// FromHex decodes a hex string to a byte array, and panics on error; only suitable for
// use in test code.
func FromHex(hexdata string) []byte {
data, err := hex.DecodeString(hexdata)
if err != nil {
panic("non hex data: " + err.Error())
}
return data
}

View File

@ -0,0 +1,60 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 6 (0x6)
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:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4:
19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04:
16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34:
1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56:
46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc:
a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02:
ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d:
d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65:
b1:a3:7a:8c:67:62:53:dc:c1
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2
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
Signature Algorithm: sha1WithRSAEncryption
17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2:
70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae:
fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f:
71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5:
06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58:
2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71:
90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da:
c0:e6
-----BEGIN CERTIFICATE-----
MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk
MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX
YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw
MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu
c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ
4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx
/KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj
eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY
4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE
BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG
A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq
hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6
n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah
ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g==
-----END CERTIFICATE-----

Binary file not shown.

View File

@ -0,0 +1,65 @@
// Copyright 2016 Google Inc. 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 tls
import (
"encoding/hex"
"strings"
"testing"
"github.com/google/certificate-transparency-go/testdata"
)
func TestGenerateHash(t *testing.T) {
var tests = []struct {
in string // hex encoded
algo HashAlgorithm
want string // hex encoded
errstr string
}{
// Empty hash values
{"", MD5, "d41d8cd98f00b204e9800998ecf8427e", ""},
{"", SHA1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", ""},
{"", SHA224, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""},
{"", SHA256, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ""},
{"", SHA384, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", ""},
{"", SHA512, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", ""},
{"", 999, "", "unsupported"},
// Hashes of "abcd".
{"61626364", MD5, testdata.AbcdMD5, ""},
{"61626364", SHA1, testdata.AbcdSHA1, ""},
{"61626364", SHA224, testdata.AbcdSHA224, ""},
{"61626364", SHA256, testdata.AbcdSHA256, ""},
{"61626364", SHA384, testdata.AbcdSHA384, ""},
{"61626364", SHA512, testdata.AbcdSHA512, ""},
}
for _, test := range tests {
got, _, err := generateHash(test.algo, testdata.FromHex(test.in))
if test.errstr != "" {
if err == nil {
t.Errorf("generateHash(%s)=%s,nil; want error %q", test.in, hex.EncodeToString(got), test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("generateHash(%s)=nil,%q; want error %q", test.in, test.errstr, err.Error())
}
continue
}
if err != nil {
t.Errorf("generateHash(%s)=nil,%q; want %s", test.in, err, test.want)
} else if hex.EncodeToString(got) != test.want {
t.Errorf("generateHash(%s)=%s,nil; want %s", test.in, hex.EncodeToString(got), test.want)
}
}
}

View File

@ -0,0 +1,152 @@
// Copyright 2016 Google Inc. 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 tls
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
_ "crypto/md5" // For registration side-effect
"crypto/rand"
"crypto/rsa"
_ "crypto/sha1" // For registration side-effect
_ "crypto/sha256" // For registration side-effect
_ "crypto/sha512" // For registration side-effect
"errors"
"fmt"
"log"
"math/big"
"github.com/google/certificate-transparency-go/asn1"
)
type dsaSig struct {
R, S *big.Int
}
func generateHash(algo HashAlgorithm, data []byte) ([]byte, crypto.Hash, error) {
var hashType crypto.Hash
switch algo {
case MD5:
hashType = crypto.MD5
case SHA1:
hashType = crypto.SHA1
case SHA224:
hashType = crypto.SHA224
case SHA256:
hashType = crypto.SHA256
case SHA384:
hashType = crypto.SHA384
case SHA512:
hashType = crypto.SHA512
default:
return nil, hashType, fmt.Errorf("unsupported Algorithm.Hash in signature: %v", algo)
}
hasher := hashType.New()
if _, err := hasher.Write(data); err != nil {
return nil, hashType, fmt.Errorf("failed to write to hasher: %v", err)
}
return hasher.Sum([]byte{}), hashType, nil
}
// VerifySignature verifies that the passed in signature over data was created by the given PublicKey.
func VerifySignature(pubKey crypto.PublicKey, data []byte, sig DigitallySigned) error {
hash, hashType, err := generateHash(sig.Algorithm.Hash, data)
if err != nil {
return err
}
switch sig.Algorithm.Signature {
case RSA:
rsaKey, ok := pubKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("cannot verify RSA signature with %T key", pubKey)
}
if err := rsa.VerifyPKCS1v15(rsaKey, hashType, hash, sig.Signature); err != nil {
return fmt.Errorf("failed to verify rsa signature: %v", err)
}
case DSA:
dsaKey, ok := pubKey.(*dsa.PublicKey)
if !ok {
return fmt.Errorf("cannot verify DSA signature with %T key", pubKey)
}
var dsaSig dsaSig
rest, err := asn1.Unmarshal(sig.Signature, &dsaSig)
if err != nil {
return fmt.Errorf("failed to unmarshal DSA signature: %v", err)
}
if len(rest) != 0 {
log.Printf("Garbage following signature %v", rest)
}
if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 {
return errors.New("DSA signature contained zero or negative values")
}
if !dsa.Verify(dsaKey, hash, dsaSig.R, dsaSig.S) {
return errors.New("failed to verify DSA signature")
}
case ECDSA:
ecdsaKey, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf("cannot verify ECDSA signature with %T key", pubKey)
}
var ecdsaSig dsaSig
rest, err := asn1.Unmarshal(sig.Signature, &ecdsaSig)
if err != nil {
return fmt.Errorf("failed to unmarshal ECDSA signature: %v", err)
}
if len(rest) != 0 {
log.Printf("Garbage following signature %v", rest)
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return errors.New("ECDSA signature contained zero or negative values")
}
if !ecdsa.Verify(ecdsaKey, hash, ecdsaSig.R, ecdsaSig.S) {
return errors.New("failed to verify ECDSA signature")
}
default:
return fmt.Errorf("unsupported Algorithm.Signature in signature: %v", sig.Algorithm.Hash)
}
return nil
}
// CreateSignature builds a signature over the given data using the specified hash algorithm and private key.
func CreateSignature(privKey crypto.PrivateKey, hashAlgo HashAlgorithm, data []byte) (DigitallySigned, error) {
var sig DigitallySigned
sig.Algorithm.Hash = hashAlgo
hash, hashType, err := generateHash(sig.Algorithm.Hash, data)
if err != nil {
return sig, err
}
switch privKey := privKey.(type) {
case rsa.PrivateKey:
sig.Algorithm.Signature = RSA
sig.Signature, err = rsa.SignPKCS1v15(rand.Reader, &privKey, hashType, hash)
return sig, err
case ecdsa.PrivateKey:
sig.Algorithm.Signature = ECDSA
var ecdsaSig dsaSig
ecdsaSig.R, ecdsaSig.S, err = ecdsa.Sign(rand.Reader, &privKey, hash)
if err != nil {
return sig, err
}
sig.Signature, err = asn1.Marshal(ecdsaSig)
return sig, err
default:
return sig, fmt.Errorf("unsupported private key type %T", privKey)
}
}

View File

@ -0,0 +1,160 @@
// Copyright 2016 Google Inc. 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 tls_test
import (
"crypto"
"encoding/pem"
mathrand "math/rand"
"reflect"
"strings"
"testing"
"time"
"github.com/google/certificate-transparency-go/testdata"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
)
func TestVerifySignature(t *testing.T) {
var tests = []struct {
pubKey crypto.PublicKey
in string // hex encoded
hashAlgo tls.HashAlgorithm
sigAlgo tls.SignatureAlgorithm
errstr string
sig string // hex encoded
}{
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", 99, tls.ECDSA, "unsupported Algorithm.Hash", "1234"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, 99, "unsupported Algorithm.Signature", "1234"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.DSA, "cannot verify DSA", "1234"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "cannot verify ECDSA", "1234"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "verification error", "1234"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "cannot verify ECDSA", "1234"},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.RSA, "cannot verify RSA", "1234"},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.ECDSA, "cannot verify ECDSA", "1234"},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "failed to unmarshal DSA signature", "1234"},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "failed to verify DSA signature", "3006020101020101eeff"},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "zero or negative values", "3006020100020181"},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "cannot verify RSA", "1234"},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.DSA, "cannot verify DSA", "1234"},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "failed to unmarshal ECDSA signature", "1234"},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "failed to verify ECDSA signature", "3006020101020101eeff"},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "zero or negative values", "3006020100020181"},
{PEM2PK(testdata.RsaPublicKeyPEM), "61626364", tls.SHA256, tls.RSA, "", testdata.RsaSignedAbcdHex},
{PEM2PK(testdata.DsaPublicKeyPEM), "61626364", tls.SHA1, tls.DSA, "", testdata.DsaSignedAbcdHex},
{PEM2PK(testdata.EcdsaPublicKeyPEM), "61626364", tls.SHA256, tls.ECDSA, "", testdata.EcdsaSignedAbcdHex},
}
for _, test := range tests {
algo := tls.SignatureAndHashAlgorithm{Hash: test.hashAlgo, Signature: test.sigAlgo}
signed := tls.DigitallySigned{Algorithm: algo, Signature: testdata.FromHex(test.sig)}
err := tls.VerifySignature(test.pubKey, testdata.FromHex(test.in), signed)
if test.errstr != "" {
if err == nil {
t.Errorf("VerifySignature(%s)=nil; want %q", test.in, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("VerifySignature(%s)=%q; want %q", test.in, err.Error(), test.errstr)
}
continue
}
if err != nil {
t.Errorf("VerifySignature(%s)=%q; want nil", test.in, err)
}
}
}
func TestCreateSignatureVerifySignatureRoundTrip(t *testing.T) {
var tests = []struct {
privKey crypto.PrivateKey
pubKey crypto.PublicKey
hashAlgo tls.HashAlgorithm
}{
{PEM2PrivKey(testdata.RsaPrivateKeyPEM), PEM2PK(testdata.RsaPublicKeyPEM), tls.SHA256},
{PEM2PrivKey(testdata.EcdsaPrivateKeyPKCS8PEM), PEM2PK(testdata.EcdsaPublicKeyPEM), tls.SHA256},
}
seed := time.Now().UnixNano()
r := mathrand.New(mathrand.NewSource(seed))
for _, test := range tests {
for j := 0; j < 1; j++ {
dataLen := 10 + r.Intn(100)
data := make([]byte, dataLen)
_, _ = r.Read(data)
sig, err := tls.CreateSignature(test.privKey, test.hashAlgo, data)
if err != nil {
t.Errorf("CreateSignature(%T, %v) failed with: %q", test.privKey, test.hashAlgo, err.Error())
continue
}
if err := tls.VerifySignature(test.pubKey, data, sig); err != nil {
t.Errorf("VerifySignature(%T, %v) failed with: %q", test.pubKey, test.hashAlgo, err)
}
}
}
}
func TestCreateSignatureFailures(t *testing.T) {
var tests = []struct {
privKey crypto.PrivateKey
hashAlgo tls.HashAlgorithm
in string // hex encoded
errstr string
}{
{PEM2PrivKey(testdata.EcdsaPrivateKeyPKCS8PEM), 99, "abcd", "unsupported Algorithm.Hash"},
{nil, tls.SHA256, "abcd", "unsupported private key type"},
}
for _, test := range tests {
if sig, err := tls.CreateSignature(test.privKey, test.hashAlgo, testdata.FromHex(test.in)); err == nil {
t.Errorf("CreateSignature(%T, %v)=%v,nil; want error %q", test.privKey, test.hashAlgo, sig, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("CreateSignature(%T, %v)=nil,%q; want error %q", test.privKey, test.hashAlgo, err.Error(), test.errstr)
}
}
}
func PEM2PK(s string) crypto.PublicKey {
p, _ := pem.Decode([]byte(s))
if p == nil {
panic("no PEM block found in " + s)
}
pubKey, _ := x509.ParsePKIXPublicKey(p.Bytes)
if pubKey == nil {
panic("public key not parsed from " + s)
}
return pubKey
}
func PEM2PrivKey(s string) crypto.PrivateKey {
p, _ := pem.Decode([]byte(s))
if p == nil {
panic("no PEM block found in " + s)
}
// Try various different private key formats one after another.
if rsaPrivKey, err := x509.ParsePKCS1PrivateKey(p.Bytes); err == nil {
return *rsaPrivKey
}
if pkcs8Key, err := x509.ParsePKCS8PrivateKey(p.Bytes); err == nil {
if reflect.TypeOf(pkcs8Key).Kind() == reflect.Ptr {
pkcs8Key = reflect.ValueOf(pkcs8Key).Elem().Interface()
}
return pkcs8Key
}
return nil
}

View File

@ -0,0 +1,711 @@
// Copyright 2016 Google Inc. 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 tls implements functionality for dealing with TLS-encoded data,
// as defined in RFC 5246. This includes parsing and generation of TLS-encoded
// data, together with utility functions for dealing with the DigitallySigned
// TLS type.
package tls
import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
"strconv"
"strings"
)
// This file holds utility functions for TLS encoding/decoding data
// as per RFC 5246 section 4.
// A structuralError suggests that the TLS data is valid, but the Go type
// which is receiving it doesn't match.
type structuralError struct {
field string
msg string
}
func (e structuralError) Error() string {
var prefix string
if e.field != "" {
prefix = e.field + ": "
}
return "tls: structure error: " + prefix + e.msg
}
// A syntaxError suggests that the TLS data is invalid.
type syntaxError struct {
field string
msg string
}
func (e syntaxError) Error() string {
var prefix string
if e.field != "" {
prefix = e.field + ": "
}
return "tls: syntax error: " + prefix + e.msg
}
// Uint24 is an unsigned 3-byte integer.
type Uint24 uint32
// Enum is an unsigned integer.
type Enum uint64
var (
uint8Type = reflect.TypeOf(uint8(0))
uint16Type = reflect.TypeOf(uint16(0))
uint24Type = reflect.TypeOf(Uint24(0))
uint32Type = reflect.TypeOf(uint32(0))
uint64Type = reflect.TypeOf(uint64(0))
enumType = reflect.TypeOf(Enum(0))
)
// Unmarshal parses the TLS-encoded data in b and uses the reflect package to
// fill in an arbitrary value pointed at by val. Because Unmarshal uses the
// reflect package, the structs being written to must use exported fields
// (upper case names).
//
// The mappings between TLS types and Go types is as follows; some fields
// must have tags (to indicate their encoded size).
//
// TLS Go Required Tags
// opaque byte / uint8
// uint8 byte / uint8
// uint16 uint16
// uint24 tls.Uint24
// uint32 uint32
// uint64 uint64
// enum tls.Enum size:S or maxval:N
// Type<N,M> []Type minlen:N,maxlen:M
// opaque[N] [N]byte / [N]uint8
// uint8[N] [N]byte / [N]uint8
// struct { } struct { }
// select(T) {
// case e1: Type *T selector:Field,val:e1
// }
//
// TLS variants (RFC 5246 s4.6.1) are only supported when the value of the
// associated enumeration type is available earlier in the same enclosing
// struct, and each possible variant is marked with a selector tag (to
// indicate which field selects the variants) and a val tag (to indicate
// what value of the selector picks this particular field).
//
// For example, a TLS structure:
//
// enum { e1(1), e2(2) } EnumType;
// struct {
// EnumType sel;
// select(sel) {
// case e1: uint16
// case e2: uint32
// } data;
// } VariantItem;
//
// would have a corresponding Go type:
//
// type VariantItem struct {
// Sel tls.Enum `tls:"maxval:2"`
// Data16 *uint16 `tls:"selector:Sel,val:1"`
// Data32 *uint32 `tls:"selector:Sel,val:2"`
// }
//
// TLS fixed-length vectors of types other than opaque or uint8 are not supported.
//
// For TLS variable-length vectors that are themselves used in other vectors,
// create a single-field structure to represent the inner type. For example, for:
//
// opaque InnerType<1..65535>;
// struct {
// InnerType inners<1,65535>;
// } Something;
//
// convert to:
//
// type InnerType struct {
// Val []byte `tls:"minlen:1,maxlen:65535"`
// }
// type Something struct {
// Inners []InnerType `tls:"minlen:1,maxlen:65535"`
// }
//
// If the encoded value does not fit in the Go type, Unmarshal returns a parse error.
func Unmarshal(b []byte, val interface{}) ([]byte, error) {
return UnmarshalWithParams(b, val, "")
}
// UnmarshalWithParams allows field parameters to be specified for the
// top-level element. The form of the params is the same as the field tags.
func UnmarshalWithParams(b []byte, val interface{}, params string) ([]byte, error) {
info, err := fieldTagToFieldInfo(params, "")
if err != nil {
return nil, err
}
// The passed in interface{} is a pointer (to allow the value to be written
// to); extract the pointed-to object as a reflect.Value, so parseField
// can do various introspection things.
v := reflect.ValueOf(val).Elem()
offset, err := parseField(v, b, 0, info)
if err != nil {
return nil, err
}
return b[offset:], nil
}
// Return the number of bytes needed to encode values up to (and including) x.
func byteCount(x uint64) uint {
switch {
case x < 0x100:
return 1
case x < 0x10000:
return 2
case x < 0x1000000:
return 3
case x < 0x100000000:
return 4
case x < 0x10000000000:
return 5
case x < 0x1000000000000:
return 6
case x < 0x100000000000000:
return 7
default:
return 8
}
}
type fieldInfo struct {
count uint // Number of bytes
countSet bool
minlen uint64 // Only relevant for slices
maxlen uint64 // Only relevant for slices
selector string // Only relevant for select sub-values
val uint64 // Only relevant for select sub-values
name string // Used for better error messages
}
func (i *fieldInfo) fieldName() string {
if i == nil {
return ""
}
return i.name
}
// Given a tag string, return a fieldInfo describing the field.
func fieldTagToFieldInfo(str string, name string) (*fieldInfo, error) {
var info *fieldInfo
// Iterate over clauses in the tag, ignoring any that don't parse properly.
for _, part := range strings.Split(str, ",") {
switch {
case strings.HasPrefix(part, "maxval:"):
if v, err := strconv.ParseUint(part[7:], 10, 64); err == nil {
info = &fieldInfo{count: byteCount(v), countSet: true}
}
case strings.HasPrefix(part, "size:"):
if sz, err := strconv.ParseUint(part[5:], 10, 32); err == nil {
info = &fieldInfo{count: uint(sz), countSet: true}
}
case strings.HasPrefix(part, "maxlen:"):
v, err := strconv.ParseUint(part[7:], 10, 64)
if err != nil {
continue
}
if info == nil {
info = &fieldInfo{}
}
info.count = byteCount(v)
info.countSet = true
info.maxlen = v
case strings.HasPrefix(part, "minlen:"):
v, err := strconv.ParseUint(part[7:], 10, 64)
if err != nil {
continue
}
if info == nil {
info = &fieldInfo{}
}
info.minlen = v
case strings.HasPrefix(part, "selector:"):
if info == nil {
info = &fieldInfo{}
}
info.selector = part[9:]
case strings.HasPrefix(part, "val:"):
v, err := strconv.ParseUint(part[4:], 10, 64)
if err != nil {
continue
}
if info == nil {
info = &fieldInfo{}
}
info.val = v
}
}
if info != nil {
info.name = name
if info.selector == "" {
if info.count < 1 {
return nil, structuralError{name, "field of unknown size in " + str}
} else if info.count > 8 {
return nil, structuralError{name, "specified size too large in " + str}
} else if info.minlen > info.maxlen {
return nil, structuralError{name, "specified length range inverted in " + str}
} else if info.val > 0 {
return nil, structuralError{name, "specified selector value but not field in " + str}
}
}
} else if name != "" {
info = &fieldInfo{name: name}
}
return info, nil
}
// Check that a value fits into a field described by a fieldInfo structure.
func (i fieldInfo) check(val uint64, fldName string) error {
if val >= (1 << (8 * i.count)) {
return structuralError{fldName, fmt.Sprintf("value %d too large for size", val)}
}
if i.maxlen != 0 {
if val < i.minlen {
return structuralError{fldName, fmt.Sprintf("value %d too small for minimum %d", val, i.minlen)}
}
if val > i.maxlen {
return structuralError{fldName, fmt.Sprintf("value %d too large for maximum %d", val, i.maxlen)}
}
}
return nil
}
// readVarUint reads an big-endian unsigned integer of the given size in
// bytes.
func readVarUint(data []byte, info *fieldInfo) (uint64, error) {
if info == nil || !info.countSet {
return 0, structuralError{info.fieldName(), "no field size information available"}
}
if len(data) < int(info.count) {
return 0, syntaxError{info.fieldName(), "truncated variable-length integer"}
}
var result uint64
for i := uint(0); i < info.count; i++ {
result = (result << 8) | uint64(data[i])
}
if err := info.check(result, info.name); err != nil {
return 0, err
}
return result, nil
}
// parseField is the main parsing function. Given a byte slice and an offset
// (in bytes) into the data, it will try to parse a suitable ASN.1 value out
// and store it in the given Value.
func parseField(v reflect.Value, data []byte, initOffset int, info *fieldInfo) (int, error) {
offset := initOffset
rest := data[offset:]
fieldType := v.Type()
// First look for known fixed types.
switch fieldType {
case uint8Type:
if len(rest) < 1 {
return offset, syntaxError{info.fieldName(), "truncated uint8"}
}
v.SetUint(uint64(rest[0]))
offset++
return offset, nil
case uint16Type:
if len(rest) < 2 {
return offset, syntaxError{info.fieldName(), "truncated uint16"}
}
v.SetUint(uint64(binary.BigEndian.Uint16(rest)))
offset += 2
return offset, nil
case uint24Type:
if len(rest) < 3 {
return offset, syntaxError{info.fieldName(), "truncated uint24"}
}
v.SetUint(uint64(data[0])<<16 | uint64(data[1])<<8 | uint64(data[2]))
offset += 3
return offset, nil
case uint32Type:
if len(rest) < 4 {
return offset, syntaxError{info.fieldName(), "truncated uint32"}
}
v.SetUint(uint64(binary.BigEndian.Uint32(rest)))
offset += 4
return offset, nil
case uint64Type:
if len(rest) < 8 {
return offset, syntaxError{info.fieldName(), "truncated uint64"}
}
v.SetUint(uint64(binary.BigEndian.Uint64(rest)))
offset += 8
return offset, nil
}
// Now deal with user-defined types.
switch v.Kind() {
case enumType.Kind():
// Assume that anything of the same kind as Enum is an Enum, so that
// users can alias types of their own to Enum.
val, err := readVarUint(rest, info)
if err != nil {
return offset, err
}
v.SetUint(val)
offset += int(info.count)
return offset, nil
case reflect.Struct:
structType := fieldType
// TLS includes a select(Enum) {..} construct, where the value of an enum
// indicates which variant field is present (like a C union). We require
// that the enum value be an earlier field in the same structure (the selector),
// and that each of the possible variant destination fields be pointers.
// So the Go mapping looks like:
// type variantType struct {
// Which tls.Enum `tls:"size:1"` // this is the selector
// Val1 *type1 `tls:"selector:Which,val:1"` // this is a destination
// Val2 *type2 `tls:"selector:Which,val:1"` // this is a destination
// }
// To deal with this, we track any enum-like fields and their values...
enums := make(map[string]uint64)
// .. and we track which selector names we've seen (in the destination field tags),
// and whether a destination for that selector has been chosen.
selectorSeen := make(map[string]bool)
for i := 0; i < structType.NumField(); i++ {
// Find information about this field.
tag := structType.Field(i).Tag.Get("tls")
fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name)
if err != nil {
return offset, err
}
destination := v.Field(i)
if fieldInfo.selector != "" {
// This is a possible select(Enum) destination, so first check that the referenced
// selector field has already been seen earlier in the struct.
choice, ok := enums[fieldInfo.selector]
if !ok {
return offset, structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector}
}
if structType.Field(i).Type.Kind() != reflect.Ptr {
return offset, structuralError{fieldInfo.name, "choice field not a pointer type"}
}
// Is this the first mention of the selector field name? If so, remember it.
seen, ok := selectorSeen[fieldInfo.selector]
if !ok {
selectorSeen[fieldInfo.selector] = false
}
if choice != fieldInfo.val {
// This destination field was not the chosen one, so make it nil (we checked
// it was a pointer above).
v.Field(i).Set(reflect.Zero(structType.Field(i).Type))
continue
}
if seen {
// We already saw a different destination field receive the value for this
// selector value, which indicates a badly annotated structure.
return offset, structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector}
}
selectorSeen[fieldInfo.selector] = true
// Make an object of the pointed-to type and parse into that.
v.Field(i).Set(reflect.New(structType.Field(i).Type.Elem()))
destination = v.Field(i).Elem()
}
offset, err = parseField(destination, data, offset, fieldInfo)
if err != nil {
return offset, err
}
// Remember any possible tls.Enum values encountered in case they are selectors.
if structType.Field(i).Type.Kind() == enumType.Kind() {
enums[structType.Field(i).Name] = v.Field(i).Uint()
}
}
// Now we have seen all fields in the structure, check that all select(Enum) {..} selector
// fields found a destination to put their data in.
for selector, seen := range selectorSeen {
if !seen {
return offset, syntaxError{info.fieldName(), selector + ": unhandled value for selector"}
}
}
return offset, nil
case reflect.Array:
datalen := v.Len()
if datalen > len(rest) {
return offset, syntaxError{info.fieldName(), "truncated array"}
}
inner := rest[:datalen]
offset += datalen
if fieldType.Elem().Kind() != reflect.Uint8 {
// Only byte/uint8 arrays are supported
return offset, structuralError{info.fieldName(), "unsupported array type: " + v.Type().String()}
}
reflect.Copy(v, reflect.ValueOf(inner))
return offset, nil
case reflect.Slice:
sliceType := fieldType
// Slices represent variable-length vectors, which are prefixed by a length field.
// The fieldInfo indicates the size of that length field.
varlen, err := readVarUint(rest, info)
if err != nil {
return offset, err
}
datalen := int(varlen)
offset += int(info.count)
rest = rest[info.count:]
if datalen > len(rest) {
return offset, syntaxError{info.fieldName(), "truncated slice"}
}
inner := rest[:datalen]
offset += datalen
if fieldType.Elem().Kind() == reflect.Uint8 {
// Fast version for []byte
v.Set(reflect.MakeSlice(sliceType, datalen, datalen))
reflect.Copy(v, reflect.ValueOf(inner))
return offset, nil
}
v.Set(reflect.MakeSlice(sliceType, 0, datalen))
single := reflect.New(sliceType.Elem())
for innerOffset := 0; innerOffset < len(inner); {
var err error
innerOffset, err = parseField(single.Elem(), inner, innerOffset, nil)
if err != nil {
return offset, err
}
v.Set(reflect.Append(v, single.Elem()))
}
return offset, nil
default:
return offset, structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())}
}
}
// Marshal returns the TLS encoding of val.
func Marshal(val interface{}) ([]byte, error) {
return MarshalWithParams(val, "")
}
// MarshalWithParams returns the TLS encoding of val, and allows field
// parameters to be specified for the top-level element. The form
// of the params is the same as the field tags.
func MarshalWithParams(val interface{}, params string) ([]byte, error) {
info, err := fieldTagToFieldInfo(params, "")
if err != nil {
return nil, err
}
var out bytes.Buffer
v := reflect.ValueOf(val)
if err := marshalField(&out, v, info); err != nil {
return nil, err
}
return out.Bytes(), err
}
func marshalField(out *bytes.Buffer, v reflect.Value, info *fieldInfo) error {
var prefix string
if info != nil && len(info.name) > 0 {
prefix = info.name + ": "
}
fieldType := v.Type()
// First look for known fixed types.
switch fieldType {
case uint8Type:
out.WriteByte(byte(v.Uint()))
return nil
case uint16Type:
scratch := make([]byte, 2)
binary.BigEndian.PutUint16(scratch, uint16(v.Uint()))
out.Write(scratch)
return nil
case uint24Type:
i := v.Uint()
if i > 0xffffff {
return structuralError{info.fieldName(), fmt.Sprintf("uint24 overflow %d", i)}
}
scratch := make([]byte, 4)
binary.BigEndian.PutUint32(scratch, uint32(i))
out.Write(scratch[1:])
return nil
case uint32Type:
scratch := make([]byte, 4)
binary.BigEndian.PutUint32(scratch, uint32(v.Uint()))
out.Write(scratch)
return nil
case uint64Type:
scratch := make([]byte, 8)
binary.BigEndian.PutUint64(scratch, uint64(v.Uint()))
out.Write(scratch)
return nil
}
// Now deal with user-defined types.
switch v.Kind() {
case enumType.Kind():
i := v.Uint()
if info == nil {
return structuralError{info.fieldName(), "enum field tag missing"}
}
if err := info.check(i, prefix); err != nil {
return err
}
scratch := make([]byte, 8)
binary.BigEndian.PutUint64(scratch, uint64(i))
out.Write(scratch[(8 - info.count):])
return nil
case reflect.Struct:
structType := fieldType
enums := make(map[string]uint64) // Values of any Enum fields
// The comment parseField() describes the mapping of the TLS select(Enum) {..} construct;
// here we have selector and source (rather than destination) fields.
// Track which selector names we've seen (in the source field tags), and whether a source
// value for that selector has been processed.
selectorSeen := make(map[string]bool)
for i := 0; i < structType.NumField(); i++ {
// Find information about this field.
tag := structType.Field(i).Tag.Get("tls")
fieldInfo, err := fieldTagToFieldInfo(tag, structType.Field(i).Name)
if err != nil {
return err
}
source := v.Field(i)
if fieldInfo.selector != "" {
// This field is a possible source for a select(Enum) {..}. First check
// the selector field name has been seen.
choice, ok := enums[fieldInfo.selector]
if !ok {
return structuralError{fieldInfo.name, "selector not seen: " + fieldInfo.selector}
}
if structType.Field(i).Type.Kind() != reflect.Ptr {
return structuralError{fieldInfo.name, "choice field not a pointer type"}
}
// Is this the first mention of the selector field name? If so, remember it.
seen, ok := selectorSeen[fieldInfo.selector]
if !ok {
selectorSeen[fieldInfo.selector] = false
}
if choice != fieldInfo.val {
// This source was not chosen; police that it should be nil.
if v.Field(i).Pointer() != uintptr(0) {
return structuralError{fieldInfo.name, "unchosen field is non-nil"}
}
continue
}
if seen {
// We already saw a different source field generate the value for this
// selector value, which indicates a badly annotated structure.
return structuralError{fieldInfo.name, "duplicate selector value for " + fieldInfo.selector}
}
selectorSeen[fieldInfo.selector] = true
if v.Field(i).Pointer() == uintptr(0) {
return structuralError{fieldInfo.name, "chosen field is nil"}
}
// Marshal from the pointed-to source object.
source = v.Field(i).Elem()
}
var fieldData bytes.Buffer
if err := marshalField(&fieldData, source, fieldInfo); err != nil {
return err
}
out.Write(fieldData.Bytes())
// Remember any tls.Enum values encountered in case they are selectors.
if structType.Field(i).Type.Kind() == enumType.Kind() {
enums[structType.Field(i).Name] = v.Field(i).Uint()
}
}
// Now we have seen all fields in the structure, check that all select(Enum) {..} selector
// fields found a source field get get their data from.
for selector, seen := range selectorSeen {
if !seen {
return syntaxError{info.fieldName(), selector + ": unhandled value for selector"}
}
}
return nil
case reflect.Array:
datalen := v.Len()
arrayType := fieldType
if arrayType.Elem().Kind() != reflect.Uint8 {
// Only byte/uint8 arrays are supported
return structuralError{info.fieldName(), "unsupported array type"}
}
bytes := make([]byte, datalen)
for i := 0; i < datalen; i++ {
bytes[i] = uint8(v.Index(i).Uint())
}
_, err := out.Write(bytes)
return err
case reflect.Slice:
if info == nil {
return structuralError{info.fieldName(), "slice field tag missing"}
}
sliceType := fieldType
if sliceType.Elem().Kind() == reflect.Uint8 {
// Fast version for []byte: first write the length as info.count bytes.
datalen := v.Len()
scratch := make([]byte, 8)
binary.BigEndian.PutUint64(scratch, uint64(datalen))
out.Write(scratch[(8 - info.count):])
if err := info.check(uint64(datalen), prefix); err != nil {
return err
}
// Then just write the data.
bytes := make([]byte, datalen)
for i := 0; i < datalen; i++ {
bytes[i] = uint8(v.Index(i).Uint())
}
_, err := out.Write(bytes)
return err
}
// General version: use a separate Buffer to write the slice entries into.
var innerBuf bytes.Buffer
for i := 0; i < v.Len(); i++ {
if err := marshalField(&innerBuf, v.Index(i), nil); err != nil {
return err
}
}
// Now insert (and check) the size.
size := uint64(innerBuf.Len())
if err := info.check(size, prefix); err != nil {
return err
}
scratch := make([]byte, 8)
binary.BigEndian.PutUint64(scratch, size)
out.Write(scratch[(8 - info.count):])
// Then copy the data.
_, err := out.Write(innerBuf.Bytes())
return err
default:
return structuralError{info.fieldName(), fmt.Sprintf("unsupported type: %s of kind %s", fieldType, v.Kind())}
}
}

View File

@ -0,0 +1,355 @@
// Copyright 2016 Google Inc. 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 tls
import (
"bytes"
"encoding/hex"
"reflect"
"strings"
"testing"
)
type testStruct struct {
Data []byte `tls:"minlen:2,maxlen:4"`
IntVal uint16
Other [4]byte
Enum Enum `tls:"size:2"`
}
type testVariant struct {
Which Enum `tls:"size:1"`
Val16 *uint16 `tls:"selector:Which,val:0"`
Val32 *uint32 `tls:"selector:Which,val:1"`
}
type testTwoVariants struct {
Which Enum `tls:"size:1"`
Val16 *uint16 `tls:"selector:Which,val:0"`
Val32 *uint32 `tls:"selector:Which,val:1"`
Second Enum `tls:"size:1"`
Second16 *uint16 `tls:"selector:Second,val:0"`
Second32 *uint32 `tls:"selector:Second,val:1"`
}
// Check that library users can define their own Enum types.
type aliasEnum Enum
type testAliasEnum struct {
Val aliasEnum `tls:"size:1"`
Val16 *uint16 `tls:"selector:Val,val:1"`
Val32 *uint32 `tls:"selector:Val,val:2"`
}
type testNonByteSlice struct {
Vals []uint16 `tls:"minlen:2,maxlen:6"`
}
type testSliceOfStructs struct {
Vals []testVariant `tls:"minlen:0,maxlen:100"`
}
type testInnerType struct {
Val []byte `tls:"minlen:0,maxlen:65535"`
}
type testSliceOfSlices struct {
Inners []testInnerType `tls:"minlen:0,maxlen:65535"`
}
func TestMarshalUnmarshalRoundTrip(t *testing.T) {
thing := testStruct{Data: []byte{0x01, 0x02, 0x03}, IntVal: 42, Other: [4]byte{1, 2, 3, 4}, Enum: 17}
data, err := Marshal(thing)
if err != nil {
t.Fatalf("Failed to Marshal(%+v): %s", thing, err.Error())
}
var other testStruct
rest, err := Unmarshal(data, &other)
if err != nil {
t.Fatalf("Failed to Unmarshal(%s)", hex.EncodeToString(data))
}
if len(rest) > 0 {
t.Errorf("Data left over after Unmarshal(%s): %s", hex.EncodeToString(data), hex.EncodeToString(rest))
}
}
func TestFieldTagToFieldInfo(t *testing.T) {
var tests = []struct {
tag string
want *fieldInfo
errstr string
}{
{"", nil, ""},
{"bogus", nil, ""},
{"also,bogus", nil, ""},
{"also,bogus:99", nil, ""},
{"maxval:1xyz", nil, ""},
{"maxval:1", &fieldInfo{count: 1, countSet: true}, ""},
{"maxval:255", &fieldInfo{count: 1, countSet: true}, ""},
{"maxval:256", &fieldInfo{count: 2, countSet: true}, ""},
{"maxval:65535", &fieldInfo{count: 2, countSet: true}, ""},
{"maxval:65536", &fieldInfo{count: 3, countSet: true}, ""},
{"maxval:16777215", &fieldInfo{count: 3, countSet: true}, ""},
{"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""},
{"maxval:16777216", &fieldInfo{count: 4, countSet: true}, ""},
{"maxval:4294967295", &fieldInfo{count: 4, countSet: true}, ""},
{"maxval:4294967296", &fieldInfo{count: 5, countSet: true}, ""},
{"maxval:1099511627775", &fieldInfo{count: 5, countSet: true}, ""},
{"maxval:1099511627776", &fieldInfo{count: 6, countSet: true}, ""},
{"maxval:281474976710655", &fieldInfo{count: 6, countSet: true}, ""},
{"maxval:281474976710656", &fieldInfo{count: 7, countSet: true}, ""},
{"maxval:72057594037927935", &fieldInfo{count: 7, countSet: true}, ""},
{"maxval:72057594037927936", &fieldInfo{count: 8, countSet: true}, ""},
{"minlen:1x", nil, ""},
{"maxlen:1x", nil, ""},
{"maxlen:1", &fieldInfo{count: 1, countSet: true, maxlen: 1}, ""},
{"maxlen:255", &fieldInfo{count: 1, countSet: true, maxlen: 255}, ""},
{"maxlen:65535", &fieldInfo{count: 2, countSet: true, maxlen: 65535}, ""},
{"minlen:65530,maxlen:65535", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""},
{"maxlen:65535,minlen:65530", &fieldInfo{count: 2, countSet: true, minlen: 65530, maxlen: 65535}, ""},
{"minlen:65536,maxlen:65535", nil, "inverted"},
{"maxlen:16777215", &fieldInfo{count: 3, countSet: true, maxlen: 16777215}, ""},
{"maxlen:281474976710655", &fieldInfo{count: 6, countSet: true, maxlen: 281474976710655}, ""},
{"maxlen:72057594037927936", &fieldInfo{count: 8, countSet: true, maxlen: 72057594037927936}, ""},
{"size:0", nil, "unknown size"},
{"size:1", &fieldInfo{count: 1, countSet: true}, ""},
{"size:2", &fieldInfo{count: 2, countSet: true}, ""},
{"size:3", &fieldInfo{count: 3, countSet: true}, ""},
{"size:4", &fieldInfo{count: 4, countSet: true}, ""},
{"size:5", &fieldInfo{count: 5, countSet: true}, ""},
{"size:6", &fieldInfo{count: 6, countSet: true}, ""},
{"size:7", &fieldInfo{count: 7, countSet: true}, ""},
{"size:8", &fieldInfo{count: 8, countSet: true}, ""},
{"size:9", nil, "too large"},
{"size:1x", nil, ""},
{"size:1,val:9", nil, "selector value"},
{"selector:Bob,val:x9", &fieldInfo{selector: "Bob"}, ""},
{"selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""},
{"val:9,selector:Fred,val:1", &fieldInfo{selector: "Fred", val: 1}, ""},
}
for _, test := range tests {
got, err := fieldTagToFieldInfo(test.tag, "")
if test.errstr != "" {
if err == nil {
t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want error %q", test.tag, got, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want error %q", test.tag, err.Error(), test.errstr)
}
continue
}
if err != nil {
t.Errorf("fieldTagToFieldInfo('%v')=nil,%q; want %+v", test.tag, err.Error(), test.want)
} else if !reflect.DeepEqual(got, test.want) {
t.Errorf("fieldTagToFieldInfo('%v')=%+v,nil; want %+v", test.tag, got, test.want)
}
}
}
// Can't take the address of a numeric constant so use helper functions
func newByte(n byte) *byte { return &n }
func newUint8(n uint8) *uint8 { return &n }
func newUint16(n uint16) *uint16 { return &n }
func newUint24(n Uint24) *Uint24 { return &n }
func newUint32(n uint32) *uint32 { return &n }
func newUint64(n uint64) *uint64 { return &n }
func newInt16(n int16) *int16 { return &n }
func newEnum(n Enum) *Enum { return &n }
func TestUnmarshalMarshalWithParamsRoundTrip(t *testing.T) {
var tests = []struct {
data string // hex encoded
params string
item interface{}
}{
{"00", "", newUint8(0)},
{"03", "", newByte(3)},
{"0101", "", newUint16(0x0101)},
{"010203", "", newUint24(0x010203)},
{"000000", "", newUint24(0x00)},
{"00000009", "", newUint32(0x09)},
{"0000000901020304", "", newUint64(0x0901020304)},
{"030405", "", &[3]byte{3, 4, 5}},
{"03", "", &[1]byte{3}},
{"0001", "size:2", newEnum(1)},
{"0100000001", "size:5", newEnum(0x100000001)},
{"12", "maxval:18", newEnum(18)},
// Note that maxval is just used to give enum size; it's not policed
{"20", "maxval:18", newEnum(32)},
{"020a0b", "minlen:1,maxlen:5", &[]byte{0xa, 0xb}},
{"020a0b0101010203040011", "", &testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}},
{"000102", "", &testVariant{Which: 0, Val16: newUint16(0x0102)}},
{"0101020304", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}},
{"0001020104030201", "", &testTwoVariants{Which: 0, Val16: newUint16(0x0102), Second: 1, Second32: newUint32(0x04030201)}},
{"06010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}},
{"00", "", &testSliceOfStructs{Vals: []testVariant{}}},
{"080001020101020304", "",
&testSliceOfStructs{
Vals: []testVariant{
{Which: 0, Val16: newUint16(0x0102)},
{Which: 1, Val32: newUint32(0x01020304)},
},
},
},
{"000a00030102030003040506", "",
&testSliceOfSlices{
Inners: []testInnerType{
{Val: []byte{1, 2, 3}},
{Val: []byte{4, 5, 6}},
},
},
},
{"011011", "", &testAliasEnum{Val: 1, Val16: newUint16(0x1011)}},
{"0403", "", &SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA}},
{"04030003010203", "",
&DigitallySigned{
Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA},
Signature: []byte{1, 2, 3},
},
},
}
for _, test := range tests {
inVal := reflect.ValueOf(test.item).Elem()
pv := reflect.New(reflect.TypeOf(test.item).Elem())
val := pv.Interface()
inData, _ := hex.DecodeString(test.data)
if _, err := UnmarshalWithParams(inData, val, test.params); err != nil {
t.Errorf("Unmarshal(%s)=nil,%q; want %+v", test.data, err.Error(), inVal)
} else if !reflect.DeepEqual(val, test.item) {
t.Errorf("Unmarshal(%s)=%+v,nil; want %+v", test.data, reflect.ValueOf(val).Elem(), inVal)
}
if data, err := MarshalWithParams(inVal.Interface(), test.params); err != nil {
t.Errorf("Marshal(%+v)=nil,%q; want %s", inVal, err.Error(), test.data)
} else if !bytes.Equal(data, inData) {
t.Errorf("Marshal(%+v)=%s,nil; want %s", inVal, hex.EncodeToString(data), test.data)
}
}
}
type testInvalidFieldTag struct {
Data []byte `tls:"minlen:3,maxlen:2"`
}
type testDuplicateSelectorVal struct {
Which Enum `tls:"size:1"`
Val *uint16 `tls:"selector:Which,val:0"`
DupVal *uint32 `tls:"selector:Which"` // implicit val:0
}
type testMissingSelector struct {
Val *uint16 `tls:"selector:Missing,val:0"`
}
type testChoiceNotPointer struct {
Which Enum `tls:"size:1"`
Val uint16 `tls:"selector:Which,val:0"`
}
type nonEnumAlias uint16
func newNonEnumAlias(n nonEnumAlias) *nonEnumAlias { return &n }
func TestUnmarshalWithParamsFailures(t *testing.T) {
var tests = []struct {
data string // hex encoded
params string
item interface{}
errstr string
}{
{"", "", newUint8(0), "truncated"},
{"0x01", "", newUint16(0x0101), "truncated"},
{"0103", "", newUint24(0x010203), "truncated"},
{"00", "", newUint24(0x00), "truncated"},
{"000009", "", newUint32(0x09), "truncated"},
{"00000901020304", "", newUint64(0x0901020304), "truncated"},
{"0102", "", newInt16(0x0102), "unsupported type"}, // TLS encoding only supports unsigned integers
{"0607", "", &[3]byte{6, 7, 8}, "truncated array"},
{"01010202", "", &[3]uint16{0x101, 0x202}, "unsupported array"},
{"01", "", newEnum(1), "no field size"},
{"00", "size:2", newEnum(0), "truncated"},
{"00", "size:9", newEnum(0), "too large"},
{"020a0b", "minlen:4,maxlen:8", &[]byte{0x0a, 0x0b}, "too small"},
{"040a0b0c0d", "minlen:1,maxlen:3", &[]byte{0x0a, 0x0b, 0x0c, 0x0d}, "too large"},
{"020a0b", "minlen:8,maxlen:6", &[]byte{0x0a, 0x0b}, "inverted"},
{"020a", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"},
{"02", "minlen:0,maxlen:6", &[]byte{0x0a, 0x0b}, "truncated"},
{"0001", "minlen:0,maxlen:256", &[]byte{0x0a, 0x0b}, "truncated"},
{"020a", "minlen:0", &[]byte{0x0a, 0x0b}, "unknown size"},
{"020a", "", &[]byte{0x0a, 0x0b}, "no field size information"},
{"020a0b", "", &testInvalidFieldTag{}, "range inverted"},
{"020a0b01010102030400", "",
&testStruct{Data: []byte{0xa, 0xb}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "truncated"},
{"010102", "", &testVariant{Which: 1, Val32: newUint32(0x01020304)}, "truncated"},
{"092122", "", &testVariant{Which: 0, Val16: newUint16(0x2122)}, "unhandled value for selector"},
{"0001020304", "", &testDuplicateSelectorVal{Which: 0, Val: newUint16(0x0102)}, "duplicate selector value"},
{"0102", "", &testMissingSelector{Val: newUint16(1)}, "selector not seen"},
{"000007", "", &testChoiceNotPointer{Which: 0, Val: 7}, "choice field not a pointer type"},
{"05010102020303", "", &testNonByteSlice{Vals: []uint16{0x101, 0x202, 0x303}}, "truncated"},
{"0101", "size:2", newNonEnumAlias(0x0102), "unsupported type"},
{"0403010203", "",
&DigitallySigned{
Algorithm: SignatureAndHashAlgorithm{Hash: SHA256, Signature: ECDSA},
Signature: []byte{1, 2, 3}}, "truncated"},
}
for _, test := range tests {
pv := reflect.New(reflect.TypeOf(test.item).Elem())
val := pv.Interface()
in, _ := hex.DecodeString(test.data)
if _, err := UnmarshalWithParams(in, val, test.params); err == nil {
t.Errorf("Unmarshal(%s)=%+v,nil; want error %q", test.data, reflect.ValueOf(val).Elem(), test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("Unmarshal(%s)=nil,%q; want error %q", test.data, err.Error(), test.errstr)
}
}
}
func TestMarshalWithParamsFailures(t *testing.T) {
var tests = []struct {
item interface{}
params string
errstr string
}{
{Uint24(0x1000000), "", "overflow"},
{int16(0x0102), "", "unsupported type"}, // All TLS ints are unsigned
{Enum(1), "", "field tag missing"},
{Enum(256), "size:1", "too large"},
{Enum(256), "maxval:255", "too large"},
{Enum(2), "", "field tag missing"},
{Enum(256), "size:9", "too large"},
{[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:1,maxlen:3", "too large"},
{[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:13", "too small"},
{[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6,maxlen:3", "inverted"},
{[]byte{0xa, 0xb, 0xc, 0xd}, "minlen:6", "unknown size"},
{[]byte{0xa, 0xb, 0xc, 0xd}, "", "field tag missing"},
{[3]uint16{0x101, 0x202}, "", "unsupported array"},
{testInvalidFieldTag{}, "", "inverted"},
{testStruct{Data: []byte{0xa}, IntVal: 0x101, Other: [4]byte{1, 2, 3, 4}, Enum: 17}, "", "too small"},
{testVariant{Which: 0, Val32: newUint32(0x01020304)}, "", "chosen field is nil"},
{testVariant{Which: 0, Val16: newUint16(11), Val32: newUint32(0x01020304)}, "", "unchosen field is non-nil"},
{testVariant{Which: 3}, "", "unhandled value for selector"},
{testMissingSelector{Val: newUint16(1)}, "", "selector not seen"},
{testChoiceNotPointer{Which: 0, Val: 7}, "", "choice field not a pointer"},
{testDuplicateSelectorVal{Which: 0, Val: newUint16(1)}, "", "duplicate selector value"},
{testNonByteSlice{Vals: []uint16{1, 2, 3, 4}}, "", "too large"},
{testSliceOfStructs{[]testVariant{{Which: 3}}}, "", "unhandled value for selector"},
{nonEnumAlias(0x0102), "", "unsupported type"},
}
for _, test := range tests {
if data, err := MarshalWithParams(test.item, test.params); err == nil {
t.Errorf("Marshal(%+v)=%x,nil; want error %q", test.item, data, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("Marshal(%+v)=nil,%q; want error %q", test.item, err.Error(), test.errstr)
}
}
}

View File

@ -0,0 +1,96 @@
// Copyright 2016 Google Inc. 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 tls
import "fmt"
// DigitallySigned gives information about a signature, including the algorithm used
// and the signature value. Defined in RFC 5246 s4.7.
type DigitallySigned struct {
Algorithm SignatureAndHashAlgorithm
Signature []byte `tls:"minlen:0,maxlen:65535"`
}
func (d DigitallySigned) String() string {
return fmt.Sprintf("Signature: HashAlgo=%v SignAlgo=%v Value=%x", d.Algorithm.Hash, d.Algorithm.Signature, d.Signature)
}
// SignatureAndHashAlgorithm gives information about the algorithms used for a
// signature. Defined in RFC 5246 s7.4.1.4.1.
type SignatureAndHashAlgorithm struct {
Hash HashAlgorithm `tls:"maxval:255"`
Signature SignatureAlgorithm `tls:"maxval:255"`
}
// HashAlgorithm enum from RFC 5246 s7.4.1.4.1.
type HashAlgorithm Enum
// HashAlgorithm constants from RFC 5246 s7.4.1.4.1.
const (
None HashAlgorithm = 0
MD5 HashAlgorithm = 1
SHA1 HashAlgorithm = 2
SHA224 HashAlgorithm = 3
SHA256 HashAlgorithm = 4
SHA384 HashAlgorithm = 5
SHA512 HashAlgorithm = 6
)
func (h HashAlgorithm) String() string {
switch h {
case None:
return "None"
case MD5:
return "MD5"
case SHA1:
return "SHA1"
case SHA224:
return "SHA224"
case SHA256:
return "SHA256"
case SHA384:
return "SHA384"
case SHA512:
return "SHA512"
default:
return fmt.Sprintf("UNKNOWN(%d)", h)
}
}
// SignatureAlgorithm enum from RFC 5246 s7.4.1.4.1.
type SignatureAlgorithm Enum
// SignatureAlgorithm constants from RFC 5246 s7.4.1.4.1.
const (
Anonymous SignatureAlgorithm = 0
RSA SignatureAlgorithm = 1
DSA SignatureAlgorithm = 2
ECDSA SignatureAlgorithm = 3
)
func (s SignatureAlgorithm) String() string {
switch s {
case Anonymous:
return "Anonymous"
case RSA:
return "RSA"
case DSA:
return "DSA"
case ECDSA:
return "ECDSA"
default:
return fmt.Sprintf("UNKNOWN(%d)", s)
}
}

View File

@ -0,0 +1,77 @@
// Copyright 2016 Google Inc. 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 tls
import "testing"
func TestHashAlgorithmString(t *testing.T) {
var tests = []struct {
algo HashAlgorithm
want string
}{
{None, "None"},
{MD5, "MD5"},
{SHA1, "SHA1"},
{SHA224, "SHA224"},
{SHA256, "SHA256"},
{SHA384, "SHA384"},
{SHA512, "SHA512"},
{99, "UNKNOWN(99)"},
}
for _, test := range tests {
if got := test.algo.String(); got != test.want {
t.Errorf("%v.String()=%q; want %q", test.algo, got, test.want)
}
}
}
func TestSignatureAlgorithmString(t *testing.T) {
var tests = []struct {
algo SignatureAlgorithm
want string
}{
{Anonymous, "Anonymous"},
{RSA, "RSA"},
{DSA, "DSA"},
{ECDSA, "ECDSA"},
{99, "UNKNOWN(99)"},
}
for _, test := range tests {
if got := test.algo.String(); got != test.want {
t.Errorf("%v.String()=%q; want %q", test.algo, got, test.want)
}
}
}
func TestDigitallySignedString(t *testing.T) {
var tests = []struct {
ds DigitallySigned
want string
}{
{
ds: DigitallySigned{Algorithm: SignatureAndHashAlgorithm{Hash: SHA1, Signature: RSA}, Signature: []byte{0x01, 0x02}},
want: "Signature: HashAlgo=SHA1 SignAlgo=RSA Value=0102",
},
{
ds: DigitallySigned{Algorithm: SignatureAndHashAlgorithm{Hash: 99, Signature: 99}, Signature: []byte{0x03, 0x04}},
want: "Signature: HashAlgo=UNKNOWN(99) SignAlgo=UNKNOWN(99) Value=0304",
},
}
for _, test := range tests {
if got := test.ds.String(); got != test.want {
t.Errorf("%v.String()=%q; want %q", test.ds, got, test.want)
}
}
}

View File

@ -0,0 +1,460 @@
// Copyright 2015 Google Inc. 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 ct holds core types and utilities for Certificate Transparency.
package ct
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
)
///////////////////////////////////////////////////////////////////////////////
// The following structures represent those outlined in RFC6962; any section
// numbers mentioned refer to that RFC.
///////////////////////////////////////////////////////////////////////////////
// LogEntryType represents the LogEntryType enum from section 3.1:
// enum { x509_entry(0), precert_entry(1), (65535) } LogEntryType;
type LogEntryType tls.Enum // tls:"maxval:65535"
// LogEntryType constants from section 3.1.
const (
X509LogEntryType LogEntryType = 0
PrecertLogEntryType LogEntryType = 1
XJSONLogEntryType LogEntryType = 0x8000 // Experimental. Don't rely on this!
)
func (e LogEntryType) String() string {
switch e {
case X509LogEntryType:
return "X509LogEntryType"
case PrecertLogEntryType:
return "PrecertLogEntryType"
case XJSONLogEntryType:
return "XJSONLogEntryType"
default:
return fmt.Sprintf("UnknownEntryType(%d)", e)
}
}
// MerkleLeafType represents the MerkleLeafType enum from section 3.4:
// enum { timestamped_entry(0), (255) } MerkleLeafType;
type MerkleLeafType tls.Enum // tls:"maxval:255"
// TimestampedEntryLeafType is the only defined MerkleLeafType constant from section 3.4.
const TimestampedEntryLeafType MerkleLeafType = 0 // Entry type for an SCT
func (m MerkleLeafType) String() string {
switch m {
case TimestampedEntryLeafType:
return "TimestampedEntryLeafType"
default:
return fmt.Sprintf("UnknownLeafType(%d)", m)
}
}
// Version represents the Version enum from section 3.2:
// enum { v1(0), (255) } Version;
type Version tls.Enum // tls:"maxval:255"
// CT Version constants from section 3.2.
const (
V1 Version = 0
)
func (v Version) String() string {
switch v {
case V1:
return "V1"
default:
return fmt.Sprintf("UnknownVersion(%d)", v)
}
}
// SignatureType differentiates STH signatures from SCT signatures, see section 3.2.
// enum { certificate_timestamp(0), tree_hash(1), (255) } SignatureType;
type SignatureType tls.Enum // tls:"maxval:255"
// SignatureType constants from section 3.2.
const (
CertificateTimestampSignatureType SignatureType = 0
TreeHashSignatureType SignatureType = 1
)
func (st SignatureType) String() string {
switch st {
case CertificateTimestampSignatureType:
return "CertificateTimestamp"
case TreeHashSignatureType:
return "TreeHash"
default:
return fmt.Sprintf("UnknownSignatureType(%d)", st)
}
}
// ASN1Cert type for holding the raw DER bytes of an ASN.1 Certificate
// (section 3.1).
type ASN1Cert struct {
Data []byte `tls:"minlen:1,maxlen:16777215"`
}
// LogID holds the hash of the Log's public key (section 3.2).
// TODO(pphaneuf): Users should be migrated to the one in the logid package.
type LogID struct {
KeyID [sha256.Size]byte
}
// PreCert represents a Precertificate (section 3.2).
type PreCert struct {
IssuerKeyHash [sha256.Size]byte
TBSCertificate []byte `tls:"minlen:1,maxlen:16777215"` // DER-encoded TBSCertificate
}
// CTExtensions is a representation of the raw bytes of any CtExtension
// structure (see section 3.2).
// nolint: golint
type CTExtensions []byte // tls:"minlen:0,maxlen:65535"`
// MerkleTreeNode represents an internal node in the CT tree.
type MerkleTreeNode []byte
// ConsistencyProof represents a CT consistency proof (see sections 2.1.2 and
// 4.4).
type ConsistencyProof []MerkleTreeNode
// AuditPath represents a CT inclusion proof (see sections 2.1.1 and 4.5).
type AuditPath []MerkleTreeNode
// LeafInput represents a serialized MerkleTreeLeaf structure.
type LeafInput []byte
// DigitallySigned is a local alias for tls.DigitallySigned so that we can
// attach a MarshalJSON method.
type DigitallySigned tls.DigitallySigned
// FromBase64String populates the DigitallySigned structure from the base64 data passed in.
// Returns an error if the base64 data is invalid.
func (d *DigitallySigned) FromBase64String(b64 string) error {
raw, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return fmt.Errorf("failed to unbase64 DigitallySigned: %v", err)
}
var ds tls.DigitallySigned
if rest, err := tls.Unmarshal(raw, &ds); err != nil {
return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err)
} else if len(rest) > 0 {
return fmt.Errorf("trailing data (%d bytes) after DigitallySigned", len(rest))
}
*d = DigitallySigned(ds)
return nil
}
// Base64String returns the base64 representation of the DigitallySigned struct.
func (d DigitallySigned) Base64String() (string, error) {
b, err := tls.Marshal(d)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(b), nil
}
// MarshalJSON implements the json.Marshaller interface.
func (d DigitallySigned) MarshalJSON() ([]byte, error) {
b64, err := d.Base64String()
if err != nil {
return []byte{}, err
}
return []byte(`"` + b64 + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (d *DigitallySigned) UnmarshalJSON(b []byte) error {
var content string
if err := json.Unmarshal(b, &content); err != nil {
return fmt.Errorf("failed to unmarshal DigitallySigned: %v", err)
}
return d.FromBase64String(content)
}
// LogEntry represents the (parsed) contents of an entry in a CT log. This is described
// in section 3.1, but note that this structure does *not* match the TLS structure
// defined there (the TLS structure is never used directly in RFC6962).
type LogEntry struct {
Index int64
Leaf MerkleTreeLeaf
// Exactly one of the following three fields should be non-empty.
X509Cert *x509.Certificate // Parsed X.509 certificate
Precert *Precertificate // Extracted precertificate
JSONData []byte
// Chain holds the issuing certificate chain, starting with the
// issuer of the leaf certificate / pre-certificate.
Chain []ASN1Cert
}
// PrecertChainEntry holds an precertificate together with a validation chain
// for it; see section 3.1.
type PrecertChainEntry struct {
PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"`
CertificateChain []ASN1Cert `tls:"minlen:0,maxlen:16777215"`
}
// CertificateChain holds a chain of certificates, as returned as extra data
// for get-entries (section 4.6).
type CertificateChain struct {
Entries []ASN1Cert `tls:"minlen:0,maxlen:16777215"`
}
// JSONDataEntry holds arbitrary data.
type JSONDataEntry struct {
Data []byte `tls:"minlen:0,maxlen:1677215"`
}
// SHA256Hash represents the output from the SHA256 hash function.
type SHA256Hash [sha256.Size]byte
// FromBase64String populates the SHA256 struct with the contents of the base64 data passed in.
func (s *SHA256Hash) FromBase64String(b64 string) error {
bs, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return fmt.Errorf("failed to unbase64 LogID: %v", err)
}
if len(bs) != sha256.Size {
return fmt.Errorf("invalid SHA256 length, expected 32 but got %d", len(bs))
}
copy(s[:], bs)
return nil
}
// Base64String returns the base64 representation of this SHA256Hash.
func (s SHA256Hash) Base64String() string {
return base64.StdEncoding.EncodeToString(s[:])
}
// MarshalJSON implements the json.Marshaller interface for SHA256Hash.
func (s SHA256Hash) MarshalJSON() ([]byte, error) {
return []byte(`"` + s.Base64String() + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (s *SHA256Hash) UnmarshalJSON(b []byte) error {
var content string
if err := json.Unmarshal(b, &content); err != nil {
return fmt.Errorf("failed to unmarshal SHA256Hash: %v", err)
}
return s.FromBase64String(content)
}
// SignedTreeHead represents the structure returned by the get-sth CT method
// after base64 decoding; see sections 3.5 and 4.3.
type SignedTreeHead struct {
Version Version `json:"sth_version"` // The version of the protocol to which the STH conforms
TreeSize uint64 `json:"tree_size"` // The number of entries in the new tree
Timestamp uint64 `json:"timestamp"` // The time at which the STH was created
SHA256RootHash SHA256Hash `json:"sha256_root_hash"` // The root hash of the log's Merkle tree
TreeHeadSignature DigitallySigned `json:"tree_head_signature"` // Log's signature over a TLS-encoded TreeHeadSignature
LogID SHA256Hash `json:"log_id"` // The SHA256 hash of the log's public key
}
// TreeHeadSignature holds the data over which the signature in an STH is
// generated; see section 3.5
type TreeHeadSignature struct {
Version Version `tls:"maxval:255"`
SignatureType SignatureType `tls:"maxval:255"` // == TreeHashSignatureType
Timestamp uint64
TreeSize uint64
SHA256RootHash SHA256Hash
}
// SignedCertificateTimestamp represents the structure returned by the
// add-chain and add-pre-chain methods after base64 decoding; see sections
// 3.2, 4.1 and 4.2.
type SignedCertificateTimestamp struct {
SCTVersion Version `tls:"maxval:255"`
LogID LogID
Timestamp uint64
Extensions CTExtensions `tls:"minlen:0,maxlen:65535"`
Signature DigitallySigned // Signature over TLS-encoded CertificateTimestamp
}
// CertificateTimestamp is the collection of data that the signature in an
// SCT is over; see section 3.2.
type CertificateTimestamp struct {
SCTVersion Version `tls:"maxval:255"`
SignatureType SignatureType `tls:"maxval:255"`
Timestamp uint64
EntryType LogEntryType `tls:"maxval:65535"`
X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"`
PrecertEntry *PreCert `tls:"selector:EntryType,val:1"`
JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"`
Extensions CTExtensions `tls:"minlen:0,maxlen:65535"`
}
func (s SignedCertificateTimestamp) String() string {
return fmt.Sprintf("{Version:%d LogId:%s Timestamp:%d Extensions:'%s' Signature:%v}", s.SCTVersion,
base64.StdEncoding.EncodeToString(s.LogID.KeyID[:]),
s.Timestamp,
s.Extensions,
s.Signature)
}
// TimestampedEntry is part of the MerkleTreeLeaf structure; see section 3.4.
type TimestampedEntry struct {
Timestamp uint64
EntryType LogEntryType `tls:"maxval:65535"`
X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"`
PrecertEntry *PreCert `tls:"selector:EntryType,val:1"`
JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"`
Extensions CTExtensions `tls:"minlen:0,maxlen:65535"`
}
// MerkleTreeLeaf represents the deserialized structure of the hash input for the
// leaves of a log's Merkle tree; see section 3.4.
type MerkleTreeLeaf struct {
Version Version `tls:"maxval:255"`
LeafType MerkleLeafType `tls:"maxval:255"`
TimestampedEntry *TimestampedEntry `tls:"selector:LeafType,val:0"`
}
// Precertificate represents the parsed CT Precertificate structure.
type Precertificate struct {
// DER-encoded pre-certificate as originally added, which includes a
// poison extension and a signature generated over the pre-cert by
// the pre-cert issuer (which might differ from the issuer of the final
// cert, see RFC6962 s3.1).
Submitted ASN1Cert
// SHA256 hash of the issuing key
IssuerKeyHash [sha256.Size]byte
// Parsed TBSCertificate structure, held in an x509.Certificate for convenience.
TBSCertificate *x509.Certificate
}
// X509Certificate returns the X.509 Certificate contained within the
// MerkleTreeLeaf.
func (m *MerkleTreeLeaf) X509Certificate() (*x509.Certificate, error) {
if m.TimestampedEntry.EntryType != X509LogEntryType {
return nil, fmt.Errorf("cannot call X509Certificate on a MerkleTreeLeaf that is not an X509 entry")
}
return x509.ParseCertificate(m.TimestampedEntry.X509Entry.Data)
}
// Precertificate returns the X.509 Precertificate contained within the MerkleTreeLeaf.
//
// The returned precertificate is embedded in an x509.Certificate, but is in the
// form stored internally in the log rather than the original submitted form
// (i.e. it does not include the poison extension and any changes to reflect the
// final certificate's issuer have been made; see x509.BuildPrecertTBS).
func (m *MerkleTreeLeaf) Precertificate() (*x509.Certificate, error) {
if m.TimestampedEntry.EntryType != PrecertLogEntryType {
return nil, fmt.Errorf("cannot call Precertificate on a MerkleTreeLeaf that is not a precert entry")
}
return x509.ParseTBSCertificate(m.TimestampedEntry.PrecertEntry.TBSCertificate)
}
// URI paths for Log requests; see section 4.
const (
AddChainPath = "/ct/v1/add-chain"
AddPreChainPath = "/ct/v1/add-pre-chain"
GetSTHPath = "/ct/v1/get-sth"
GetEntriesPath = "/ct/v1/get-entries"
GetProofByHashPath = "/ct/v1/get-proof-by-hash"
GetSTHConsistencyPath = "/ct/v1/get-sth-consistency"
GetRootsPath = "/ct/v1/get-roots"
GetEntryAndProofPath = "/ct/v1/get-entry-and-proof"
AddJSONPath = "/ct/v1/add-json" // Experimental addition
)
// AddChainRequest represents the JSON request body sent to the add-chain and
// add-pre-chain POST methods from sections 4.1 and 4.2.
type AddChainRequest struct {
Chain [][]byte `json:"chain"`
}
// AddChainResponse represents the JSON response to the add-chain and
// add-pre-chain POST methods.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type AddChainResponse struct {
SCTVersion Version `json:"sct_version"` // SCT structure version
ID []byte `json:"id"` // Log ID
Timestamp uint64 `json:"timestamp"` // Timestamp of issuance
Extensions string `json:"extensions"` // Holder for any CT extensions
Signature []byte `json:"signature"` // Log signature for this SCT
}
// AddJSONRequest represents the JSON request body sent to the add-json POST method.
// The corresponding response re-uses AddChainResponse.
// This is an experimental addition not covered by RFC6962.
type AddJSONRequest struct {
Data interface{} `json:"data"`
}
// GetSTHResponse respresents the JSON response to the get-sth GET method from section 4.3.
type GetSTHResponse struct {
TreeSize uint64 `json:"tree_size"` // Number of certs in the current tree
Timestamp uint64 `json:"timestamp"` // Time that the tree was created
SHA256RootHash []byte `json:"sha256_root_hash"` // Root hash of the tree
TreeHeadSignature []byte `json:"tree_head_signature"` // Log signature for this STH
}
// 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'.)
type GetSTHConsistencyResponse struct {
Consistency [][]byte `json:"consistency"`
}
// GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET
// method from section 4.5. (The corresponding GET request has parameters 'hash'
// and 'tree_size'.)
type GetProofByHashResponse struct {
LeafIndex int64 `json:"leaf_index"` // The 0-based index of the end entity corresponding to the "hash" parameter.
AuditPath [][]byte `json:"audit_path"` // An array of base64-encoded Merkle Tree nodes proving the inclusion of the chosen certificate.
}
// LeafEntry represents a leaf in the Log's Merkle tree, as returned by the get-entries
// GET method from section 4.6.
type LeafEntry struct {
// LeafInput is a TLS-encoded MerkleTreeLeaf
LeafInput []byte `json:"leaf_input"`
// ExtraData holds (unsigned) extra data, normally the cert validation chain.
ExtraData []byte `json:"extra_data"`
}
// GetEntriesResponse respresents the JSON response to the get-entries GET method
// from section 4.6.
type GetEntriesResponse struct {
Entries []LeafEntry `json:"entries"` // the list of returned entries
}
// GetRootsResponse represents the JSON response to the get-roots GET method from section 4.7.
type GetRootsResponse struct {
Certificates []string `json:"certificates"`
}
// GetEntryAndProofResponse represents the JSON response to the get-entry-and-proof
// GET method from section 4.8. (The corresponding GET request has parameters 'leaf_index'
// and 'tree_size'.)
type GetEntryAndProofResponse struct {
LeafInput []byte `json:"leaf_input"` // the entry itself
ExtraData []byte `json:"extra_data"` // any chain provided when the entry was added to the log
AuditPath [][]byte `json:"audit_path"` // the corresponding proof
}

View File

@ -0,0 +1,66 @@
// Copyright 2015 Google Inc. 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 ct
import (
"encoding/hex"
"strings"
"testing"
"github.com/google/certificate-transparency-go/tls"
)
const (
CertEntry = "000000000149a6e03abe00000006513082064d30820535a003020102020c6a5d4161f5c9b68043270b0c300d06092a864886f70d0101050500305e310b300906035504061302424531193017060355040a1310476c6f62616c5369676e206e762d7361313430320603550403132b476c6f62616c5369676e20457874656e6465642056616c69646174696f6e204341202d2047322054455354301e170d3134313131333031353830315a170d3136313131333031353830315a3082011331183016060355040f0c0f427573696e65737320456e74697479311230100603550405130936363636363636363631133011060b2b0601040182373c0201031302444531293027060b2b0601040182373c02010113186576206a7572697364696374696f6e206c6f63616c69747931263024060b2b0601040182373c02010213156576206a7572697364696374696f6e207374617465310b3009060355040613024a50310a300806035504080c0153310a300806035504070c014c311530130603550409130c657620616464726573732033310c300a060355040b0c034f5531310c300a060355040b0c034f5532310a3008060355040a0c014f3117301506035504030c0e637372636e2e73736c32342e6a7030820122300d06092a864886f70d01010105000382010f003082010a02820101008db9f0d6b359466dffe95ba43dc1a5680eedc8f3cabbc573a236a109bf6e58df816c7bb8156147ab526eceaffd0576e6e1c09ea33433e114d7e5038c697298c7957f01a7e1142320847cf234995bbe42798340cb99e6a7e2cfa950277aef6e02f4d96ddceb0af9541171b0f8f1aa4f0d02453e6e654b25a13f2aff4357cae8177d3bd21855686591a2309d9ff5dead8240304e22eafcc5508587e6b6ad1d00b53c28e5b936269afbf214b73edbdc8a48a86c1c23f3dce55fcce60502c0908bca9bdb22c16c0b34d11b4fd27e9d7bcb56c5ec0fc4d52500fb06b0af5c4112e421022b78b31030cb73e9fd92ffc65919fd8f35e604fcaf025b9c77e3e5dff749a70203010001a38202523082024e300e0603551d0f0101ff0404030205a0304c0603551d2004453043304106092b06010401a03201013034303206082b06010505070201162668747470733a2f2f7777772e676c6f62616c7369676e2e636f6d2f7265706f7369746f72792f30480603551d1f0441303f303da03ba0398637687474703a2f2f63726c2e676c6f62616c7369676e2e636f6d2f67732f67736f7267616e697a6174696f6e76616c63617467322e63726c30819c06082b0601050507010104818f30818c304a06082b06010505073002863e687474703a2f2f7365637572652e676c6f62616c7369676e2e636f6d2f6361636572742f67736f7267616e697a6174696f6e76616c63617467322e637274303e06082b060105050730018632687474703a2f2f6f637370322e676c6f62616c7369676e2e636f6d2f67736f7267616e697a6174696f6e76616c6361746732301d0603551d250416301406082b0601050507030106082b0601050507030230190603551d1104123010820e637372636e2e73736c32342e6a70301d0603551d0e041604147f834b2903e35efff651619083a2efd69a6d70f4301f0603551d23041830168014ab30a406d972d0029ab2c7d3f4241be2fca5320230818a060a2b06010401d679020402047c047a0078007600b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c7700000149a6dc346b00000403004730450220469f4dc0553b7832bd56633c3b9d53faaec84df414b7a05ab1b2d544d146ac3e022100ee899419fd4f95544798f7883fe093692feb4c90e84d651600f7019166a43701300d06092a864886f70d010105050003820101007dcd3e228d68cdc0734c7629fd7d40cd742d0ed1d0d9f49a643af12dcdbc61394638b7c519bb7cae530ccdc3a5037d5cdd8a4d2c01abdc834daf1993f7a22ee2c223377a94da4e68ac69a0b50d2d473ec77651e001c5f71a23cc2defe7616fd6c6491aa7f9a2bb16b930ce3f8cc37cf6a47bfb04fd4eff7db8433cc6fdb05146a4a31fe65211875f2c51129bf0729ce2dc7ce1a5afc6eaa1eb3a36296cb9e091375edfc408c727f6d54bba408da60b46c496a364c504adf47ee0496a9260fe223c8b23c14832635c3dff0dba8a0c8cdd957a77f18443b7782a9b6c7636b7d66df426350b959537e911888e45b2c0b218e50d03fdcfa7f758e8e60dd1a1996bc00000"
PrecertEntry = "00000000014b4981f0c800013760e2790f33a498f9b6c149fecfca3993954b536fbf36ad45d0a8415b79337d00047a30820476a00302010202100532298c396a3e25fcaa1977e827b5f3300d06092a864886f70d01010b0500306d310b300906035504061302555331163014060355040a130d47656f547275737420496e632e311f301d060355040b1316464f52205445535420505552504f534553204f4e4c59312530230603550403131c47656f54727573742045562053534c2054455354204341202d204734301e170d3135303230323030303030305a170d3136303232373233353935395a3081c331133011060b2b0601040182373c02010313024742311b3019060b2b0601040182373c020102140a43616c69666f726e6961311e301c060b2b0601040182373c0201010c0d4d6f756e7461696e2056696577310b30090603550406130247423113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e2056696577311d301b060355040a0c1453796d616e74656320436f72706f726174696f6e3116301406035504030c0d736466656473662e747275737430820122300d06092a864886f70d01010105000382010f003082010a0282010100b19d97def39ff829c65ea099a3257298b33ff675451fdc5641a222347aee4a56201f4c1a406f2f19815d86dec1a611768e7d556c8e33a7f1b4c78db19cceae540e97ae1f0660b2ee4f8cff2045b84a9da228349744406eceaed0b08d46fdab3543b3d86ea708627a61a529b793a76adc6b776bc8d5b3d4fe21e2c4aa92cfd33b45e7412068e0683a2beffad1df2fc320b8ddbf02ffb603d2cf74798277fd9656b5acd45659b0e5d761e02dcf95c53095555a931ad5bfa9b4967c045d5f12de2d6b537cd93af2ad8b45e5540bd43279876d13e376fb649778e10dfa56165b901bd37e9dee4e46027b4c0732ca7ed64491862abaf6a24a4aaed8f49a0922ca4fb50203010001a38201d1308201cd30470603551d110440303e820d6b6a61736468662e7472757374820b73736466732e7472757374820d736466656473662e747275737482117777772e736466656473662e747275737430090603551d1304023000300e0603551d0f0101ff0404030205a0302b0603551d1f042430223020a01ea01c861a687474703a2f2f676d2e73796d63622e636f6d2f676d2e63726c3081a00603551d2004819830819530819206092b06010401f0220106308184303f06082b06010505070201163368747470733a2f2f7777772e67656f74727573742e636f6d2f7265736f75726365732f7265706f7369746f72792f6c6567616c304106082b0601050507020230350c3368747470733a2f2f7777772e67656f74727573742e636f6d2f7265736f75726365732f7265706f7369746f72792f6c6567616c301d0603551d250416301406082b0601050507030106082b06010505070302301f0603551d23041830168014b1699461abe6cb0c4ce759af5a498b1833c1e147305706082b06010505070101044b3049301f06082b060105050730018613687474703a2f2f676d2e73796d63642e636f6d302606082b06010505073002861a687474703a2f2f676d2e73796d63622e636f6d2f676d2e6372740000"
)
func TestUnmarshalMerkleTreeLeaf(t *testing.T) {
var tests = []struct {
in string // hex string
want LogEntryType
errstr string
}{
{CertEntry, X509LogEntryType, ""},
{PrecertEntry, PrecertLogEntryType, ""},
{"001234", 0, "LeafType: unhandled value"},
}
for _, test := range tests {
inData, _ := hex.DecodeString(test.in)
var got MerkleTreeLeaf
_, err := tls.Unmarshal(inData, &got)
if test.errstr != "" {
if err == nil {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=%+v,nil; want error %q", test.in, got, test.errstr)
} else if !strings.Contains(err.Error(), test.errstr) {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=nil,%q; want error %q", test.in, err.Error(), test.errstr)
}
continue
}
if err != nil {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=nil,%q; want type %v", test.in, err.Error(), test.want)
continue
}
if got.Version != V1 {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=version=%v,nil; want version 1", test.in, got.Version)
}
if got.LeafType != TimestampedEntryLeafType {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=LeafType=%v,nil; want LeafType=%v", test.in, got.LeafType, TimestampedEntryLeafType)
}
if got.TimestampedEntry.EntryType != test.want {
t.Errorf("tls.Unmarshal(%s, &MerkleTreeLeaf)=EntryType=%v,nil; want LeafType=%v", test.in, got.TimestampedEntry.EntryType, test.want)
}
}
}

View File

@ -0,0 +1,143 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"encoding/pem"
"errors"
"runtime"
)
// CertPool is a set of certificates.
type CertPool struct {
bySubjectKeyId map[string][]int
byName map[string][]int
certs []*Certificate
}
// NewCertPool returns a new, empty CertPool.
func NewCertPool() *CertPool {
return &CertPool{
bySubjectKeyId: make(map[string][]int),
byName: make(map[string][]int),
}
}
// SystemCertPool returns a copy of the system cert pool.
//
// Any mutations to the returned pool are not written to disk and do
// not affect any other pool.
func SystemCertPool() (*CertPool, error) {
if runtime.GOOS == "windows" {
// Issue 16736, 18609:
return nil, errors.New("crypto/x509: system root pool is not available on Windows")
}
return loadSystemRoots()
}
// findVerifiedParents attempts to find certificates in s which have signed the
// given certificate. If any candidates were rejected then errCert will be set
// to one of them, arbitrarily, and err will contain the reason that it was
// rejected.
func (s *CertPool) findVerifiedParents(cert *Certificate) (parents []int, errCert *Certificate, err error) {
if s == nil {
return
}
var candidates []int
if len(cert.AuthorityKeyId) > 0 {
candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)]
}
if len(candidates) == 0 {
candidates = s.byName[string(cert.RawIssuer)]
}
for _, c := range candidates {
if err = cert.CheckSignatureFrom(s.certs[c]); err == nil {
parents = append(parents, c)
} else {
errCert = s.certs[c]
}
}
return
}
func (s *CertPool) contains(cert *Certificate) bool {
if s == nil {
return false
}
candidates := s.byName[string(cert.RawSubject)]
for _, c := range candidates {
if s.certs[c].Equal(cert) {
return true
}
}
return false
}
// AddCert adds a certificate to a pool.
func (s *CertPool) AddCert(cert *Certificate) {
if cert == nil {
panic("adding nil Certificate to CertPool")
}
// Check that the certificate isn't being added twice.
if s.contains(cert) {
return
}
n := len(s.certs)
s.certs = append(s.certs, cert)
if len(cert.SubjectKeyId) > 0 {
keyId := string(cert.SubjectKeyId)
s.bySubjectKeyId[keyId] = append(s.bySubjectKeyId[keyId], n)
}
name := string(cert.RawSubject)
s.byName[name] = append(s.byName[name], n)
}
// AppendCertsFromPEM attempts to parse a series of PEM encoded certificates.
// It appends any certificates found to s and reports whether any certificates
// were successfully parsed.
//
// On many Linux systems, /etc/ssl/cert.pem will contain the system wide set
// of root CAs in a format suitable for this function.
func (s *CertPool) 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 != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := ParseCertificate(block.Bytes)
if err != nil {
continue
}
s.AddCert(cert)
ok = true
}
return
}
// Subjects returns a list of the DER-encoded subjects of
// all of the certificates in the pool.
func (s *CertPool) Subjects() [][]byte {
res := make([][]byte, len(s.certs))
for i, c := range s.certs {
res[i] = c.RawSubject
}
return res
}

View File

@ -0,0 +1,230 @@
package x509
import (
"bytes"
"fmt"
"strconv"
"strings"
)
// Error implements the error interface and describes a single error in an X.509 certificate or CRL.
type Error struct {
ID ErrorID
Category ErrCategory
Summary string
Field string
SpecRef string
SpecText string
// Fatal indicates that parsing has been aborted.
Fatal bool
}
func (err Error) Error() string {
var msg bytes.Buffer
if err.ID != ErrInvalidID {
if err.Fatal {
msg.WriteRune('E')
} else {
msg.WriteRune('W')
}
msg.WriteString(fmt.Sprintf("%03d: ", err.ID))
}
msg.WriteString(err.Summary)
return msg.String()
}
// VerboseError creates a more verbose error string, including spec details.
func (err Error) VerboseError() string {
var msg bytes.Buffer
msg.WriteString(err.Error())
if len(err.Field) > 0 || err.Category != UnknownCategory || len(err.SpecRef) > 0 || len(err.SpecText) > 0 {
msg.WriteString(" (")
needSep := false
if len(err.Field) > 0 {
msg.WriteString(err.Field)
needSep = true
}
if err.Category != UnknownCategory {
if needSep {
msg.WriteString(": ")
}
msg.WriteString(err.Category.String())
needSep = true
}
if len(err.SpecRef) > 0 {
if needSep {
msg.WriteString(": ")
}
msg.WriteString(err.SpecRef)
needSep = true
}
if len(err.SpecText) > 0 {
if needSep {
if len(err.SpecRef) > 0 {
msg.WriteString(", ")
} else {
msg.WriteString(": ")
}
}
msg.WriteString("'")
msg.WriteString(err.SpecText)
msg.WriteString("'")
}
msg.WriteString(")")
}
return msg.String()
}
// ErrCategory indicates the category of an x509.Error.
type ErrCategory int
// ErrCategory values.
const (
UnknownCategory ErrCategory = iota
// Errors in ASN.1 encoding
InvalidASN1Encoding
InvalidASN1Content
InvalidASN1DER
// Errors in ASN.1 relative to schema
InvalidValueRange
InvalidASN1Type
UnexpectedAdditionalData
// Errors in X.509
PoorlyFormedCertificate // Fails a SHOULD clause
MalformedCertificate // Fails a MUST clause
PoorlyFormedCRL // Fails a SHOULD clause
MalformedCRL // Fails a MUST clause
// Errors relative to CA/Browser Forum guidelines
BaselineRequirementsFailure
EVRequirementsFailure
// Other errors
InsecureAlgorithm
UnrecognizedValue
)
func (category ErrCategory) String() string {
switch category {
case InvalidASN1Encoding:
return "Invalid ASN.1 encoding"
case InvalidASN1Content:
return "Invalid ASN.1 content"
case InvalidASN1DER:
return "Invalid ASN.1 distinguished encoding"
case InvalidValueRange:
return "Invalid value for range given in schema"
case InvalidASN1Type:
return "Invalid ASN.1 type for schema"
case UnexpectedAdditionalData:
return "Unexpected additional data present"
case PoorlyFormedCertificate:
return "Certificate does not comply with SHOULD clause in spec"
case MalformedCertificate:
return "Certificate does not comply with MUST clause in spec"
case PoorlyFormedCRL:
return "Certificate Revocation List does not comply with SHOULD clause in spec"
case MalformedCRL:
return "Certificate Revocation List does not comply with MUST clause in spec"
case BaselineRequirementsFailure:
return "Certificate does not comply with CA/BF baseline requirements"
case EVRequirementsFailure:
return "Certificate does not comply with CA/BF EV requirements"
case InsecureAlgorithm:
return "Certificate uses an insecure algorithm"
case UnrecognizedValue:
return "Certificate uses an unrecognized value"
default:
return fmt.Sprintf("Unknown (%d)", category)
}
}
// ErrorID is an identifier for an x509.Error, to allow filtering.
type ErrorID int
// Errors implements the error interface and holds a collection of errors found in a certificate or CRL.
type Errors struct {
Errs []Error
}
// Error converts to a string.
func (e *Errors) Error() string {
return e.combineErrors(Error.Error)
}
// VerboseError creates a more verbose error string, including spec details.
func (e *Errors) VerboseError() string {
return e.combineErrors(Error.VerboseError)
}
// Fatal indicates whether e includes a fatal error
func (e *Errors) Fatal() bool {
return (e.FirstFatal() != nil)
}
// Empty indicates whether e has no errors.
func (e *Errors) Empty() bool {
return len(e.Errs) == 0
}
// FirstFatal returns the first fatal error in e, or nil
// if there is no fatal error.
func (e *Errors) FirstFatal() error {
for _, err := range e.Errs {
if err.Fatal {
return err
}
}
return nil
}
// AddID adds the Error identified by the given id to an x509.Errors.
func (e *Errors) AddID(id ErrorID, args ...interface{}) {
e.Errs = append(e.Errs, NewError(id, args...))
}
func (e Errors) combineErrors(errfn func(Error) string) string {
if len(e.Errs) == 0 {
return ""
}
if len(e.Errs) == 1 {
return errfn((e.Errs)[0])
}
var msg bytes.Buffer
msg.WriteString("Errors:")
for _, err := range e.Errs {
msg.WriteString("\n ")
msg.WriteString(errfn(err))
}
return msg.String()
}
// Filter creates a new Errors object with any entries from the filtered
// list of IDs removed.
func (e Errors) Filter(filtered []ErrorID) Errors {
var results Errors
eloop:
for _, v := range e.Errs {
for _, f := range filtered {
if v.ID == f {
break eloop
}
}
results.Errs = append(results.Errs, v)
}
return results
}
// ErrorFilter builds a list of error IDs (suitable for use with Errors.Filter) from a comma-separated string.
func ErrorFilter(ignore string) []ErrorID {
var ids []ErrorID
filters := strings.Split(ignore, ",")
for _, f := range filters {
v, err := strconv.Atoi(f)
if err != nil {
continue
}
ids = append(ids, ErrorID(v))
}
return ids
}

View File

@ -0,0 +1,192 @@
package x509_test
import (
"fmt"
"testing"
"github.com/google/certificate-transparency-go/x509"
)
func TestErrors(t *testing.T) {
var tests = []struct {
errs []x509.Error
want string
wantVerbose string
wantFatal bool
}{
{
errs: []x509.Error{
{Summary: "Error", Field: "a.b.c"},
},
want: "Error",
wantVerbose: "Error (a.b.c)",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecRef: "RFC5280 s4.1.2.2",
SpecText: "The serial number MUST be a positive integer",
Category: x509.MalformedCertificate,
},
},
want: "Error",
wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: RFC5280 s4.1.2.2, 'The serial number MUST be a positive integer')",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecRef: "RFC5280 s4.1.2.2",
SpecText: "The serial number MUST be a positive integer",
},
},
want: "Error",
wantVerbose: "Error (a.b.c: RFC5280 s4.1.2.2, 'The serial number MUST be a positive integer')",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecRef: "RFC5280 s4.1.2.2",
Category: x509.MalformedCertificate,
},
},
want: "Error",
wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: RFC5280 s4.1.2.2)",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecText: "The serial number MUST be a positive integer",
Category: x509.MalformedCertificate,
},
},
want: "Error",
wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec: 'The serial number MUST be a positive integer')",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecRef: "RFC5280 s4.1.2.2",
},
},
want: "Error",
wantVerbose: "Error (a.b.c: RFC5280 s4.1.2.2)",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
SpecText: "The serial number MUST be a positive integer",
},
},
want: "Error",
wantVerbose: "Error (a.b.c: 'The serial number MUST be a positive integer')",
},
{
errs: []x509.Error{
{
Summary: "Error",
Field: "a.b.c",
Category: x509.MalformedCertificate,
},
},
want: "Error",
wantVerbose: "Error (a.b.c: Certificate does not comply with MUST clause in spec)",
},
{
errs: []x509.Error{
{Summary: "Error"},
},
want: "Error",
wantVerbose: "Error",
},
{
errs: []x509.Error{
{Summary: "Error\nwith newline", Field: "x", Category: x509.InvalidASN1DER},
},
want: "Error\nwith newline",
wantVerbose: "Error\nwith newline (x: Invalid ASN.1 distinguished encoding)",
},
{
errs: []x509.Error{
{Summary: "Error1", Field: "a.b.c"},
{Summary: "Error2", Field: "a.b.c.d"},
{Summary: "Error3", Field: "x.y.z"},
},
want: "Errors:\n Error1\n Error2\n Error3",
wantVerbose: "Errors:\n Error1 (a.b.c)\n Error2 (a.b.c.d)\n Error3 (x.y.z)",
},
{
errs: []x509.Error{
{Summary: "Error1", Field: "a.b.c"},
{Summary: "Error2", Field: "a.b.c.d", Fatal: true},
{Summary: "Error3", Field: "x.y.z"},
},
want: "Errors:\n Error1\n Error2\n Error3",
wantVerbose: "Errors:\n Error1 (a.b.c)\n Error2 (a.b.c.d)\n Error3 (x.y.z)",
wantFatal: true,
},
}
for _, test := range tests {
errs := x509.Errors{Errs: test.errs}
if got := errs.Error(); got != test.want {
t.Errorf("Errors(%+v).Error()=%q; want %q", test.errs, got, test.want)
}
if got := errs.VerboseError(); got != test.wantVerbose {
t.Errorf("Errors(%+v).VerboseError()=%q; want %q", test.errs, got, test.wantVerbose)
}
if got := errs.Fatal(); got != test.wantFatal {
t.Errorf("Errors(%+v).Fatal()=%v; want %v", test.errs, got, test.wantFatal)
}
}
}
func TestErrorsAppend(t *testing.T) {
var errs x509.Errors
if got, want := errs.Error(), ""; got != want {
t.Errorf("Errors().Error()=%q; want %q", got, want)
}
if got, want := errs.Empty(), true; got != want {
t.Errorf("Errors().Empty()=%t; want %t", got, want)
}
errs.Errs = append(errs.Errs, x509.Error{
Summary: "Error",
Field: "a.b.c",
SpecRef: "RFC5280 s4.1.2.2"})
if got, want := errs.VerboseError(), "Error (a.b.c: RFC5280 s4.1.2.2)"; got != want {
t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want)
}
if got, want := errs.Empty(), false; got != want {
t.Errorf("Errors().Empty()=%t; want %t", got, want)
}
}
func TestErrorsFilter(t *testing.T) {
var errs x509.Errors
id := x509.ErrMaxID + 2
errs.AddID(id, "arg1", 2, "arg3")
baseErr := errs.Error()
errs.AddID(x509.ErrMaxID + 1)
if got, want := errs.Error(), fmt.Sprintf("Errors:\n %s\n E%03d: Unknown error ID %v: args []", baseErr, x509.ErrMaxID+1, x509.ErrMaxID+1); got != want {
t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want)
}
errList := fmt.Sprintf("%d, %d", x509.ErrMaxID+1, x509.ErrMaxID+1)
filter := x509.ErrorFilter(errList)
errs2 := errs.Filter(filter)
if got, want := errs2.Error(), baseErr; got != want {
t.Errorf("Errors(%+v).Error=%q; want %q", errs, got, want)
}
}

View File

@ -0,0 +1,302 @@
package x509
import "fmt"
// To preserve error IDs, only append to this list, never insert.
const (
ErrInvalidID ErrorID = iota
ErrInvalidCertList
ErrTrailingCertList
ErrUnexpectedlyCriticalCertListExtension
ErrUnexpectedlyNonCriticalCertListExtension
ErrInvalidCertListAuthKeyID
ErrTrailingCertListAuthKeyID
ErrInvalidCertListIssuerAltName
ErrInvalidCertListCRLNumber
ErrTrailingCertListCRLNumber
ErrNegativeCertListCRLNumber
ErrInvalidCertListDeltaCRL
ErrTrailingCertListDeltaCRL
ErrNegativeCertListDeltaCRL
ErrInvalidCertListIssuingDP
ErrTrailingCertListIssuingDP
ErrCertListIssuingDPMultipleTypes
ErrCertListIssuingDPInvalidFullName
ErrInvalidCertListFreshestCRL
ErrInvalidCertListAuthInfoAccess
ErrTrailingCertListAuthInfoAccess
ErrUnhandledCriticalCertListExtension
ErrUnexpectedlyCriticalRevokedCertExtension
ErrUnexpectedlyNonCriticalRevokedCertExtension
ErrInvalidRevocationReason
ErrTrailingRevocationReason
ErrInvalidRevocationInvalidityDate
ErrTrailingRevocationInvalidityDate
ErrInvalidRevocationIssuer
ErrUnhandledCriticalRevokedCertExtension
ErrMaxID
)
// idToError gives a template x509.Error for each defined ErrorID; where the Summary
// field may hold format specifiers that take field parameters.
var idToError map[ErrorID]Error
var errorInfo = []Error{
{
ID: ErrInvalidCertList,
Summary: "x509: failed to parse CertificateList: %v",
Field: "CertificateList",
SpecRef: "RFC 5280 s5.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertList,
Summary: "x509: trailing data after CertificateList",
Field: "CertificateList",
SpecRef: "RFC 5280 s5.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrUnexpectedlyCriticalCertListExtension,
Summary: "x509: certificate list extension %v marked critical but expected to be non-critical",
Field: "tbsCertList.crlExtensions.*.critical",
SpecRef: "RFC 5280 s5.2",
Category: MalformedCRL,
},
{
ID: ErrUnexpectedlyNonCriticalCertListExtension,
Summary: "x509: certificate list extension %v marked non-critical but expected to be critical",
Field: "tbsCertList.crlExtensions.*.critical",
SpecRef: "RFC 5280 s5.2",
Category: MalformedCRL,
},
{
ID: ErrInvalidCertListAuthKeyID,
Summary: "x509: failed to unmarshal certificate-list authority key-id: %v",
Field: "tbsCertList.crlExtensions.*.AuthorityKeyIdentifier",
SpecRef: "RFC 5280 s5.2.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertListAuthKeyID,
Summary: "x509: trailing data after certificate list auth key ID",
Field: "tbsCertList.crlExtensions.*.AuthorityKeyIdentifier",
SpecRef: "RFC 5280 s5.2.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidCertListIssuerAltName,
Summary: "x509: failed to parse CRL issuer alt name: %v",
Field: "tbsCertList.crlExtensions.*.IssuerAltName",
SpecRef: "RFC 5280 s5.2.2",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidCertListCRLNumber,
Summary: "x509: failed to unmarshal certificate-list crl-number: %v",
Field: "tbsCertList.crlExtensions.*.CRLNumber",
SpecRef: "RFC 5280 s5.2.3",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertListCRLNumber,
Summary: "x509: trailing data after certificate list crl-number",
Field: "tbsCertList.crlExtensions.*.CRLNumber",
SpecRef: "RFC 5280 s5.2.3",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrNegativeCertListCRLNumber,
Summary: "x509: negative certificate list crl-number: %d",
Field: "tbsCertList.crlExtensions.*.CRLNumber",
SpecRef: "RFC 5280 s5.2.3",
Category: MalformedCRL,
Fatal: true,
},
{
ID: ErrInvalidCertListDeltaCRL,
Summary: "x509: failed to unmarshal certificate-list delta-crl: %v",
Field: "tbsCertList.crlExtensions.*.BaseCRLNumber",
SpecRef: "RFC 5280 s5.2.4",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertListDeltaCRL,
Summary: "x509: trailing data after certificate list delta-crl",
Field: "tbsCertList.crlExtensions.*.BaseCRLNumber",
SpecRef: "RFC 5280 s5.2.4",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrNegativeCertListDeltaCRL,
Summary: "x509: negative certificate list base-crl-number: %d",
Field: "tbsCertList.crlExtensions.*.BaseCRLNumber",
SpecRef: "RFC 5280 s5.2.4",
Category: MalformedCRL,
Fatal: true,
},
{
ID: ErrInvalidCertListIssuingDP,
Summary: "x509: failed to unmarshal certificate list issuing distribution point: %v",
Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint",
SpecRef: "RFC 5280 s5.2.5",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertListIssuingDP,
Summary: "x509: trailing data after certificate list issuing distribution point",
Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint",
SpecRef: "RFC 5280 s5.2.5",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrCertListIssuingDPMultipleTypes,
Summary: "x509: multiple cert types set in issuing-distribution-point: user:%v CA:%v attr:%v",
Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint",
SpecRef: "RFC 5280 s5.2.5",
SpecText: "at most one of onlyContainsUserCerts, onlyContainsCACerts, and onlyContainsAttributeCerts may be set to TRUE.",
Category: MalformedCRL,
Fatal: true,
},
{
ID: ErrCertListIssuingDPInvalidFullName,
Summary: "x509: failed to parse CRL issuing-distribution-point fullName: %v",
Field: "tbsCertList.crlExtensions.*.IssuingDistributionPoint.distributionPoint",
SpecRef: "RFC 5280 s5.2.5",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidCertListFreshestCRL,
Summary: "x509: failed to unmarshal certificate list freshestCRL: %v",
Field: "tbsCertList.crlExtensions.*.FreshestCRL",
SpecRef: "RFC 5280 s5.2.6",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidCertListAuthInfoAccess,
Summary: "x509: failed to unmarshal certificate list authority info access: %v",
Field: "tbsCertList.crlExtensions.*.AuthorityInfoAccess",
SpecRef: "RFC 5280 s5.2.7",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingCertListAuthInfoAccess,
Summary: "x509: trailing data after certificate list authority info access",
Field: "tbsCertList.crlExtensions.*.AuthorityInfoAccess",
SpecRef: "RFC 5280 s5.2.7",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrUnhandledCriticalCertListExtension,
Summary: "x509: unhandled critical extension in certificate list: %v",
Field: "tbsCertList.revokedCertificates.crlExtensions.*",
SpecRef: "RFC 5280 s5.2",
SpecText: "If a CRL contains a critical extension that the application cannot process, then the application MUST NOT use that CRL to determine the status of certificates.",
Category: MalformedCRL,
Fatal: true,
},
{
ID: ErrUnexpectedlyCriticalRevokedCertExtension,
Summary: "x509: revoked certificate extension %v marked critical but expected to be non-critical",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.critical",
SpecRef: "RFC 5280 s5.3",
Category: MalformedCRL,
},
{
ID: ErrUnexpectedlyNonCriticalRevokedCertExtension,
Summary: "x509: revoked certificate extension %v marked non-critical but expected to be critical",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.critical",
SpecRef: "RFC 5280 s5.3",
Category: MalformedCRL,
},
{
ID: ErrInvalidRevocationReason,
Summary: "x509: failed to parse revocation reason: %v",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CRLReason",
SpecRef: "RFC 5280 s5.3.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingRevocationReason,
Summary: "x509: trailing data after revoked certificate reason",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CRLReason",
SpecRef: "RFC 5280 s5.3.1",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidRevocationInvalidityDate,
Summary: "x509: failed to parse revoked certificate invalidity date: %v",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.InvalidityDate",
SpecRef: "RFC 5280 s5.3.2",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrTrailingRevocationInvalidityDate,
Summary: "x509: trailing data after revoked certificate invalidity date",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.InvalidityDate",
SpecRef: "RFC 5280 s5.3.2",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrInvalidRevocationIssuer,
Summary: "x509: failed to parse revocation issuer %v",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*.CertificateIssuer",
SpecRef: "RFC 5280 s5.3.3",
Category: InvalidASN1Content,
Fatal: true,
},
{
ID: ErrUnhandledCriticalRevokedCertExtension,
Summary: "x509: unhandled critical extension in revoked certificate: %v",
Field: "tbsCertList.revokedCertificates.crlEntryExtensions.*",
SpecRef: "RFC 5280 s5.3",
SpecText: "If a CRL contains a critical CRL entry extension that the application cannot process, then the application MUST NOT use that CRL to determine the status of any certificates.",
Category: MalformedCRL,
Fatal: true,
},
}
func init() {
idToError = make(map[ErrorID]Error, len(errorInfo))
for _, info := range errorInfo {
idToError[info.ID] = info
}
}
// NewError builds a new x509.Error based on the template for the given id.
func NewError(id ErrorID, args ...interface{}) Error {
var err Error
if id >= ErrMaxID {
err.ID = id
err.Summary = fmt.Sprintf("Unknown error ID %v: args %+v", id, args)
err.Fatal = true
} else {
err = idToError[id]
err.Summary = fmt.Sprintf(err.Summary, args...)
}
return err
}

View File

@ -0,0 +1,13 @@
package x509
import (
"testing"
)
func TestTemplateIDs(t *testing.T) {
for id, template := range idToError {
if template.ID != id {
t.Errorf("idToError[%v].id=%v; want %v", id, template.ID, id)
}
}
}

View File

@ -0,0 +1,135 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509_test
import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"encoding/pem"
"fmt"
"github.com/google/certificate-transparency-go/x509"
)
func ExampleCertificate_Verify() {
// Verifying with a custom list of root certificates.
const rootPEM = `
-----BEGIN CERTIFICATE-----
MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
-----END CERTIFICATE-----`
const certPEM = `
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfRrObuSW5T7q
5CnSEqefEmtH4CCv6+5EckuriNr1CjfVvqzwfAhopXkLrq45EQm8vkmf7W96XJhC
7ZM0dYi1/qOCAU8wggFLMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAa
BgNVHREEEzARgg9tYWlsLmdvb2dsZS5jb20wCwYDVR0PBAQDAgeAMGgGCCsGAQUF
BwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcy
LmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5jb20vb2Nz
cDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/BAIwADAf
BgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHSAEEDAOMAwGCisG
AQQB1nkCBQEwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29nbGUuY29t
L0dJQUcyLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAH6RYHxHdcGpMpFE3oxDoFnP+
gtuBCHan2yE2GRbJ2Cw8Lw0MmuKqHlf9RSeYfd3BXeKkj1qO6TVKwCh+0HdZk283
TZZyzmEOyclm3UGFYe82P/iDFt+CeQ3NpmBg+GoaVCuWAARJN/KfglbLyyYygcQq
0SgeDh8dRKUiaW3HQSoYvTvdTuqzwK4CXsr3b5/dAOY8uMuG/IAR3FgwTbZ1dtoW
RvOTa8hYiU6A475WuZKyEHcwnGYe57u2I2KbMgcKjPniocj4QzgYsVAVKW3IwaOh
yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA==
-----END CERTIFICATE-----`
// First, create the set of root certificates. For this example we only
// have one. It's also possible to omit this in order to use the
// default root set of the current operating system.
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
panic("failed to parse root certificate")
}
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
panic("failed to parse certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic("failed to parse certificate: " + err.Error())
}
opts := x509.VerifyOptions{
DNSName: "mail.google.com",
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
panic("failed to verify certificate: " + err.Error())
}
}
func ExampleParsePKIXPublicKey() {
const pubPEM = `
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlRuRnThUjU8/prwYxbty
WPT9pURI3lbsKMiB6Fn/VHOKE13p4D8xgOCADpdRagdT6n4etr9atzDKUSvpMtR3
CP5noNc97WiNCggBjVWhs7szEe8ugyqF23XwpHQ6uV1LKH50m92MbOWfCtjU9p/x
qhNpQQ1AZhqNy5Gevap5k8XzRmjSldNAFZMY7Yv3Gi+nyCwGwpVtBUwhuLzgNFK/
yDtw2WcWmUU7NuC8Q6MWvPebxVtCfVp/iQU6q60yyt6aGOBkhAX0LpKAEhKidixY
nP9PNVBvxgu3XZ4P36gZV6+ummKdBVnc3NqwBLu5+CcdRdusmHPHd5pHf4/38Z3/
6qU2a/fPvWzceVTEgZ47QjFMTCTmCwNt29cvi7zZeQzjtwQgn4ipN9NibRH/Ax/q
TbIzHfrJ1xa2RteWSdFjwtxi9C20HUkjXSeI4YlzQMH0fPX6KCE7aVePTOnB69I/
a9/q96DiXZajwlpq3wFctrs1oXqBp5DVrCIj8hU2wNgB7LtQ1mCtsYz//heai0K9
PhE4X6hiE0YmeAZjR0uHl8M/5aW9xCoJ72+12kKpWAa0SFRWLy6FejNYCYpkupVJ
yecLk/4L1W0l6jQQZnWErXZYe0PNFcmwGXy1Rep83kfBRNKRy5tvocalLlwXLdUk
AIU+2GKjyT3iMuzZxxFxPFMCAwEAAQ==
-----END PUBLIC KEY-----`
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
panic("failed to parse PEM block containing the public key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic("failed to parse DER encoded public key: " + err.Error())
}
switch pub := pub.(type) {
case *rsa.PublicKey:
fmt.Println("pub is of type RSA:", pub)
case *dsa.PublicKey:
fmt.Println("pub is of type DSA:", pub)
case *ecdsa.PublicKey:
fmt.Println("pub is of type ECDSA:", pub)
default:
panic("unknown type of public key")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,164 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"fmt"
"net"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509/pkix"
)
const (
// GeneralName tag values from RFC 5280, 4.2.1.6
tagOtherName = 0
tagRFC822Name = 1
tagDNSName = 2
tagX400Address = 3
tagDirectoryName = 4
tagEDIPartyName = 5
tagURI = 6
tagIPAddress = 7
tagRegisteredID = 8
)
// OtherName describes a name related to a certificate which is not in one
// of the standard name formats. RFC 5280, 4.2.1.6:
// OtherName ::= SEQUENCE {
// type-id OBJECT IDENTIFIER,
// value [0] EXPLICIT ANY DEFINED BY type-id }
type OtherName struct {
TypeID asn1.ObjectIdentifier
Value asn1.RawValue
}
// GeneralNames holds a collection of names related to a certificate.
type GeneralNames struct {
DNSNames []string
EmailAddresses []string
DirectoryNames []pkix.Name
URIs []string
IPNets []net.IPNet
RegisteredIDs []asn1.ObjectIdentifier
OtherNames []OtherName
}
// Len returns the total number of names in a GeneralNames object.
func (gn GeneralNames) Len() int {
return (len(gn.DNSNames) + len(gn.EmailAddresses) + len(gn.DirectoryNames) +
len(gn.URIs) + len(gn.IPNets) + len(gn.RegisteredIDs) + len(gn.OtherNames))
}
// Empty indicates whether a GeneralNames object is empty.
func (gn GeneralNames) Empty() bool {
return gn.Len() == 0
}
func parseGeneralNames(value []byte, gname *GeneralNames) error {
// RFC 5280, 4.2.1.6
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
//
// GeneralName ::= CHOICE {
// otherName [0] OtherName,
// rfc822Name [1] IA5String,
// dNSName [2] IA5String,
// x400Address [3] ORAddress,
// directoryName [4] Name,
// ediPartyName [5] EDIPartyName,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// registeredID [8] OBJECT IDENTIFIER }
var seq asn1.RawValue
var rest []byte
if rest, err := asn1.Unmarshal(value, &seq); err != nil {
return fmt.Errorf("x509: failed to parse GeneralNames: %v", err)
} else if len(rest) != 0 {
return fmt.Errorf("x509: trailing data after GeneralNames")
}
if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal {
return fmt.Errorf("x509: failed to parse GeneralNames sequence, tag %+v", seq)
}
rest = seq.Bytes
for len(rest) > 0 {
var err error
rest, err = parseGeneralName(rest, gname, false)
if err != nil {
return fmt.Errorf("x509: failed to parse GeneralName: %v", err)
}
}
return nil
}
func parseGeneralName(data []byte, gname *GeneralNames, withMask bool) ([]byte, error) {
var v asn1.RawValue
var rest []byte
var err error
rest, err = asn1.Unmarshal(data, &v)
if err != nil {
return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames: %v", err)
}
switch v.Tag {
case tagOtherName:
if !v.IsCompound {
return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.otherName: not compound")
}
var other OtherName
v.FullBytes = append([]byte{}, v.FullBytes...)
v.FullBytes[0] = asn1.TagSequence | 0x20
_, err = asn1.Unmarshal(v.FullBytes, &other)
if err != nil {
return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.otherName: %v", err)
}
gname.OtherNames = append(gname.OtherNames, other)
case tagRFC822Name:
gname.EmailAddresses = append(gname.EmailAddresses, string(v.Bytes))
case tagDNSName:
dns := string(v.Bytes)
gname.DNSNames = append(gname.DNSNames, dns)
case tagDirectoryName:
var rdnSeq pkix.RDNSequence
if _, err := asn1.Unmarshal(v.Bytes, &rdnSeq); err != nil {
return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.directoryName: %v", err)
}
var dirName pkix.Name
dirName.FillFromRDNSequence(&rdnSeq)
gname.DirectoryNames = append(gname.DirectoryNames, dirName)
case tagURI:
gname.URIs = append(gname.URIs, string(v.Bytes))
case tagIPAddress:
vlen := len(v.Bytes)
if withMask {
switch vlen {
case (2 * net.IPv4len), (2 * net.IPv6len):
ipNet := net.IPNet{IP: v.Bytes[0 : vlen/2], Mask: v.Bytes[vlen/2:]}
gname.IPNets = append(gname.IPNets, ipNet)
default:
return nil, fmt.Errorf("x509: invalid IP/mask length %d in GeneralNames.iPAddress", vlen)
}
} else {
switch vlen {
case net.IPv4len, net.IPv6len:
ipNet := net.IPNet{IP: v.Bytes}
gname.IPNets = append(gname.IPNets, ipNet)
default:
return nil, fmt.Errorf("x509: invalid IP length %d in GeneralNames.iPAddress", vlen)
}
}
case tagRegisteredID:
var oid asn1.ObjectIdentifier
v.FullBytes = append([]byte{}, v.FullBytes...)
v.FullBytes[0] = asn1.TagOID
_, err = asn1.Unmarshal(v.FullBytes, &oid)
if err != nil {
return nil, fmt.Errorf("x509: failed to unmarshal GeneralNames.registeredID: %v", err)
}
gname.RegisteredIDs = append(gname.RegisteredIDs, oid)
default:
return nil, fmt.Errorf("x509: failed to unmarshal GeneralName: unknown tag %d", v.Tag)
}
return rest, nil
}

View File

@ -0,0 +1,267 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"bytes"
"encoding/hex"
"net"
"reflect"
"strings"
"testing"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509/pkix"
)
func TestParseGeneralNames(t *testing.T) {
var tests = []struct {
data string // as hex
want GeneralNames
wantErr string
}{
{
data: ("3012" +
("8210" + "7777772e676f6f676c652e636f2e756b")),
want: GeneralNames{
DNSNames: []string{"www.google.co.uk"},
},
},
{
data: ("3024" +
("8210" + "7777772e676f6f676c652e636f2e756b") +
("8610" + "7777772e676f6f676c652e636f2e756b")),
want: GeneralNames{
DNSNames: []string{"www.google.co.uk"},
URIs: []string{"www.google.co.uk"},
},
},
{
data: "0a0101",
wantErr: "failed to parse GeneralNames sequence",
},
{
data: "0a",
wantErr: "failed to parse GeneralNames:",
},
{
data: "03000a0101",
wantErr: "trailing data",
},
{
data: ("3005" + ("8703" + "010203")),
wantErr: "invalid IP length",
},
}
for _, test := range tests {
inData := fromHex(test.data)
var got GeneralNames
err := parseGeneralNames(inData, &got)
if err != nil {
if test.wantErr == "" {
t.Errorf("parseGeneralNames(%s)=%v; want nil", test.data, err)
} else if !strings.Contains(err.Error(), test.wantErr) {
t.Errorf("parseGeneralNames(%s)=%v; want %q", test.data, err, test.wantErr)
}
continue
}
if test.wantErr != "" {
t.Errorf("parseGeneralNames(%s)=%+v,nil; want %q", test.data, got, test.wantErr)
continue
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("parseGeneralNames(%s)=%+v; want %+v", test.data, got, test.want)
}
}
}
func TestParseGeneralName(t *testing.T) {
var tests = []struct {
data string // as hex
withMask bool
want GeneralNames
wantErr string
}{
{
data: ("a008" +
("0603" + "551d0e") + // OID: subject-key-id
("0a01" + "01")), // enum=1
want: GeneralNames{
OtherNames: []OtherName{
{
TypeID: OIDExtensionSubjectKeyId,
Value: asn1.RawValue{
Class: asn1.ClassUniversal,
Tag: asn1.TagEnum,
IsCompound: false,
Bytes: fromHex("01"),
FullBytes: fromHex("0a0101"),
},
},
},
},
},
{
data: ("8008" +
("0603" + "551d0e") + // OID: subject-key-id
("0a01" + "01")), // enum=1
wantErr: "not compound",
},
{
data: ("a005" +
("0603" + "551d0e")), // OID: subject-key-id
wantErr: "sequence truncated",
},
{
data: ("8110" + "77777740676f6f676c652e636f2e756b"),
want: GeneralNames{
EmailAddresses: []string{"www@google.co.uk"},
},
},
{
data: ("8210" + "7777772e676f6f676c652e636f2e756b"),
want: GeneralNames{
DNSNames: []string{"www.google.co.uk"},
},
},
{
data: ("844b" +
("3049" +
("310b" +
("3009" +
("0603" + "550406") +
("1302" + "5553"))) + // "US"
("3113" +
("3011" +
("0603" + "55040a") +
("130a" + "476f6f676c6520496e63"))) + // "Google Inc"
("3125" +
("3023" +
("0603" + "550403") +
("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732"))))), // "GoogleInternet Authority G2"
want: GeneralNames{
DirectoryNames: []pkix.Name{
{
Country: []string{"US"},
Organization: []string{"Google Inc"},
CommonName: "Google Internet Authority G2",
Names: []pkix.AttributeTypeAndValue{
{Type: pkix.OIDCountry, Value: "US"},
{Type: pkix.OIDOrganization, Value: "Google Inc"},
{Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"},
},
},
},
},
},
{
data: ("8410" + "7777772e676f6f676c652e636f2e756b"),
wantErr: "failed to unmarshal GeneralNames.directoryName",
},
{
data: ("8610" + "7777772e676f6f676c652e636f2e756b"),
want: GeneralNames{
URIs: []string{"www.google.co.uk"},
},
},
{
data: ("8704" + "01020304"),
want: GeneralNames{
IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}}},
},
},
{
data: ("8708" + "01020304ffffff00"),
withMask: true,
want: GeneralNames{
IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{0xff, 0xff, 0xff, 0x00}}},
},
},
{
data: ("8710" + "01020304111213142122232431323334"),
want: GeneralNames{
IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4, 0x11, 0x12, 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34}}},
},
},
{
data: ("8703" + "010203"),
wantErr: "invalid IP length",
},
{
data: ("8707" + "01020304ffffff"),
withMask: true,
wantErr: "invalid IP/mask length",
},
{
data: ("8803" + "551d0e"), // OID: subject-key-id
want: GeneralNames{
RegisteredIDs: []asn1.ObjectIdentifier{OIDExtensionSubjectKeyId},
},
},
{
data: ("8803" + "551d8e"),
wantErr: "syntax error",
},
{
data: ("9003" + "551d8e"),
wantErr: "unknown tag",
},
{
data: ("8803"),
wantErr: "data truncated",
},
}
for _, test := range tests {
inData := fromHex(test.data)
var got GeneralNames
_, err := parseGeneralName(inData, &got, test.withMask)
if err != nil {
if test.wantErr == "" {
t.Errorf("parseGeneralName(%s)=%v; want nil", test.data, err)
} else if !strings.Contains(err.Error(), test.wantErr) {
t.Errorf("parseGeneralName(%s)=%v; want %q", test.data, err, test.wantErr)
}
continue
}
if test.wantErr != "" {
t.Errorf("parseGeneralName(%s)=%+v,nil; want %q", test.data, got, test.wantErr)
continue
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("parseGeneralName(%s)=%+v; want %+v", test.data, got, test.want)
}
if got.Empty() {
t.Errorf("parseGeneralName(%s).Empty(%+v)=true; want false", test.data, got)
}
if gotLen, wantLen := got.Len(), 1; gotLen != wantLen {
t.Errorf("parseGeneralName(%s).Len(%+v)=%d; want %d", test.data, got, gotLen, wantLen)
}
if !bytes.Equal(inData, fromHex(test.data)) {
t.Errorf("parseGeneralName(%s) modified data to %x", test.data, inData)
}
// Wrap the GeneralName up in a SEQUENCE and check that we get the same result using parseGeneralNames.
if test.withMask {
continue
}
seqData := append([]byte{0x30, byte(len(inData))}, inData...)
var gotSeq GeneralNames
err = parseGeneralNames(seqData, &gotSeq)
if err != nil {
t.Errorf("parseGeneralNames(%x)=%v; want nil", seqData, err)
continue
}
if !reflect.DeepEqual(gotSeq, test.want) {
t.Errorf("parseGeneralNames(%x)=%+v; want %+v", seqData, gotSeq, test.want)
}
}
}
func fromHex(s string) []byte {
d, _ := hex.DecodeString(s)
return d
}

View File

@ -0,0 +1,26 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo,!arm,!arm64,!ios,!go1.10
package x509
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
*/
import "C"
// For Go versions before 1.10, nil values for Apple's CoreFoundation
// CF*Ref types were represented by nil. See:
// https://github.com/golang/go/commit/b868616b63a8
func setNilCFRef(v *C.CFDataRef) {
*v = nil
}
func isNilCFRef(v C.CFDataRef) bool {
return v == nil
}

View File

@ -0,0 +1,26 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo,!arm,!arm64,!ios,go1.10
package x509
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
*/
import "C"
// For Go versions >= 1.10, nil values for Apple's CoreFoundation
// CF*Ref types are represented by zero. See:
// https://github.com/golang/go/commit/b868616b63a8
func setNilCFRef(v *C.CFDataRef) {
*v = 0
}
func isNilCFRef(v C.CFDataRef) bool {
return v == 0
}

View File

@ -0,0 +1,240 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
// RFC 1423 describes the encryption of PEM blocks. The algorithm used to
// generate a key from the password was derived by looking at the OpenSSL
// implementation.
import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/md5"
"encoding/hex"
"encoding/pem"
"errors"
"io"
"strings"
)
type PEMCipher int
// Possible values for the EncryptPEMBlock encryption algorithm.
const (
_ PEMCipher = iota
PEMCipherDES
PEMCipher3DES
PEMCipherAES128
PEMCipherAES192
PEMCipherAES256
)
// rfc1423Algo holds a method for enciphering a PEM block.
type rfc1423Algo struct {
cipher PEMCipher
name string
cipherFunc func(key []byte) (cipher.Block, error)
keySize int
blockSize int
}
// rfc1423Algos holds a slice of the possible ways to encrypt a PEM
// block. The ivSize numbers were taken from the OpenSSL source.
var rfc1423Algos = []rfc1423Algo{{
cipher: PEMCipherDES,
name: "DES-CBC",
cipherFunc: des.NewCipher,
keySize: 8,
blockSize: des.BlockSize,
}, {
cipher: PEMCipher3DES,
name: "DES-EDE3-CBC",
cipherFunc: des.NewTripleDESCipher,
keySize: 24,
blockSize: des.BlockSize,
}, {
cipher: PEMCipherAES128,
name: "AES-128-CBC",
cipherFunc: aes.NewCipher,
keySize: 16,
blockSize: aes.BlockSize,
}, {
cipher: PEMCipherAES192,
name: "AES-192-CBC",
cipherFunc: aes.NewCipher,
keySize: 24,
blockSize: aes.BlockSize,
}, {
cipher: PEMCipherAES256,
name: "AES-256-CBC",
cipherFunc: aes.NewCipher,
keySize: 32,
blockSize: aes.BlockSize,
},
}
// deriveKey uses a key derivation function to stretch the password into a key
// with the number of bits our cipher requires. This algorithm was derived from
// the OpenSSL source.
func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
hash := md5.New()
out := make([]byte, c.keySize)
var digest []byte
for i := 0; i < len(out); i += len(digest) {
hash.Reset()
hash.Write(digest)
hash.Write(password)
hash.Write(salt)
digest = hash.Sum(digest[:0])
copy(out[i:], digest)
}
return out
}
// IsEncryptedPEMBlock returns if the PEM block is password encrypted.
func IsEncryptedPEMBlock(b *pem.Block) bool {
_, ok := b.Headers["DEK-Info"]
return ok
}
// IncorrectPasswordError is returned when an incorrect password is detected.
var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
// DecryptPEMBlock takes a password encrypted PEM block and the password used to
// encrypt it and returns a slice of decrypted DER encoded bytes. It inspects
// the DEK-Info header to determine the algorithm used for decryption. If no
// DEK-Info header is present, an error is returned. If an incorrect password
// is detected an IncorrectPasswordError is returned. Because of deficiencies
// in the encrypted-PEM format, it's not always possible to detect an incorrect
// password. In these cases no error will be returned but the decrypted DER
// bytes will be random noise.
func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
dek, ok := b.Headers["DEK-Info"]
if !ok {
return nil, errors.New("x509: no DEK-Info header in block")
}
idx := strings.Index(dek, ",")
if idx == -1 {
return nil, errors.New("x509: malformed DEK-Info header")
}
mode, hexIV := dek[:idx], dek[idx+1:]
ciph := cipherByName(mode)
if ciph == nil {
return nil, errors.New("x509: unknown encryption mode")
}
iv, err := hex.DecodeString(hexIV)
if err != nil {
return nil, err
}
if len(iv) != ciph.blockSize {
return nil, errors.New("x509: incorrect IV size")
}
// Based on the OpenSSL implementation. The salt is the first 8 bytes
// of the initialization vector.
key := ciph.deriveKey(password, iv[:8])
block, err := ciph.cipherFunc(key)
if err != nil {
return nil, err
}
if len(b.Bytes)%block.BlockSize() != 0 {
return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
}
data := make([]byte, len(b.Bytes))
dec := cipher.NewCBCDecrypter(block, iv)
dec.CryptBlocks(data, b.Bytes)
// Blocks are padded using a scheme where the last n bytes of padding are all
// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
// For example:
// [x y z 2 2]
// [x y 7 7 7 7 7 7 7]
// If we detect a bad padding, we assume it is an invalid password.
dlen := len(data)
if dlen == 0 || dlen%ciph.blockSize != 0 {
return nil, errors.New("x509: invalid padding")
}
last := int(data[dlen-1])
if dlen < last {
return nil, IncorrectPasswordError
}
if last == 0 || last > ciph.blockSize {
return nil, IncorrectPasswordError
}
for _, val := range data[dlen-last:] {
if int(val) != last {
return nil, IncorrectPasswordError
}
}
return data[:dlen-last], nil
}
// EncryptPEMBlock returns a PEM block of the specified type holding the
// given DER-encoded data encrypted with the specified algorithm and
// password.
func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
ciph := cipherByKey(alg)
if ciph == nil {
return nil, errors.New("x509: unknown encryption mode")
}
iv := make([]byte, ciph.blockSize)
if _, err := io.ReadFull(rand, iv); err != nil {
return nil, errors.New("x509: cannot generate IV: " + err.Error())
}
// The salt is the first 8 bytes of the initialization vector,
// matching the key derivation in DecryptPEMBlock.
key := ciph.deriveKey(password, iv[:8])
block, err := ciph.cipherFunc(key)
if err != nil {
return nil, err
}
enc := cipher.NewCBCEncrypter(block, iv)
pad := ciph.blockSize - len(data)%ciph.blockSize
encrypted := make([]byte, len(data), len(data)+pad)
// We could save this copy by encrypting all the whole blocks in
// the data separately, but it doesn't seem worth the additional
// code.
copy(encrypted, data)
// See RFC 1423, section 1.1
for i := 0; i < pad; i++ {
encrypted = append(encrypted, byte(pad))
}
enc.CryptBlocks(encrypted, encrypted)
return &pem.Block{
Type: blockType,
Headers: map[string]string{
"Proc-Type": "4,ENCRYPTED",
"DEK-Info": ciph.name + "," + hex.EncodeToString(iv),
},
Bytes: encrypted,
}, nil
}
func cipherByName(name string) *rfc1423Algo {
for i := range rfc1423Algos {
alg := &rfc1423Algos[i]
if alg.name == name {
return alg
}
}
return nil
}
func cipherByKey(key PEMCipher) *rfc1423Algo {
for i := range rfc1423Algos {
alg := &rfc1423Algos[i]
if alg.cipher == key {
return alg
}
}
return nil
}

View File

@ -0,0 +1,247 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/pem"
"strings"
"testing"
)
func TestDecrypt(t *testing.T) {
for i, data := range testData {
t.Logf("test %v. %v", i, data.kind)
block, rest := pem.Decode(data.pemData)
if len(rest) > 0 {
t.Error("extra data")
}
der, err := DecryptPEMBlock(block, data.password)
if err != nil {
t.Error("decrypt failed: ", err)
continue
}
if _, err := ParsePKCS1PrivateKey(der); err != nil {
t.Error("invalid private key: ", err)
}
plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
if err != nil {
t.Fatal("cannot decode test DER data: ", err)
}
if !bytes.Equal(der, plainDER) {
t.Error("data mismatch")
}
}
}
func TestEncrypt(t *testing.T) {
for i, data := range testData {
t.Logf("test %v. %v", i, data.kind)
plainDER, err := base64.StdEncoding.DecodeString(data.plainDER)
if err != nil {
t.Fatal("cannot decode test DER data: ", err)
}
password := []byte("kremvax1")
block, err := EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", plainDER, password, data.kind)
if err != nil {
t.Error("encrypt: ", err)
continue
}
if !IsEncryptedPEMBlock(block) {
t.Error("PEM block does not appear to be encrypted")
}
if block.Type != "RSA PRIVATE KEY" {
t.Errorf("unexpected block type; got %q want %q", block.Type, "RSA PRIVATE KEY")
}
if block.Headers["Proc-Type"] != "4,ENCRYPTED" {
t.Errorf("block does not have correct Proc-Type header")
}
der, err := DecryptPEMBlock(block, password)
if err != nil {
t.Error("decrypt: ", err)
continue
}
if !bytes.Equal(der, plainDER) {
t.Errorf("data mismatch")
}
}
}
var testData = []struct {
kind PEMCipher
password []byte
pemData []byte
plainDER string
}{
{
kind: PEMCipherDES,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-CBC,34F09A4FC8DE22B5
WXxy8kbZdiZvANtKvhmPBLV7eVFj2A5z6oAxvI9KGyhG0ZK0skfnt00C24vfU7m5
ICXeoqP67lzJ18xCzQfHjDaBNs53DSDT+Iz4e8QUep1xQ30+8QKX2NA2coee3nwc
6oM1cuvhNUDemBH2i3dKgMVkfaga0zQiiOq6HJyGSncCMSruQ7F9iWEfRbFcxFCx
qtHb1kirfGKEtgWTF+ynyco6+2gMXNu70L7nJcnxnV/RLFkHt7AUU1yrclxz7eZz
XOH9VfTjb52q/I8Suozq9coVQwg4tXfIoYUdT//O+mB7zJb9HI9Ps77b9TxDE6Gm
4C9brwZ3zg2vqXcwwV6QRZMtyll9rOpxkbw6NPlpfBqkc3xS51bbxivbO/Nve4KD
r12ymjFNF4stXCfJnNqKoZ50BHmEEUDu5Wb0fpVn82XrGw7CYc4iug==
-----END RSA PRIVATE KEY-----`),
plainDER: `
MIIBPAIBAAJBAPASZe+tCPU6p80AjHhDkVsLYa51D35e/YGa8QcZyooeZM8EHozo
KD0fNiKI+53bHdy07N+81VQ8/ejPcRoXPlsCAwEAAQJBAMTxIuSq27VpR+zZ7WJf
c6fvv1OBvpMZ0/d1pxL/KnOAgq2rD5hDtk9b0LGhTPgQAmrrMTKuSeGoIuYE+gKQ
QvkCIQD+GC1m+/do+QRurr0uo46Kx1LzLeSCrjBk34wiOp2+dwIhAPHfTLRXS2fv
7rljm0bYa4+eDZpz+E8RcXEgzhhvcQQ9AiAI5eHZJGOyml3MXnQjiPi55WcDOw0w
glcRgT6QCEtz2wIhANSyqaFtosIkHKqrDUGfz/bb5tqMYTAnBruVPaf/WEOBAiEA
9xORWeRG1tRpso4+dYy4KdDkuLPIO01KY6neYGm3BCM=`,
},
{
kind: PEMCipher3DES,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,C1F4A6A03682C2C7
0JqVdBEH6iqM7drTkj+e2W/bE3LqakaiWhb9WUVonFkhyu8ca/QzebY3b5gCvAZQ
YwBvDcT/GHospKqPx+cxDHJNsUASDZws6bz8ZXWJGwZGExKzr0+Qx5fgXn44Ms3x
8g1ENFuTXtxo+KoNK0zuAMAqp66Llcds3Fjl4XR18QaD0CrVNAfOdgATWZm5GJxk
Fgx5f84nT+/ovvreG+xeOzWgvtKo0UUZVrhGOgfKLpa57adumcJ6SkUuBtEFpZFB
ldw5w7WC7d13x2LsRkwo8ZrDKgIV+Y9GNvhuCCkTzNP0V3gNeJpd201HZHR+9n3w
3z0VjR/MGqsfcy1ziEWMNOO53At3zlG6zP05aHMnMcZoVXadEK6L1gz++inSSDCq
gI0UJP4e3JVB7AkgYymYAwiYALAkoEIuanxoc50njJk=
-----END RSA PRIVATE KEY-----`),
plainDER: `
MIIBOwIBAAJBANOCXKdoNS/iP/MAbl9cf1/SF3P+Ns7ZeNL27CfmDh0O6Zduaax5
NBiumd2PmjkaCu7lQ5JOibHfWn+xJsc3kw0CAwEAAQJANX/W8d1Q/sCqzkuAn4xl
B5a7qfJWaLHndu1QRLNTRJPn0Ee7OKJ4H0QKOhQM6vpjRrz+P2u9thn6wUxoPsef
QQIhAP/jCkfejFcy4v15beqKzwz08/tslVjF+Yq41eJGejmxAiEA05pMoqfkyjcx
fyvGhpoOyoCp71vSGUfR2I9CR65oKh0CIC1Msjs66LlfJtQctRq6bCEtFCxEcsP+
eEjYo/Sk6WphAiEAxpgWPMJeU/shFT28gS+tmhjPZLpEoT1qkVlC14u0b3ECIQDX
tZZZxCtPAm7shftEib0VU77Lk8MsXJcx2C4voRsjEw==`,
},
{
kind: PEMCipherAES128,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,D4492E793FC835CC038A728ED174F78A
EyfQSzXSjv6BaNH+NHdXRlkHdimpF9izWlugVJAPApgXrq5YldPe2aGIOFXyJ+QE
ZIG20DYqaPzJRjTEbPNZ6Es0S2JJ5yCpKxwJuDkgJZKtF39Q2i36JeGbSZQIuWJE
GZbBpf1jDH/pr0iGonuAdl2PCCZUiy+8eLsD2tyviHUkFLOB+ykYoJ5t8ngZ/B6D
33U43LLb7+9zD4y3Q9OVHqBFGyHcxCY9+9Qh4ZnFp7DTf6RY5TNEvE3s4g6aDpBs
3NbvRVvYTgs8K9EPk4K+5R+P2kD8J8KvEIGxVa1vz8QoCJ/jr7Ka2rvNgPCex5/E
080LzLHPCrXKdlr/f50yhNWq08ZxMWQFkui+FDHPDUaEELKAXV8/5PDxw80Rtybo
AVYoCVIbZXZCuCO81op8UcOgEpTtyU5Lgh3Mw5scQL0=
-----END RSA PRIVATE KEY-----`),
plainDER: `
MIIBOgIBAAJBAMBlj5FxYtqbcy8wY89d/S7n0+r5MzD9F63BA/Lpl78vQKtdJ5dT
cDGh/rBt1ufRrNp0WihcmZi7Mpl/3jHjiWECAwEAAQJABNOHYnKhtDIqFYj1OAJ3
k3GlU0OlERmIOoeY/cL2V4lgwllPBEs7r134AY4wMmZSBUj8UR/O4SNO668ElKPE
cQIhAOuqY7/115x5KCdGDMWi+jNaMxIvI4ETGwV40ykGzqlzAiEA0P9oEC3m9tHB
kbpjSTxaNkrXxDgdEOZz8X0uOUUwHNsCIAwzcSCiGLyYJTULUmP1ESERfW1mlV78
XzzESaJpIM/zAiBQkSTcl9VhcJreQqvjn5BnPZLP4ZHS4gPwJAGdsj5J4QIhAOVR
B3WlRNTXR2WsJ5JdByezg9xzdXzULqmga0OE339a`,
},
{
kind: PEMCipherAES192,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-192-CBC,E2C9FB02BCA23ADE1829F8D8BC5F5369
cqVslvHqDDM6qwU6YjezCRifXmKsrgEev7ng6Qs7UmDJOpHDgJQZI9fwMFUhIyn5
FbCu1SHkLMW52Ld3CuEqMnzWMlhPrW8tFvUOrMWPYSisv7nNq88HobZEJcUNL2MM
Y15XmHW6IJwPqhKyLHpWXyOCVEh4ODND2nV15PCoi18oTa475baxSk7+1qH7GuIs
Rb7tshNTMqHbCpyo9Rn3UxeFIf9efdl8YLiMoIqc7J8E5e9VlbeQSdLMQOgDAQJG
ReUtTw8exmKsY4gsSjhkg5uiw7/ZB1Ihto0qnfQJgjGc680qGkT1d6JfvOfeYAk6
xn5RqS/h8rYAYm64KnepfC9vIujo4NqpaREDmaLdX5MJPQ+SlytITQvgUsUq3q/t
Ss85xjQEZH3hzwjQqdJvmA4hYP6SUjxYpBM+02xZ1Xw=
-----END RSA PRIVATE KEY-----`),
plainDER: `
MIIBOwIBAAJBAMGcRrZiNNmtF20zyS6MQ7pdGx17aFDl+lTl+qnLuJRUCMUG05xs
OmxmL/O1Qlf+bnqR8Bgg65SfKg21SYuLhiMCAwEAAQJBAL94uuHyO4wux2VC+qpj
IzPykjdU7XRcDHbbvksf4xokSeUFjjD3PB0Qa83M94y89ZfdILIqS9x5EgSB4/lX
qNkCIQD6cCIqLfzq/lYbZbQgAAjpBXeQVYsbvVtJrPrXJAlVVQIhAMXpDKMeFPMn
J0g2rbx1gngx0qOa5r5iMU5w/noN4W2XAiBjf+WzCG5yFvazD+dOx3TC0A8+4x3P
uZ3pWbaXf5PNuQIgAcdXarvhelH2w2piY1g3BPeFqhzBSCK/yLGxR82KIh8CIQDD
+qGKsd09NhQ/G27y/DARzOYtml1NvdmCQAgsDIIOLA==`,
},
{
kind: PEMCipherAES256,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,8E7ED5CD731902CE938957A886A5FFBD
4Mxr+KIzRVwoOP0wwq6caSkvW0iS+GE2h2Ov/u+n9ZTMwL83PRnmjfjzBgfRZLVf
JFPXxUK26kMNpIdssNnqGOds+DhB+oSrsNKoxgxSl5OBoYv9eJTVYm7qOyAFIsjr
DRKAcjYCmzfesr7PVTowwy0RtHmYwyXMGDlAzzZrEvaiySFFmMyKKvtoavwaFoc7
Pz3RZScwIuubzTGJ1x8EzdffYOsdCa9Mtgpp3L136+23dOd6L/qK2EG2fzrJSHs/
2XugkleBFSMKzEp9mxXKRfa++uidQvMZTFLDK9w5YjrRvMBo/l2BoZIsq0jAIE1N
sv5Z/KwlX+3MDEpPQpUwGPlGGdLnjI3UZ+cjgqBcoMiNc6HfgbBgYJSU6aDSHuCk
clCwByxWkBNgJ2GrkwNrF26v+bGJJJNR4SKouY1jQf0=
-----END RSA PRIVATE KEY-----`),
plainDER: `
MIIBOgIBAAJBAKy3GFkstoCHIEeUU/qO8207m8WSrjksR+p9B4tf1w5k+2O1V/GY
AQ5WFCApItcOkQe/I0yZZJk/PmCqMzSxrc8CAwEAAQJAOCAz0F7AW9oNelVQSP8F
Sfzx7O1yom+qWyAQQJF/gFR11gpf9xpVnnyu1WxIRnDUh1LZwUsjwlDYb7MB74id
oQIhANPcOiLwOPT4sIUpRM5HG6BF1BI7L77VpyGVk8xNP7X/AiEA0LMHZtk4I+lJ
nClgYp4Yh2JZ1Znbu7IoQMCEJCjwKDECIGd8Dzm5tViTkUW6Hs3Tlf73nNs65duF
aRnSglss8I3pAiEAonEnKruawgD8RavDFR+fUgmQiPz4FnGGeVgfwpGG1JECIBYq
PXHYtPqxQIbD2pScR5qum7iGUh11lEUPkmt+2uqS`,
},
{
// generated with:
// openssl genrsa -aes128 -passout pass:asdf -out server.orig.key 128
kind: PEMCipherAES128,
password: []byte("asdf"),
pemData: []byte(`
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7
6ei/MlytjE0FFgZOGQ+jrwomKfpl8kdefeE0NSt/DMRrw8OacHAzBNi3pPEa0eX3
eND9l7C9meCirWovjj9QWVHrXyugFuDIqgdhQ8iHTgCfF3lrmcttVrbIfMDw+smD
hTP8O1mS/MHl92NE0nhv0w==
-----END RSA PRIVATE KEY-----`),
plainDER: `
MGMCAQACEQC6ssxmYuauuHGOCDAI54RdAgMBAAECEQCWIn6Yv2O+kBcDF7STctKB
AgkA8SEfu/2i3g0CCQDGNlXbBHX7kQIIK3Ww5o0cYbECCQDCimPb0dYGsQIIeQ7A
jryIst8=`,
},
}
const incompleteBlockPEM = `
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,74611ABC2571AF11B1BF9B69E62C89E7
6L8yXK2MTQUWBk4ZD6OvCiYp+mXyR1594TQ1K38MxGvDw5pwcDME2Lek8RrR5fd40P2XsL2Z4KKt
ai+OP1BZUetfK6AW4MiqB2FDyIdOAJ8XeWuZy21Wtsh8wPD6yYOFM/w7WZL8weX3Y0TSeG/T
-----END RSA PRIVATE KEY-----`
func TestIncompleteBlock(t *testing.T) {
// incompleteBlockPEM contains ciphertext that is not a multiple of the
// block size. This previously panicked. See #11215.
block, _ := pem.Decode([]byte(incompleteBlockPEM))
_, err := DecryptPEMBlock(block, []byte("foo"))
if err == nil {
t.Fatal("Bad PEM data decrypted successfully")
}
const expectedSubstr = "block size"
if e := err.Error(); !strings.Contains(e, expectedSubstr) {
t.Fatalf("Expected error containing %q but got: %q", expectedSubstr, e)
}
}

View File

@ -0,0 +1,155 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"crypto/rsa"
"errors"
"math/big"
"github.com/google/certificate-transparency-go/asn1"
)
// pkcs1PrivateKey is a structure which mirrors the PKCS#1 ASN.1 for an RSA private key.
type pkcs1PrivateKey struct {
Version int
N *big.Int
E int
D *big.Int
P *big.Int
Q *big.Int
// We ignore these values, if present, because rsa will calculate them.
Dp *big.Int `asn1:"optional"`
Dq *big.Int `asn1:"optional"`
Qinv *big.Int `asn1:"optional"`
AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"`
}
type pkcs1AdditionalRSAPrime struct {
Prime *big.Int
// We ignore these values because rsa will calculate them.
Exp *big.Int
Coeff *big.Int
}
// pkcs1PublicKey reflects the ASN.1 structure of a PKCS#1 public key.
type pkcs1PublicKey struct {
N *big.Int
E int
}
// ParsePKCS1PrivateKey returns an RSA private key from its ASN.1 PKCS#1 DER encoded form.
func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) {
var priv pkcs1PrivateKey
rest, err := asn1.Unmarshal(der, &priv)
if len(rest) > 0 {
return nil, asn1.SyntaxError{Msg: "trailing data"}
}
if err != nil {
return nil, err
}
if priv.Version > 1 {
return nil, errors.New("x509: unsupported private key version")
}
if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 {
return nil, errors.New("x509: private key contains zero or negative value")
}
key := new(rsa.PrivateKey)
key.PublicKey = rsa.PublicKey{
E: priv.E,
N: priv.N,
}
key.D = priv.D
key.Primes = make([]*big.Int, 2+len(priv.AdditionalPrimes))
key.Primes[0] = priv.P
key.Primes[1] = priv.Q
for i, a := range priv.AdditionalPrimes {
if a.Prime.Sign() <= 0 {
return nil, errors.New("x509: private key contains zero or negative prime")
}
key.Primes[i+2] = a.Prime
// We ignore the other two values because rsa will calculate
// them as needed.
}
err = key.Validate()
if err != nil {
return nil, err
}
key.Precompute()
return key, nil
}
// MarshalPKCS1PrivateKey converts a private key to ASN.1 DER encoded form.
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte {
key.Precompute()
version := 0
if len(key.Primes) > 2 {
version = 1
}
priv := pkcs1PrivateKey{
Version: version,
N: key.N,
E: key.PublicKey.E,
D: key.D,
P: key.Primes[0],
Q: key.Primes[1],
Dp: key.Precomputed.Dp,
Dq: key.Precomputed.Dq,
Qinv: key.Precomputed.Qinv,
}
priv.AdditionalPrimes = make([]pkcs1AdditionalRSAPrime, len(key.Precomputed.CRTValues))
for i, values := range key.Precomputed.CRTValues {
priv.AdditionalPrimes[i].Prime = key.Primes[2+i]
priv.AdditionalPrimes[i].Exp = values.Exp
priv.AdditionalPrimes[i].Coeff = values.Coeff
}
b, _ := asn1.Marshal(priv)
return b
}
// ParsePKCS1PublicKey parses a PKCS#1 public key in ASN.1 DER form.
func ParsePKCS1PublicKey(der []byte) (*rsa.PublicKey, error) {
var pub pkcs1PublicKey
rest, err := asn1.Unmarshal(der, &pub)
if err != nil {
return nil, err
}
if len(rest) > 0 {
return nil, asn1.SyntaxError{Msg: "trailing data"}
}
if pub.N.Sign() <= 0 || pub.E <= 0 {
return nil, errors.New("x509: public key contains zero or negative value")
}
if pub.E > 1<<31-1 {
return nil, errors.New("x509: public key contains large public exponent")
}
return &rsa.PublicKey{
E: pub.E,
N: pub.N,
}, nil
}
// MarshalPKCS1PublicKey converts an RSA public key to PKCS#1, ASN.1 DER form.
func MarshalPKCS1PublicKey(key *rsa.PublicKey) []byte {
derBytes, _ := asn1.Marshal(pkcs1PublicKey{
N: key.N,
E: key.E,
})
return derBytes
}

View File

@ -0,0 +1,102 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"crypto/ecdsa"
"crypto/rsa"
"errors"
"fmt"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509/pkix"
)
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn
// and RFC 5208.
type pkcs8 struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
// optional attributes omitted.
}
// ParsePKCS8PrivateKey parses an unencrypted, PKCS#8 private key.
// See RFC 5208.
func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
var privKey pkcs8
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, err
}
switch {
case privKey.Algo.Algorithm.Equal(OIDPublicKeyRSA):
key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
if err != nil {
return nil, errors.New("x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
}
return key, nil
case privKey.Algo.Algorithm.Equal(OIDPublicKeyECDSA):
bytes := privKey.Algo.Parameters.FullBytes
namedCurveOID := new(asn1.ObjectIdentifier)
if _, err := asn1.Unmarshal(bytes, namedCurveOID); err != nil {
namedCurveOID = nil
}
key, err = parseECPrivateKey(namedCurveOID, privKey.PrivateKey)
if err != nil {
return nil, errors.New("x509: failed to parse EC private key embedded in PKCS#8: " + err.Error())
}
return key, nil
default:
return nil, fmt.Errorf("x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm)
}
}
// MarshalPKCS8PrivateKey converts a private key to PKCS#8 encoded form.
// The following key types are supported: *rsa.PrivateKey, *ecdsa.PublicKey.
// Unsupported key types result in an error.
//
// See RFC 5208.
func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
var privKey pkcs8
switch k := key.(type) {
case *rsa.PrivateKey:
privKey.Algo = pkix.AlgorithmIdentifier{
Algorithm: OIDPublicKeyRSA,
Parameters: asn1.NullRawValue,
}
privKey.PrivateKey = MarshalPKCS1PrivateKey(k)
case *ecdsa.PrivateKey:
oid, ok := OIDFromNamedCurve(k.Curve)
if !ok {
return nil, errors.New("x509: unknown curve while marshalling to PKCS#8")
}
oidBytes, err := asn1.Marshal(oid)
if err != nil {
return nil, errors.New("x509: failed to marshal curve OID: " + err.Error())
}
privKey.Algo = pkix.AlgorithmIdentifier{
Algorithm: OIDPublicKeyECDSA,
Parameters: asn1.RawValue{
FullBytes: oidBytes,
},
}
if privKey.PrivateKey, err = marshalECPrivateKeyWithOID(k, nil); err != nil {
return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error())
}
default:
return nil, fmt.Errorf("x509: unknown key type while marshalling PKCS#8: %T", key)
}
return asn1.Marshal(privKey)
}

View File

@ -0,0 +1,109 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/hex"
"reflect"
"testing"
)
// Generated using:
// openssl genrsa 1024 | openssl pkcs8 -topk8 -nocrypt
var pkcs8RSAPrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031`
// Generated using:
// openssl ecparam -genkey -name secp224r1 | openssl pkcs8 -topk8 -nocrypt
var pkcs8P224PrivateKeyHex = `3078020100301006072a8648ce3d020106052b810400210461305f020101041cca3d72b3e88fed2684576dad9b80a9180363a5424986900e3abcab3fa13c033a0004f8f2a6372872a4e61263ed893afb919576a4cacfecd6c081a2cbc76873cf4ba8530703c6042b3a00e2205087e87d2435d2e339e25702fae1`
// Generated using:
// openssl ecparam -genkey -name secp256r1 | openssl pkcs8 -topk8 -nocrypt
var pkcs8P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420dad6b2f49ca774c36d8ae9517e935226f667c929498f0343d2424d0b9b591b43a14403420004b9c9b90095476afe7b860d8bd43568cab7bcb2eed7b8bf2fa0ce1762dd20b04193f859d2d782b1e4cbfd48492f1f533113a6804903f292258513837f07fda735`
// Generated using:
// openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt
var pkcs8P384PrivateKeyHex = `3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104309bf832f6aaaeacb78ce47ffb15e6fd0fd48683ae79df6eca39bfb8e33829ac94aa29d08911568684c2264a08a4ceb679a164036200049070ad4ed993c7770d700e9f6dc2baa83f63dd165b5507f98e8ff29b5d2e78ccbe05c8ddc955dbf0f7497e8222cfa49314fe4e269459f8e880147f70d785e530f2939e4bf9f838325bb1a80ad4cf59272ae0e5efe9a9dc33d874492596304bd3`
// Generated using:
// openssl ecparam -genkey -name secp521r1 | openssl pkcs8 -topk8 -nocrypt
//
// Note that OpenSSL will truncate the private key if it can (i.e. it emits it
// like an integer, even though it's an OCTET STRING field). Thus if you
// regenerate this you may, randomly, find that it's a byte shorter than
// expected and the Go test will fail to recreate it exactly.
var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020101044200cfe0b87113a205cf291bb9a8cd1a74ac6c7b2ebb8199aaa9a5010d8b8012276fa3c22ac913369fa61beec2a3b8b4516bc049bde4fb3b745ac11b56ab23ac52e361a1818903818600040138f75acdd03fbafa4f047a8e4b272ba9d555c667962b76f6f232911a5786a0964e5edea6bd21a6f8725720958de049c6e3e6661c1c91b227cebee916c0319ed6ca003db0a3206d372229baf9dd25d868bf81140a518114803ce40c1855074d68c4e9dab9e65efba7064c703b400f1767f217dac82715ac1f6d88c74baf47a7971de4ea`
func TestPKCS8(t *testing.T) {
tests := []struct {
name string
keyHex string
keyType reflect.Type
curve elliptic.Curve
}{
{
name: "RSA private key",
keyHex: pkcs8RSAPrivateKeyHex,
keyType: reflect.TypeOf(&rsa.PrivateKey{}),
},
{
name: "P-224 private key",
keyHex: pkcs8P224PrivateKeyHex,
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
curve: elliptic.P224(),
},
{
name: "P-256 private key",
keyHex: pkcs8P256PrivateKeyHex,
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
curve: elliptic.P256(),
},
{
name: "P-384 private key",
keyHex: pkcs8P384PrivateKeyHex,
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
curve: elliptic.P384(),
},
{
name: "P-521 private key",
keyHex: pkcs8P521PrivateKeyHex,
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
curve: elliptic.P521(),
},
}
for _, test := range tests {
derBytes, err := hex.DecodeString(test.keyHex)
if err != nil {
t.Errorf("%s: failed to decode hex: %s", test.name, err)
continue
}
privKey, err := ParsePKCS8PrivateKey(derBytes)
if err != nil {
t.Errorf("%s: failed to decode PKCS#8: %s", test.name, err)
continue
}
if reflect.TypeOf(privKey) != test.keyType {
t.Errorf("%s: decoded PKCS#8 returned unexpected key type: %T", test.name, privKey)
continue
}
if ecKey, isEC := privKey.(*ecdsa.PrivateKey); isEC && ecKey.Curve != test.curve {
t.Errorf("%s: decoded PKCS#8 returned unexpected curve %#v", test.name, ecKey.Curve)
continue
}
reserialised, err := MarshalPKCS8PrivateKey(privKey)
if err != nil {
t.Errorf("%s: failed to marshal into PKCS#8: %s", test.name, err)
continue
}
if !bytes.Equal(derBytes, reserialised) {
t.Errorf("%s: marshalled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes)
continue
}
}
}

View File

@ -0,0 +1,288 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pkix contains shared, low level structures used for ASN.1 parsing
// and serialization of X.509 certificates, CRL and OCSP.
package pkix
import (
// START CT CHANGES
"encoding/hex"
"fmt"
"github.com/google/certificate-transparency-go/asn1"
// END CT CHANGES
"math/big"
"time"
)
// AlgorithmIdentifier represents the ASN.1 structure of the same name. See RFC
// 5280, section 4.1.1.2.
type AlgorithmIdentifier struct {
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue `asn1:"optional"`
}
type RDNSequence []RelativeDistinguishedNameSET
var attributeTypeNames = map[string]string{
"2.5.4.6": "C",
"2.5.4.10": "O",
"2.5.4.11": "OU",
"2.5.4.3": "CN",
"2.5.4.5": "SERIALNUMBER",
"2.5.4.7": "L",
"2.5.4.8": "ST",
"2.5.4.9": "STREET",
"2.5.4.17": "POSTALCODE",
}
// String returns a string representation of the sequence r,
// roughly following the RFC 2253 Distinguished Names syntax.
func (r RDNSequence) String() string {
s := ""
for i := 0; i < len(r); i++ {
rdn := r[len(r)-1-i]
if i > 0 {
s += ","
}
for j, tv := range rdn {
if j > 0 {
s += "+"
}
oidString := tv.Type.String()
typeName, ok := attributeTypeNames[oidString]
if !ok {
derBytes, err := asn1.Marshal(tv.Value)
if err == nil {
s += oidString + "=#" + hex.EncodeToString(derBytes)
continue // No value escaping necessary.
}
typeName = oidString
}
valueString := fmt.Sprint(tv.Value)
escaped := make([]rune, 0, len(valueString))
for k, c := range valueString {
escape := false
switch c {
case ',', '+', '"', '\\', '<', '>', ';':
escape = true
case ' ':
escape = k == 0 || k == len(valueString)-1
case '#':
escape = k == 0
}
if escape {
escaped = append(escaped, '\\', c)
} else {
escaped = append(escaped, c)
}
}
s += typeName + "=" + string(escaped)
}
}
return s
}
type RelativeDistinguishedNameSET []AttributeTypeAndValue
// AttributeTypeAndValue mirrors the ASN.1 structure of the same name in
// http://tools.ietf.org/html/rfc5280#section-4.1.2.4
type AttributeTypeAndValue struct {
Type asn1.ObjectIdentifier
Value interface{}
}
// AttributeTypeAndValueSET represents a set of ASN.1 sequences of
// AttributeTypeAndValue sequences from RFC 2986 (PKCS #10).
type AttributeTypeAndValueSET struct {
Type asn1.ObjectIdentifier
Value [][]AttributeTypeAndValue `asn1:"set"`
}
// Extension represents the ASN.1 structure of the same name. See RFC
// 5280, section 4.2.
type Extension struct {
Id asn1.ObjectIdentifier
Critical bool `asn1:"optional"`
Value []byte
}
// Name represents an X.509 distinguished name. This only includes the common
// elements of a DN. When parsing, all elements are stored in Names and
// non-standard elements can be extracted from there. When marshaling, elements
// in ExtraNames are appended and override other values with the same OID.
type Name struct {
Country, Organization, OrganizationalUnit []string
Locality, Province []string
StreetAddress, PostalCode []string
SerialNumber, CommonName string
Names []AttributeTypeAndValue
ExtraNames []AttributeTypeAndValue
}
func (n *Name) FillFromRDNSequence(rdns *RDNSequence) {
for _, rdn := range *rdns {
if len(rdn) == 0 {
continue
}
for _, atv := range rdn {
n.Names = append(n.Names, atv)
value, ok := atv.Value.(string)
if !ok {
continue
}
t := atv.Type
if len(t) == 4 && t[0] == OIDAttribute[0] && t[1] == OIDAttribute[1] && t[2] == OIDAttribute[2] {
switch t[3] {
case OIDCommonName[3]:
n.CommonName = value
case OIDSerialNumber[3]:
n.SerialNumber = value
case OIDCountry[3]:
n.Country = append(n.Country, value)
case OIDLocality[3]:
n.Locality = append(n.Locality, value)
case OIDProvince[3]:
n.Province = append(n.Province, value)
case OIDStreetAddress[3]:
n.StreetAddress = append(n.StreetAddress, value)
case OIDOrganization[3]:
n.Organization = append(n.Organization, value)
case OIDOrganizationalUnit[3]:
n.OrganizationalUnit = append(n.OrganizationalUnit, value)
case OIDPostalCode[3]:
n.PostalCode = append(n.PostalCode, value)
}
}
}
}
}
var (
OIDAttribute = asn1.ObjectIdentifier{2, 5, 4}
OIDCountry = asn1.ObjectIdentifier{2, 5, 4, 6}
OIDOrganization = asn1.ObjectIdentifier{2, 5, 4, 10}
OIDOrganizationalUnit = asn1.ObjectIdentifier{2, 5, 4, 11}
OIDCommonName = asn1.ObjectIdentifier{2, 5, 4, 3}
OIDSerialNumber = asn1.ObjectIdentifier{2, 5, 4, 5}
OIDLocality = asn1.ObjectIdentifier{2, 5, 4, 7}
OIDProvince = asn1.ObjectIdentifier{2, 5, 4, 8}
OIDStreetAddress = asn1.ObjectIdentifier{2, 5, 4, 9}
OIDPostalCode = asn1.ObjectIdentifier{2, 5, 4, 17}
OIDPseudonym = asn1.ObjectIdentifier{2, 5, 4, 65}
OIDTitle = asn1.ObjectIdentifier{2, 5, 4, 12}
OIDDnQualifier = asn1.ObjectIdentifier{2, 5, 4, 46}
OIDName = asn1.ObjectIdentifier{2, 5, 4, 41}
OIDSurname = asn1.ObjectIdentifier{2, 5, 4, 4}
OIDGivenName = asn1.ObjectIdentifier{2, 5, 4, 42}
OIDInitials = asn1.ObjectIdentifier{2, 5, 4, 43}
OIDGenerationQualifier = asn1.ObjectIdentifier{2, 5, 4, 44}
)
// appendRDNs appends a relativeDistinguishedNameSET to the given RDNSequence
// and returns the new value. The relativeDistinguishedNameSET contains an
// attributeTypeAndValue for each of the given values. See RFC 5280, A.1, and
// search for AttributeTypeAndValue.
func (n Name) appendRDNs(in RDNSequence, values []string, oid asn1.ObjectIdentifier) RDNSequence {
if len(values) == 0 || oidInAttributeTypeAndValue(oid, n.ExtraNames) {
return in
}
s := make([]AttributeTypeAndValue, len(values))
for i, value := range values {
s[i].Type = oid
s[i].Value = value
}
return append(in, s)
}
func (n Name) ToRDNSequence() (ret RDNSequence) {
ret = n.appendRDNs(ret, n.Country, OIDCountry)
ret = n.appendRDNs(ret, n.Province, OIDProvince)
ret = n.appendRDNs(ret, n.Locality, OIDLocality)
ret = n.appendRDNs(ret, n.StreetAddress, OIDStreetAddress)
ret = n.appendRDNs(ret, n.PostalCode, OIDPostalCode)
ret = n.appendRDNs(ret, n.Organization, OIDOrganization)
ret = n.appendRDNs(ret, n.OrganizationalUnit, OIDOrganizationalUnit)
if len(n.CommonName) > 0 {
ret = n.appendRDNs(ret, []string{n.CommonName}, OIDCommonName)
}
if len(n.SerialNumber) > 0 {
ret = n.appendRDNs(ret, []string{n.SerialNumber}, OIDSerialNumber)
}
for _, atv := range n.ExtraNames {
ret = append(ret, []AttributeTypeAndValue{atv})
}
return ret
}
// String returns the string form of n, roughly following
// the RFC 2253 Distinguished Names syntax.
func (n Name) String() string {
return n.ToRDNSequence().String()
}
// oidInAttributeTypeAndValue returns whether a type with the given OID exists
// in atv.
func oidInAttributeTypeAndValue(oid asn1.ObjectIdentifier, atv []AttributeTypeAndValue) bool {
for _, a := range atv {
if a.Type.Equal(oid) {
return true
}
}
return false
}
// CertificateList represents the ASN.1 structure of the same name. See RFC
// 5280, section 5.1. Use Certificate.CheckCRLSignature to verify the
// signature.
type CertificateList struct {
TBSCertList TBSCertificateList
SignatureAlgorithm AlgorithmIdentifier
SignatureValue asn1.BitString
}
// HasExpired reports whether certList should have been updated by now.
func (certList *CertificateList) HasExpired(now time.Time) bool {
return !now.Before(certList.TBSCertList.NextUpdate)
}
// TBSCertificateList represents the ASN.1 structure TBSCertList. See RFC
// 5280, section 5.1.
type TBSCertificateList struct {
Raw asn1.RawContent
Version int `asn1:"optional,default:0"`
Signature AlgorithmIdentifier
Issuer RDNSequence
ThisUpdate time.Time
NextUpdate time.Time `asn1:"optional"`
RevokedCertificates []RevokedCertificate `asn1:"optional"`
Extensions []Extension `asn1:"tag:0,optional,explicit"`
}
// RevokedCertificate represents the unnamed ASN.1 structure that makes up the
// revokedCertificates member of the TBSCertList structure. See RFC
// 5280, section 5.1.
type RevokedCertificate struct {
SerialNumber *big.Int
RevocationTime time.Time
Extensions []Extension `asn1:"optional"`
}

View File

@ -0,0 +1,362 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"bytes"
"encoding/pem"
"time"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509/pkix"
)
var (
// OID values for CRL extensions (TBSCertList.Extensions), RFC 5280 s5.2.
OIDExtensionCRLNumber = asn1.ObjectIdentifier{2, 5, 29, 20}
OIDExtensionDeltaCRLIndicator = asn1.ObjectIdentifier{2, 5, 29, 27}
OIDExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
// OID values for CRL entry extensions (RevokedCertificate.Extensions), RFC 5280 s5.3
OIDExtensionCRLReasons = asn1.ObjectIdentifier{2, 5, 29, 21}
OIDExtensionInvalidityDate = asn1.ObjectIdentifier{2, 5, 29, 24}
OIDExtensionCertificateIssuer = asn1.ObjectIdentifier{2, 5, 29, 29}
)
// RevocationReasonCode represents the reason for a certificate revocation; see RFC 5280 s5.3.1.
type RevocationReasonCode asn1.Enumerated
// RevocationReasonCode values.
var (
Unspecified = RevocationReasonCode(0)
KeyCompromise = RevocationReasonCode(1)
CACompromise = RevocationReasonCode(2)
AffiliationChanged = RevocationReasonCode(3)
Superseded = RevocationReasonCode(4)
CessationOfOperation = RevocationReasonCode(5)
CertificateHold = RevocationReasonCode(6)
RemoveFromCRL = RevocationReasonCode(8)
PrivilegeWithdrawn = RevocationReasonCode(9)
AACompromise = RevocationReasonCode(10)
)
// ReasonFlag holds a bitmask of applicable revocation reasons, from RFC 5280 s4.2.1.13
type ReasonFlag int
// ReasonFlag values.
const (
UnusedFlag ReasonFlag = 1 << iota
KeyCompromiseFlag
CACompromiseFlag
AffiliationChangedFlag
SupersededFlag
CessationOfOperationFlag
CertificateHoldFlag
PrivilegeWithdrawnFlag
AACompromiseFlag
)
// CertificateList represents the ASN.1 structure of the same name from RFC 5280, s5.1.
// It has the same content as pkix.CertificateList, but the contents include parsed versions
// of any extensions.
type CertificateList struct {
Raw asn1.RawContent
TBSCertList TBSCertList
SignatureAlgorithm pkix.AlgorithmIdentifier
SignatureValue asn1.BitString
}
// ExpiredAt reports whether now is past the expiry time of certList.
func (certList *CertificateList) ExpiredAt(now time.Time) bool {
return now.After(certList.TBSCertList.NextUpdate)
}
// Indication of whether extensions need to be critical or non-critical. Extensions that
// can be either are omitted from the map.
var listExtCritical = map[string]bool{
// From RFC 5280...
OIDExtensionAuthorityKeyId.String(): false, // s5.2.1
OIDExtensionIssuerAltName.String(): false, // s5.2.2
OIDExtensionCRLNumber.String(): false, // s5.2.3
OIDExtensionDeltaCRLIndicator.String(): true, // s5.2.4
OIDExtensionIssuingDistributionPoint.String(): true, // s5.2.5
OIDExtensionFreshestCRL.String(): false, // s5.2.6
OIDExtensionAuthorityInfoAccess.String(): false, // s5.2.7
}
var certExtCritical = map[string]bool{
// From RFC 5280...
OIDExtensionCRLReasons.String(): false, // s5.3.1
OIDExtensionInvalidityDate.String(): false, // s5.3.2
OIDExtensionCertificateIssuer.String(): true, // s5.3.3
}
// IssuingDistributionPoint represents the ASN.1 structure of the same
// name
type IssuingDistributionPoint struct {
DistributionPoint distributionPointName `asn1:"optional,tag:0"`
OnlyContainsUserCerts bool `asn1:"optional,tag:1"`
OnlyContainsCACerts bool `asn1:"optional,tag:2"`
OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"`
IndirectCRL bool `asn1:"optional,tag:4"`
OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"`
}
// TBSCertList represents the ASN.1 structure of the same name from RFC
// 5280, section 5.1. It has the same content as pkix.TBSCertificateList
// but the extensions are included in a parsed format.
type TBSCertList struct {
Raw asn1.RawContent
Version int
Signature pkix.AlgorithmIdentifier
Issuer pkix.RDNSequence
ThisUpdate time.Time
NextUpdate time.Time
RevokedCertificates []*RevokedCertificate
Extensions []pkix.Extension
// Cracked out extensions:
AuthorityKeyID []byte
IssuerAltNames GeneralNames
CRLNumber int
BaseCRLNumber int // -1 if no delta CRL present
IssuingDistributionPoint IssuingDistributionPoint
IssuingDPFullNames GeneralNames
FreshestCRLDistributionPoint []string
OCSPServer []string
IssuingCertificateURL []string
}
// ParseCertificateList parses a CertificateList (e.g. a CRL) from the given
// bytes. It's often the case that PEM encoded CRLs will appear where they
// should be DER encoded, so this function will transparently handle PEM
// encoding as long as there isn't any leading garbage.
func ParseCertificateList(clBytes []byte) (*CertificateList, error) {
if bytes.HasPrefix(clBytes, pemCRLPrefix) {
block, _ := pem.Decode(clBytes)
if block != nil && block.Type == pemType {
clBytes = block.Bytes
}
}
return ParseCertificateListDER(clBytes)
}
// ParseCertificateListDER parses a DER encoded CertificateList from the given bytes.
// For non-fatal errors, this function returns both an error and a CertificateList
// object.
func ParseCertificateListDER(derBytes []byte) (*CertificateList, error) {
var errs Errors
// First parse the DER into the pkix structures.
pkixList := new(pkix.CertificateList)
if rest, err := asn1.Unmarshal(derBytes, pkixList); err != nil {
errs.AddID(ErrInvalidCertList, err)
return nil, &errs
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertList)
return nil, &errs
}
// Transcribe the revoked certs but crack out extensions.
revokedCerts := make([]*RevokedCertificate, len(pkixList.TBSCertList.RevokedCertificates))
for i, pkixRevoked := range pkixList.TBSCertList.RevokedCertificates {
revokedCerts[i] = parseRevokedCertificate(pkixRevoked, &errs)
if revokedCerts[i] == nil {
return nil, &errs
}
}
certList := CertificateList{
Raw: derBytes,
TBSCertList: TBSCertList{
Raw: pkixList.TBSCertList.Raw,
Version: pkixList.TBSCertList.Version,
Signature: pkixList.TBSCertList.Signature,
Issuer: pkixList.TBSCertList.Issuer,
ThisUpdate: pkixList.TBSCertList.ThisUpdate,
NextUpdate: pkixList.TBSCertList.NextUpdate,
RevokedCertificates: revokedCerts,
Extensions: pkixList.TBSCertList.Extensions,
CRLNumber: -1,
BaseCRLNumber: -1,
},
SignatureAlgorithm: pkixList.SignatureAlgorithm,
SignatureValue: pkixList.SignatureValue,
}
// Now crack out extensions.
for _, e := range certList.TBSCertList.Extensions {
if expectCritical, present := listExtCritical[e.Id.String()]; present {
if e.Critical && !expectCritical {
errs.AddID(ErrUnexpectedlyCriticalCertListExtension, e.Id)
} else if !e.Critical && expectCritical {
errs.AddID(ErrUnexpectedlyNonCriticalCertListExtension, e.Id)
}
}
switch {
case e.Id.Equal(OIDExtensionAuthorityKeyId):
// RFC 5280 s5.2.1
var a authKeyId
if rest, err := asn1.Unmarshal(e.Value, &a); err != nil {
errs.AddID(ErrInvalidCertListAuthKeyID, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertListAuthKeyID)
}
certList.TBSCertList.AuthorityKeyID = a.Id
case e.Id.Equal(OIDExtensionIssuerAltName):
// RFC 5280 s5.2.2
if err := parseGeneralNames(e.Value, &certList.TBSCertList.IssuerAltNames); err != nil {
errs.AddID(ErrInvalidCertListIssuerAltName, err)
}
case e.Id.Equal(OIDExtensionCRLNumber):
// RFC 5280 s5.2.3
if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.CRLNumber); err != nil {
errs.AddID(ErrInvalidCertListCRLNumber, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertListCRLNumber)
}
if certList.TBSCertList.CRLNumber < 0 {
errs.AddID(ErrNegativeCertListCRLNumber, certList.TBSCertList.CRLNumber)
}
case e.Id.Equal(OIDExtensionDeltaCRLIndicator):
// RFC 5280 s5.2.4
if rest, err := asn1.Unmarshal(e.Value, &certList.TBSCertList.BaseCRLNumber); err != nil {
errs.AddID(ErrInvalidCertListDeltaCRL, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertListDeltaCRL)
}
if certList.TBSCertList.BaseCRLNumber < 0 {
errs.AddID(ErrNegativeCertListDeltaCRL, certList.TBSCertList.BaseCRLNumber)
}
case e.Id.Equal(OIDExtensionIssuingDistributionPoint):
parseIssuingDistributionPoint(e.Value, &certList.TBSCertList.IssuingDistributionPoint, &certList.TBSCertList.IssuingDPFullNames, &errs)
case e.Id.Equal(OIDExtensionFreshestCRL):
// RFC 5280 s5.2.6
if err := parseDistributionPoints(e.Value, &certList.TBSCertList.FreshestCRLDistributionPoint); err != nil {
errs.AddID(ErrInvalidCertListFreshestCRL, err)
return nil, err
}
case e.Id.Equal(OIDExtensionAuthorityInfoAccess):
// RFC 5280 s5.2.7
var aia []authorityInfoAccess
if rest, err := asn1.Unmarshal(e.Value, &aia); err != nil {
errs.AddID(ErrInvalidCertListAuthInfoAccess, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertListAuthInfoAccess)
}
for _, v := range aia {
// GeneralName: uniformResourceIdentifier [6] IA5String
if v.Location.Tag != tagURI {
continue
}
switch {
case v.Method.Equal(OIDAuthorityInfoAccessOCSP):
certList.TBSCertList.OCSPServer = append(certList.TBSCertList.OCSPServer, string(v.Location.Bytes))
case v.Method.Equal(OIDAuthorityInfoAccessIssuers):
certList.TBSCertList.IssuingCertificateURL = append(certList.TBSCertList.IssuingCertificateURL, string(v.Location.Bytes))
}
// TODO(drysdale): cope with more possibilities
}
default:
if e.Critical {
errs.AddID(ErrUnhandledCriticalCertListExtension, e.Id)
}
}
}
if errs.Fatal() {
return nil, &errs
}
if errs.Empty() {
return &certList, nil
}
return &certList, &errs
}
func parseIssuingDistributionPoint(data []byte, idp *IssuingDistributionPoint, name *GeneralNames, errs *Errors) {
// RFC 5280 s5.2.5
if rest, err := asn1.Unmarshal(data, idp); err != nil {
errs.AddID(ErrInvalidCertListIssuingDP, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingCertListIssuingDP)
}
typeCount := 0
if idp.OnlyContainsUserCerts {
typeCount++
}
if idp.OnlyContainsCACerts {
typeCount++
}
if idp.OnlyContainsAttributeCerts {
typeCount++
}
if typeCount > 1 {
errs.AddID(ErrCertListIssuingDPMultipleTypes, idp.OnlyContainsUserCerts, idp.OnlyContainsCACerts, idp.OnlyContainsAttributeCerts)
}
for _, fn := range idp.DistributionPoint.FullName {
if _, err := parseGeneralName(fn.FullBytes, name, false); err != nil {
errs.AddID(ErrCertListIssuingDPInvalidFullName, err)
}
}
}
// RevokedCertificate represents the unnamed ASN.1 structure that makes up the
// revokedCertificates member of the TBSCertList structure from RFC 5280, s5.1.
// It has the same content as pkix.RevokedCertificate but the extensions are
// included in a parsed format.
type RevokedCertificate struct {
pkix.RevokedCertificate
// Cracked out extensions:
RevocationReason RevocationReasonCode
InvalidityDate time.Time
Issuer GeneralNames
}
func parseRevokedCertificate(pkixRevoked pkix.RevokedCertificate, errs *Errors) *RevokedCertificate {
result := RevokedCertificate{RevokedCertificate: pkixRevoked}
for _, e := range pkixRevoked.Extensions {
if expectCritical, present := certExtCritical[e.Id.String()]; present {
if e.Critical && !expectCritical {
errs.AddID(ErrUnexpectedlyCriticalRevokedCertExtension, e.Id)
} else if !e.Critical && expectCritical {
errs.AddID(ErrUnexpectedlyNonCriticalRevokedCertExtension, e.Id)
}
}
switch {
case e.Id.Equal(OIDExtensionCRLReasons):
// RFC 5280, s5.3.1
var reason asn1.Enumerated
if rest, err := asn1.Unmarshal(e.Value, &reason); err != nil {
errs.AddID(ErrInvalidRevocationReason, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingRevocationReason)
}
result.RevocationReason = RevocationReasonCode(reason)
case e.Id.Equal(OIDExtensionInvalidityDate):
// RFC 5280, s5.3.2
if rest, err := asn1.Unmarshal(e.Value, &result.InvalidityDate); err != nil {
errs.AddID(ErrInvalidRevocationInvalidityDate, err)
} else if len(rest) != 0 {
errs.AddID(ErrTrailingRevocationInvalidityDate)
}
case e.Id.Equal(OIDExtensionCertificateIssuer):
// RFC 5280, s5.3.3
if err := parseGeneralNames(e.Value, &result.Issuer); err != nil {
errs.AddID(ErrInvalidRevocationIssuer, err)
}
default:
if e.Critical {
errs.AddID(ErrUnhandledCriticalRevokedCertExtension, e.Id)
}
}
}
return &result
}
// CheckCertificateListSignature checks that the signature in crl is from c.
func (c *Certificate) CheckCertificateListSignature(crl *CertificateList) error {
algo := SignatureAlgorithmFromAI(crl.SignatureAlgorithm)
return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign())
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import "sync"
var (
once sync.Once
systemRoots *CertPool
systemRootsErr error
)
func systemRootsPool() *CertPool {
once.Do(initSystemRoots)
return systemRoots
}
func initSystemRoots() {
systemRoots, systemRootsErr = loadSystemRoots()
}

View File

@ -0,0 +1,15 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd netbsd openbsd
package x509
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/usr/local/etc/ssl/cert.pem", // FreeBSD
"/etc/ssl/cert.pem", // OpenBSD
"/usr/local/share/certs/ca-root-nss.crt", // DragonFly
"/etc/openssl/certs/ca-certificates.crt", // NetBSD
}

View File

@ -0,0 +1,252 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo,!arm,!arm64,!ios
package x509
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <errno.h>
#include <sys/sysctl.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
// FetchPEMRootsCTX509_MountainLion is the version of FetchPEMRoots from Go 1.6
// which still works on OS X 10.8 (Mountain Lion).
// It lacks support for admin & user cert domains.
// See golang.org/issue/16473
int FetchPEMRootsCTX509_MountainLion(CFDataRef *pemRoots) {
if (pemRoots == NULL) {
return -1;
}
CFArrayRef certs = NULL;
OSStatus err = SecTrustCopyAnchorCertificates(&certs);
if (err != noErr) {
return -1;
}
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
int i, ncerts = CFArrayGetCount(certs);
for (i = 0; i < ncerts; i++) {
CFDataRef data = NULL;
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
if (cert == NULL) {
continue;
}
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// Once we support weak imports via cgo we should prefer that, and fall back to this
// for older systems.
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
if (err != noErr) {
continue;
}
if (data != NULL) {
CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
}
CFRelease(certs);
*pemRoots = combinedData;
return 0;
}
// useOldCodeCTX509 reports whether the running machine is OS X 10.8 Mountain Lion
// or older. We only support Mountain Lion and higher, but we'll at least try our
// best on older machines and continue to use the old code path.
//
// See golang.org/issue/16473
int useOldCodeCTX509() {
char str[256];
size_t size = sizeof(str);
memset(str, 0, size);
sysctlbyname("kern.osrelease", str, &size, NULL, 0);
// OS X 10.8 is osrelease "12.*", 10.7 is 11.*, 10.6 is 10.*.
// We never supported things before that.
return memcmp(str, "12.", 3) == 0 || memcmp(str, "11.", 3) == 0 || memcmp(str, "10.", 3) == 0;
}
// FetchPEMRootsCTX509 fetches the system's list of trusted X.509 root certificates.
//
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
// certificates of the system. On failure, the function returns -1.
// Additionally, it fills untrustedPemRoots with certs that must be removed from pemRoots.
//
// Note: The CFDataRef returned in pemRoots and untrustedPemRoots must
// be released (using CFRelease) after we've consumed its content.
int FetchPEMRootsCTX509(CFDataRef *pemRoots, CFDataRef *untrustedPemRoots) {
if (useOldCodeCTX509()) {
return FetchPEMRootsCTX509_MountainLion(pemRoots);
}
// Get certificates from all domains, not just System, this lets
// the user add CAs to their "login" keychain, and Admins to add
// to the "System" keychain
SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem,
kSecTrustSettingsDomainAdmin,
kSecTrustSettingsDomainUser };
int numDomains = sizeof(domains)/sizeof(SecTrustSettingsDomain);
if (pemRoots == NULL) {
return -1;
}
// kSecTrustSettingsResult is defined as CFSTR("kSecTrustSettingsResult"),
// but the Go linker's internal linking mode can't handle CFSTR relocations.
// Create our own dynamic string instead and release it below.
CFStringRef policy = CFStringCreateWithCString(NULL, "kSecTrustSettingsResult", kCFStringEncodingUTF8);
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
CFMutableDataRef combinedUntrustedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
for (int i = 0; i < numDomains; i++) {
CFArrayRef certs = NULL;
OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs);
if (err != noErr) {
continue;
}
CFIndex numCerts = CFArrayGetCount(certs);
for (int j = 0; j < numCerts; j++) {
CFDataRef data = NULL;
CFErrorRef errRef = NULL;
CFArrayRef trustSettings = NULL;
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j);
if (cert == NULL) {
continue;
}
// We only want trusted certs.
int untrusted = 0;
int trustAsRoot = 0;
int trustRoot = 0;
if (i == 0) {
trustAsRoot = 1;
} else {
// Certs found in the system domain are always trusted. If the user
// configures "Never Trust" on such a cert, it will also be found in the
// admin or user domain, causing it to be added to untrustedPemRoots. The
// Go code will then clean this up.
// Trust may be stored in any of the domains. According to Apple's
// SecTrustServer.c, "user trust settings overrule admin trust settings",
// so take the last trust settings array we find.
// Skip the system domain since it is always trusted.
for (int k = i; k < numDomains; k++) {
CFArrayRef domainTrustSettings = NULL;
err = SecTrustSettingsCopyTrustSettings(cert, domains[k], &domainTrustSettings);
if (err == errSecSuccess && domainTrustSettings != NULL) {
if (trustSettings) {
CFRelease(trustSettings);
}
trustSettings = domainTrustSettings;
}
}
if (trustSettings == NULL) {
// "this certificate must be verified to a known trusted certificate"; aka not a root.
continue;
}
for (CFIndex k = 0; k < CFArrayGetCount(trustSettings); k++) {
CFNumberRef cfNum;
CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, k);
if (CFDictionaryGetValueIfPresent(tSetting, policy, (const void**)&cfNum)){
SInt32 result = 0;
CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result);
// TODO: The rest of the dictionary specifies conditions for evaluation.
if (result == kSecTrustSettingsResultDeny) {
untrusted = 1;
} else if (result == kSecTrustSettingsResultTrustAsRoot) {
trustAsRoot = 1;
} else if (result == kSecTrustSettingsResultTrustRoot) {
trustRoot = 1;
}
}
}
CFRelease(trustSettings);
}
if (trustRoot) {
// We only want to add Root CAs, so make sure Subject and Issuer Name match
CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef);
if (errRef != NULL) {
CFRelease(errRef);
continue;
}
CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef);
if (errRef != NULL) {
CFRelease(subjectName);
CFRelease(errRef);
continue;
}
Boolean equal = CFEqual(subjectName, issuerName);
CFRelease(subjectName);
CFRelease(issuerName);
if (!equal) {
continue;
}
}
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// Once we support weak imports via cgo we should prefer that, and fall back to this
// for older systems.
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
if (err != noErr) {
continue;
}
if (data != NULL) {
if (!trustRoot && !trustAsRoot) {
untrusted = 1;
}
CFMutableDataRef appendTo = untrusted ? combinedUntrustedData : combinedData;
CFDataAppendBytes(appendTo, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
}
CFRelease(certs);
}
CFRelease(policy);
*pemRoots = combinedData;
*untrustedPemRoots = combinedUntrustedData;
return 0;
}
*/
import "C"
import (
"errors"
"unsafe"
)
func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool()
var data C.CFDataRef
setNilCFRef(&data)
var untrustedData C.CFDataRef
setNilCFRef(&untrustedData)
err := C.FetchPEMRootsCTX509(&data, &untrustedData)
if err == -1 {
// TODO: better error message
return nil, errors.New("crypto/x509: failed to load darwin system roots with cgo")
}
defer C.CFRelease(C.CFTypeRef(data))
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
roots.AppendCertsFromPEM(buf)
if isNilCFRef(untrustedData) {
return roots, nil
}
defer C.CFRelease(C.CFTypeRef(untrustedData))
buf = C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(untrustedData)), C.int(C.CFDataGetLength(untrustedData)))
untrustedRoots := NewCertPool()
untrustedRoots.AppendCertsFromPEM(buf)
trustedRoots := NewCertPool()
for _, c := range roots.certs {
if !untrustedRoots.contains(c) {
trustedRoots.AddCert(c)
}
}
return trustedRoots, nil
}

View File

@ -0,0 +1,264 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
package x509
import (
"bufio"
"bytes"
"crypto/sha1"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"sync"
)
var debugExecDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil
}
// This code is only used when compiling without cgo.
// It is here, instead of root_nocgo_darwin.go, so that tests can check it
// even if the tests are run with cgo enabled.
// The linker will not include these unused functions in binaries built with cgo enabled.
// execSecurityRoots finds the macOS list of trusted root certificates
// using only command-line tools. This is our fallback path when cgo isn't available.
//
// The strategy is as follows:
//
// 1. Run "security trust-settings-export" and "security
// trust-settings-export -d" to discover the set of certs with some
// user-tweaked trust policy. We're too lazy to parse the XML (at
// least at this stage of Go 1.8) to understand what the trust
// policy actually is. We just learn that there is _some_ policy.
//
// 2. Run "security find-certificate" to dump the list of system root
// CAs in PEM format.
//
// 3. For each dumped cert, conditionally verify it with "security
// verify-cert" if that cert was in the set discovered in Step 1.
// Without the Step 1 optimization, running "security verify-cert"
// 150-200 times takes 3.5 seconds. With the optimization, the
// whole process takes about 180 milliseconds with 1 untrusted root
// CA. (Compared to 110ms in the cgo path)
func execSecurityRoots() (*CertPool, error) {
hasPolicy, err := getCertsWithTrustPolicy()
if err != nil {
return nil, err
}
if debugExecDarwinRoots {
println(fmt.Sprintf("crypto/x509: %d certs have a trust policy", len(hasPolicy)))
}
args := []string{"find-certificate", "-a", "-p",
"/System/Library/Keychains/SystemRootCertificates.keychain",
"/Library/Keychains/System.keychain",
}
u, err := user.Current()
if err != nil {
if debugExecDarwinRoots {
println(fmt.Sprintf("crypto/x509: get current user: %v", err))
}
} else {
args = append(args,
filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"),
// Fresh installs of Sierra use a slightly different path for the login keychain
filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"),
)
}
cmd := exec.Command("/usr/bin/security", args...)
data, err := cmd.Output()
if err != nil {
return nil, err
}
var (
mu sync.Mutex
roots = NewCertPool()
numVerified int // number of execs of 'security verify-cert', for debug stats
)
blockCh := make(chan *pem.Block)
var wg sync.WaitGroup
// Using 4 goroutines to pipe into verify-cert seems to be
// about the best we can do. The verify-cert binary seems to
// just RPC to another server with coarse locking anyway, so
// running 16 at a time for instance doesn't help at all. Due
// to the "if hasPolicy" check below, though, we will rarely
// (or never) call verify-cert on stock macOS systems, though.
// The hope is that we only call verify-cert when the user has
// tweaked their trust policy. These 4 goroutines are only
// defensive in the pathological case of many trust edits.
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for block := range blockCh {
cert, err := ParseCertificate(block.Bytes)
if err != nil {
continue
}
sha1CapHex := fmt.Sprintf("%X", sha1.Sum(block.Bytes))
valid := true
verifyChecks := 0
if hasPolicy[sha1CapHex] {
verifyChecks++
if !verifyCertWithSystem(block, cert) {
valid = false
}
}
mu.Lock()
numVerified += verifyChecks
if valid {
roots.AddCert(cert)
}
mu.Unlock()
}
}()
}
for len(data) > 0 {
var block *pem.Block
block, data = pem.Decode(data)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
blockCh <- block
}
close(blockCh)
wg.Wait()
if debugExecDarwinRoots {
mu.Lock()
defer mu.Unlock()
println(fmt.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified))
}
return roots, nil
}
func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool {
data := pem.EncodeToMemory(block)
f, err := ioutil.TempFile("", "cert")
if err != nil {
fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
return false
}
defer os.Remove(f.Name())
if _, err := f.Write(data); err != nil {
fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
return false
}
if err := f.Close(); err != nil {
fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
return false
}
cmd := exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l", "-L")
var stderr bytes.Buffer
if debugExecDarwinRoots {
cmd.Stderr = &stderr
}
if err := cmd.Run(); err != nil {
if debugExecDarwinRoots {
println(fmt.Sprintf("crypto/x509: verify-cert rejected %s: %q", cert.Subject.CommonName, bytes.TrimSpace(stderr.Bytes())))
}
return false
}
if debugExecDarwinRoots {
println(fmt.Sprintf("crypto/x509: verify-cert approved %s", cert.Subject.CommonName))
}
return true
}
// getCertsWithTrustPolicy returns the set of certs that have a
// possibly-altered trust policy. The keys of the map are capitalized
// sha1 hex of the raw cert.
// They are the certs that should be checked against `security
// verify-cert` to see whether the user altered the default trust
// settings. This code is only used for cgo-disabled builds.
func getCertsWithTrustPolicy() (map[string]bool, error) {
set := map[string]bool{}
td, err := ioutil.TempDir("", "x509trustpolicy")
if err != nil {
return nil, err
}
defer os.RemoveAll(td)
run := func(file string, args ...string) error {
file = filepath.Join(td, file)
args = append(args, file)
cmd := exec.Command("/usr/bin/security", args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
// If there are no trust settings, the
// `security trust-settings-export` command
// fails with:
// exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found.
// Rather than match on English substrings that are probably
// localized on macOS, just interpret any failure to mean that
// there are no trust settings.
if debugExecDarwinRoots {
println(fmt.Sprintf("crypto/x509: exec %q: %v, %s", cmd.Args, err, stderr.Bytes()))
}
return nil
}
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
// Gather all the runs of 40 capitalized hex characters.
br := bufio.NewReader(f)
var hexBuf bytes.Buffer
for {
b, err := br.ReadByte()
isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9')
if isHex {
hexBuf.WriteByte(b)
} else {
if hexBuf.Len() == 40 {
set[hexBuf.String()] = true
}
hexBuf.Reset()
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
return nil
}
if err := run("user", "trust-settings-export"); err != nil {
return nil, fmt.Errorf("dump-trust-settings (user): %v", err)
}
if err := run("admin", "trust-settings-export", "-d"); err != nil {
return nil, fmt.Errorf("dump-trust-settings (admin): %v", err)
}
return set, nil
}

View File

@ -0,0 +1,187 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// Generates root_darwin_armx.go.
//
// As of iOS 8, there is no API for querying the system trusted X.509 root
// certificates. We could use SecTrustEvaluate to verify that a trust chain
// exists for a certificate, but the x509 API requires returning the entire
// chain.
//
// Apple publishes the list of trusted root certificates for iOS on
// support.apple.com. So we parse the list and extract the certificates from
// an OS X machine and embed them into the x509 package.
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/pem"
"flag"
"fmt"
"go/format"
"io/ioutil"
"log"
"net/http"
"os/exec"
"regexp"
"strings"
"github.com/google/certificate-transparency-go/x509"
)
var output = flag.String("output", "root_darwin_armx.go", "file name to write")
func main() {
certs, err := selectCerts()
if err != nil {
log.Fatal(err)
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "// Code generated by root_darwin_arm_gen --output %s; DO NOT EDIT.\n", *output)
fmt.Fprintf(buf, "%s", header)
fmt.Fprintf(buf, "const systemRootsPEM = `\n")
for _, cert := range certs {
b := &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
if err := pem.Encode(buf, b); err != nil {
log.Fatal(err)
}
}
fmt.Fprintf(buf, "`")
source, err := format.Source(buf.Bytes())
if err != nil {
log.Fatal("source format error:", err)
}
if err := ioutil.WriteFile(*output, source, 0644); err != nil {
log.Fatal(err)
}
}
func selectCerts() ([]*x509.Certificate, error) {
ids, err := fetchCertIDs()
if err != nil {
return nil, err
}
scerts, err := sysCerts()
if err != nil {
return nil, err
}
var certs []*x509.Certificate
for _, id := range ids {
if c, ok := scerts[id.fingerprint]; ok {
certs = append(certs, c)
} else {
fmt.Printf("WARNING: cannot find certificate: %s (fingerprint: %s)\n", id.name, id.fingerprint)
}
}
return certs, nil
}
func sysCerts() (certs map[string]*x509.Certificate, err error) {
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
if err != nil {
return nil, err
}
certs = make(map[string]*x509.Certificate)
for len(data) > 0 {
var block *pem.Block
block, data = pem.Decode(data)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
fingerprint := sha256.Sum256(cert.Raw)
certs[hex.EncodeToString(fingerprint[:])] = cert
}
return certs, nil
}
type certID struct {
name string
fingerprint string
}
// fetchCertIDs fetches IDs of iOS X509 certificates from apple.com.
func fetchCertIDs() ([]certID, error) {
// Download the iOS 11 support page. The index for all iOS versions is here:
// https://support.apple.com/en-us/HT204132
resp, err := http.Get("https://support.apple.com/en-us/HT208125")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
text := string(body)
text = text[strings.Index(text, "<div id=trusted"):]
text = text[:strings.Index(text, "</div>")]
var ids []certID
cols := make(map[string]int)
for i, rowmatch := range regexp.MustCompile("(?s)<tr>(.*?)</tr>").FindAllStringSubmatch(text, -1) {
row := rowmatch[1]
if i == 0 {
// Parse table header row to extract column names
for i, match := range regexp.MustCompile("(?s)<th>(.*?)</th>").FindAllStringSubmatch(row, -1) {
cols[match[1]] = i
}
continue
}
values := regexp.MustCompile("(?s)<td>(.*?)</td>").FindAllStringSubmatch(row, -1)
name := values[cols["Certificate name"]][1]
fingerprint := values[cols["Fingerprint (SHA-256)"]][1]
fingerprint = strings.Replace(fingerprint, "<br>", "", -1)
fingerprint = strings.Replace(fingerprint, "\n", "", -1)
fingerprint = strings.Replace(fingerprint, " ", "", -1)
fingerprint = strings.ToLower(fingerprint)
ids = append(ids, certID{
name: name,
fingerprint: fingerprint,
})
}
return ids, nil
}
const header = `
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo
// +build darwin
// +build arm arm64 ios
package x509
func loadSystemRoots() (*CertPool, error) {
p := NewCertPool()
p.AppendCertsFromPEM([]byte(systemRootsPEM))
return p, nil
}
`

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"runtime"
"testing"
"time"
)
func TestSystemRoots(t *testing.T) {
switch runtime.GOARCH {
case "arm", "arm64":
t.Skipf("skipping on %s/%s, no system root", runtime.GOOS, runtime.GOARCH)
}
switch runtime.GOOS {
case "darwin":
t.Skipf("skipping on %s/%s until cgo part of golang.org/issue/16532 has been implemented.", runtime.GOOS, runtime.GOARCH)
}
t0 := time.Now()
sysRoots := systemRootsPool() // actual system roots
sysRootsDuration := time.Since(t0)
t1 := time.Now()
execRoots, err := execSecurityRoots() // non-cgo roots
execSysRootsDuration := time.Since(t1)
if err != nil {
t.Fatalf("failed to read system roots: %v", err)
}
t.Logf(" cgo sys roots: %v", sysRootsDuration)
t.Logf("non-cgo sys roots: %v", execSysRootsDuration)
for _, tt := range []*CertPool{sysRoots, execRoots} {
if tt == nil {
t.Fatal("no system roots")
}
// On Mavericks, there are 212 bundled certs, at least
// there was at one point in time on one machine.
// (Maybe it was a corp laptop with extra certs?)
// Other OS X users report
// 135, 142, 145... Let's try requiring at least 100,
// since this is just a sanity check.
t.Logf("got %d roots", len(tt.certs))
if want, have := 100, len(tt.certs); have < want {
t.Fatalf("want at least %d system roots, have %d", want, have)
}
}
// Check that the two cert pools are roughly the same;
// |A∩B| > max(|A|, |B|) / 2 should be a reasonably robust check.
isect := make(map[string]bool, len(sysRoots.certs))
for _, c := range sysRoots.certs {
isect[string(c.Raw)] = true
}
have := 0
for _, c := range execRoots.certs {
if isect[string(c.Raw)] {
have++
}
}
var want int
if nsys, nexec := len(sysRoots.certs), len(execRoots.certs); nsys > nexec {
want = nsys / 2
} else {
want = nexec / 2
}
if have < want {
t.Errorf("insufficient overlap between cgo and non-cgo roots; want at least %d, have %d", want, have)
}
}

View File

@ -0,0 +1,14 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
}

View File

@ -0,0 +1,8 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
// Possible certificate files; stop after finding one.
var certFiles = []string{}

View File

@ -0,0 +1,11 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !cgo
package x509
func loadSystemRoots() (*CertPool, error) {
return execSecurityRoots()
}

View File

@ -0,0 +1,37 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build plan9
package x509
import (
"io/ioutil"
"os"
)
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/sys/lib/tls/ca.pem",
}
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil
}
func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool()
var bestErr error
for _, file := range certFiles {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
return roots, nil
}
if bestErr == nil || (os.IsNotExist(bestErr) && !os.IsNotExist(err)) {
bestErr = err
}
}
return nil, bestErr
}

View File

@ -0,0 +1,12 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/certs/ca-certificates.crt", // Solaris 11.2+
"/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS
"/etc/ssl/cacert.pem", // OmniOS
}

View File

@ -0,0 +1,88 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd linux nacl netbsd openbsd solaris
package x509
import (
"io/ioutil"
"os"
)
// Possible directories with certificate files; stop after successfully
// reading at least one file from a directory.
var certDirectories = []string{
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
"/system/etc/security/cacerts", // Android
"/usr/local/share/certs", // FreeBSD
"/etc/pki/tls/certs", // Fedora/RHEL
"/etc/openssl/certs", // NetBSD
}
const (
// certFileEnv is the environment variable which identifies where to locate
// the SSL certificate file. If set this overrides the system default.
certFileEnv = "SSL_CERT_FILE"
// certDirEnv is the environment variable which identifies which directory
// to check for SSL certificate files. If set this overrides the system default.
certDirEnv = "SSL_CERT_DIR"
)
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
return nil, nil
}
func loadSystemRoots() (*CertPool, error) {
roots := NewCertPool()
files := certFiles
if f := os.Getenv(certFileEnv); f != "" {
files = []string{f}
}
var firstErr error
for _, file := range files {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
break
}
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
}
}
dirs := certDirectories
if d := os.Getenv(certDirEnv); d != "" {
dirs = []string{d}
}
for _, directory := range dirs {
fis, err := ioutil.ReadDir(directory)
if err != nil {
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
}
continue
}
rootsAdded := false
for _, fi := range fis {
data, err := ioutil.ReadFile(directory + "/" + fi.Name())
if err == nil && roots.AppendCertsFromPEM(data) {
rootsAdded = true
}
}
if rootsAdded {
return roots, nil
}
}
if len(roots.certs) > 0 {
return roots, nil
}
return nil, firstErr
}

View File

@ -0,0 +1,127 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd linux netbsd openbsd solaris
package x509
import (
"fmt"
"os"
"testing"
)
const (
testDir = "testdata"
testDirCN = "test-dir"
testFile = "test-file.crt"
testFileCN = "test-file"
testMissing = "missing"
)
func TestEnvVars(t *testing.T) {
testCases := []struct {
name string
fileEnv string
dirEnv string
files []string
dirs []string
cns []string
}{
{
// Environment variables override the default locations preventing fall through.
name: "override-defaults",
fileEnv: testMissing,
dirEnv: testMissing,
files: []string{testFile},
dirs: []string{testDir},
cns: nil,
},
{
// File environment overrides default file locations.
name: "file",
fileEnv: testFile,
dirEnv: "",
files: nil,
dirs: nil,
cns: []string{testFileCN},
},
{
// Directory environment overrides default directory locations.
name: "dir",
fileEnv: "",
dirEnv: testDir,
files: nil,
dirs: nil,
cns: []string{testDirCN},
},
{
// File & directory environment overrides both default locations.
name: "file+dir",
fileEnv: testFile,
dirEnv: testDir,
files: nil,
dirs: nil,
cns: []string{testFileCN, testDirCN},
},
{
// Environment variable empty / unset uses default locations.
name: "empty-fall-through",
fileEnv: "",
dirEnv: "",
files: []string{testFile},
dirs: []string{testDir},
cns: []string{testFileCN, testDirCN},
},
}
// Save old settings so we can restore before the test ends.
origCertFiles, origCertDirectories := certFiles, certDirectories
origFile, origDir := os.Getenv(certFileEnv), os.Getenv(certDirEnv)
defer func() {
certFiles = origCertFiles
certDirectories = origCertDirectories
os.Setenv(certFileEnv, origFile)
os.Setenv(certDirEnv, origDir)
}()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if err := os.Setenv(certFileEnv, tc.fileEnv); err != nil {
t.Fatalf("setenv %q failed: %v", certFileEnv, err)
}
if err := os.Setenv(certDirEnv, tc.dirEnv); err != nil {
t.Fatalf("setenv %q failed: %v", certDirEnv, err)
}
certFiles, certDirectories = tc.files, tc.dirs
r, err := loadSystemRoots()
if err != nil {
t.Fatal("unexpected failure:", err)
}
if r == nil {
if tc.cns == nil {
// Expected nil
return
}
t.Fatal("nil roots")
}
// Verify that the returned certs match, otherwise report where the mismatch is.
for i, cn := range tc.cns {
if i >= len(r.certs) {
t.Errorf("missing cert %v @ %v", cn, i)
} else if r.certs[i].Subject.CommonName != cn {
fmt.Printf("%#v\n", r.certs[0].Subject)
t.Errorf("unexpected cert common name %q, want %q", r.certs[i].Subject.CommonName, cn)
}
}
if len(r.certs) > len(tc.cns) {
t.Errorf("got %v certs, which is more than %v wanted", len(r.certs), len(tc.cns))
}
})
}
}

View File

@ -0,0 +1,266 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"errors"
"syscall"
"unsafe"
)
// Creates a new *syscall.CertContext representing the leaf certificate in an in-memory
// certificate store containing itself and all of the intermediate certificates specified
// in the opts.Intermediates CertPool.
//
// A pointer to the in-memory store is available in the returned CertContext's Store field.
// The store is automatically freed when the CertContext is freed using
// syscall.CertFreeCertificateContext.
func createStoreContext(leaf *Certificate, opts *VerifyOptions) (*syscall.CertContext, error) {
var storeCtx *syscall.CertContext
leafCtx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &leaf.Raw[0], uint32(len(leaf.Raw)))
if err != nil {
return nil, err
}
defer syscall.CertFreeCertificateContext(leafCtx)
handle, err := syscall.CertOpenStore(syscall.CERT_STORE_PROV_MEMORY, 0, 0, syscall.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, 0)
if err != nil {
return nil, err
}
defer syscall.CertCloseStore(handle, 0)
err = syscall.CertAddCertificateContextToStore(handle, leafCtx, syscall.CERT_STORE_ADD_ALWAYS, &storeCtx)
if err != nil {
return nil, err
}
if opts.Intermediates != nil {
for _, intermediate := range opts.Intermediates.certs {
ctx, err := syscall.CertCreateCertificateContext(syscall.X509_ASN_ENCODING|syscall.PKCS_7_ASN_ENCODING, &intermediate.Raw[0], uint32(len(intermediate.Raw)))
if err != nil {
return nil, err
}
err = syscall.CertAddCertificateContextToStore(handle, ctx, syscall.CERT_STORE_ADD_ALWAYS, nil)
syscall.CertFreeCertificateContext(ctx)
if err != nil {
return nil, err
}
}
}
return storeCtx, nil
}
// extractSimpleChain extracts the final certificate chain from a CertSimpleChain.
func extractSimpleChain(simpleChain **syscall.CertSimpleChain, count int) (chain []*Certificate, err error) {
if simpleChain == nil || count == 0 {
return nil, errors.New("x509: invalid simple chain")
}
simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(simpleChain))[:]
lastChain := simpleChains[count-1]
elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:]
for i := 0; i < int(lastChain.NumElements); i++ {
// Copy the buf, since ParseCertificate does not create its own copy.
cert := elements[i].CertContext
encodedCert := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf := make([]byte, cert.Length)
copy(buf, encodedCert[:])
parsedCert, err := ParseCertificate(buf)
if err != nil {
return nil, err
}
chain = append(chain, parsedCert)
}
return chain, nil
}
// checkChainTrustStatus checks the trust status of the certificate chain, translating
// any errors it finds into Go errors in the process.
func checkChainTrustStatus(c *Certificate, chainCtx *syscall.CertChainContext) error {
if chainCtx.TrustStatus.ErrorStatus != syscall.CERT_TRUST_NO_ERROR {
status := chainCtx.TrustStatus.ErrorStatus
switch status {
case syscall.CERT_TRUST_IS_NOT_TIME_VALID:
return CertificateInvalidError{c, Expired, ""}
default:
return UnknownAuthorityError{c, nil, nil}
}
}
return nil
}
// checkChainSSLServerPolicy checks that the certificate chain in chainCtx is valid for
// use as a certificate chain for a SSL/TLS server.
func checkChainSSLServerPolicy(c *Certificate, chainCtx *syscall.CertChainContext, opts *VerifyOptions) error {
servernamep, err := syscall.UTF16PtrFromString(opts.DNSName)
if err != nil {
return err
}
sslPara := &syscall.SSLExtraCertChainPolicyPara{
AuthType: syscall.AUTHTYPE_SERVER,
ServerName: servernamep,
}
sslPara.Size = uint32(unsafe.Sizeof(*sslPara))
para := &syscall.CertChainPolicyPara{
ExtraPolicyPara: uintptr(unsafe.Pointer(sslPara)),
}
para.Size = uint32(unsafe.Sizeof(*para))
status := syscall.CertChainPolicyStatus{}
err = syscall.CertVerifyCertificateChainPolicy(syscall.CERT_CHAIN_POLICY_SSL, chainCtx, para, &status)
if err != nil {
return err
}
// TODO(mkrautz): use the lChainIndex and lElementIndex fields
// of the CertChainPolicyStatus to provide proper context, instead
// using c.
if status.Error != 0 {
switch status.Error {
case syscall.CERT_E_EXPIRED:
return CertificateInvalidError{c, Expired, ""}
case syscall.CERT_E_CN_NO_MATCH:
return HostnameError{c, opts.DNSName}
case syscall.CERT_E_UNTRUSTEDROOT:
return UnknownAuthorityError{c, nil, nil}
default:
return UnknownAuthorityError{c, nil, nil}
}
}
return nil
}
// systemVerify is like Verify, except that it uses CryptoAPI calls
// to build certificate chains and verify them.
func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
hasDNSName := opts != nil && len(opts.DNSName) > 0
storeCtx, err := createStoreContext(c, opts)
if err != nil {
return nil, err
}
defer syscall.CertFreeCertificateContext(storeCtx)
para := new(syscall.CertChainPara)
para.Size = uint32(unsafe.Sizeof(*para))
// If there's a DNSName set in opts, assume we're verifying
// a certificate from a TLS server.
if hasDNSName {
oids := []*byte{
&syscall.OID_PKIX_KP_SERVER_AUTH[0],
// Both IE and Chrome allow certificates with
// Server Gated Crypto as well. Some certificates
// in the wild require them.
&syscall.OID_SERVER_GATED_CRYPTO[0],
&syscall.OID_SGC_NETSCAPE[0],
}
para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_OR
para.RequestedUsage.Usage.Length = uint32(len(oids))
para.RequestedUsage.Usage.UsageIdentifiers = &oids[0]
} else {
para.RequestedUsage.Type = syscall.USAGE_MATCH_TYPE_AND
para.RequestedUsage.Usage.Length = 0
para.RequestedUsage.Usage.UsageIdentifiers = nil
}
var verifyTime *syscall.Filetime
if opts != nil && !opts.CurrentTime.IsZero() {
ft := syscall.NsecToFiletime(opts.CurrentTime.UnixNano())
verifyTime = &ft
}
// CertGetCertificateChain will traverse Windows's root stores
// in an attempt to build a verified certificate chain. Once
// it has found a verified chain, it stops. MSDN docs on
// CERT_CHAIN_CONTEXT:
//
// When a CERT_CHAIN_CONTEXT is built, the first simple chain
// begins with an end certificate and ends with a self-signed
// certificate. If that self-signed certificate is not a root
// or otherwise trusted certificate, an attempt is made to
// build a new chain. CTLs are used to create the new chain
// beginning with the self-signed certificate from the original
// chain as the end certificate of the new chain. This process
// continues building additional simple chains until the first
// self-signed certificate is a trusted certificate or until
// an additional simple chain cannot be built.
//
// The result is that we'll only get a single trusted chain to
// return to our caller.
var chainCtx *syscall.CertChainContext
err = syscall.CertGetCertificateChain(syscall.Handle(0), storeCtx, verifyTime, storeCtx.Store, para, 0, 0, &chainCtx)
if err != nil {
return nil, err
}
defer syscall.CertFreeCertificateChain(chainCtx)
err = checkChainTrustStatus(c, chainCtx)
if err != nil {
return nil, err
}
if hasDNSName {
err = checkChainSSLServerPolicy(c, chainCtx, opts)
if err != nil {
return nil, err
}
}
chain, err := extractSimpleChain(chainCtx.Chains, int(chainCtx.ChainCount))
if err != nil {
return nil, err
}
chains = append(chains, chain)
return chains, nil
}
func loadSystemRoots() (*CertPool, error) {
// TODO: restore this functionality on Windows. We tried to do
// it in Go 1.8 but had to revert it. See Issue 18609.
// Returning (nil, nil) was the old behavior, prior to CL 30578.
return nil, nil
const CRYPT_E_NOT_FOUND = 0x80092004
store, err := syscall.CertOpenSystemStore(0, syscall.StringToUTF16Ptr("ROOT"))
if err != nil {
return nil, err
}
defer syscall.CertCloseStore(store, 0)
roots := NewCertPool()
var cert *syscall.CertContext
for {
cert, err = syscall.CertEnumCertificatesInStore(store, cert)
if err != nil {
if errno, ok := err.(syscall.Errno); ok {
if errno == CRYPT_E_NOT_FOUND {
break
}
}
return nil, err
}
if cert == nil {
break
}
// Copy the buf, since ParseCertificate does not create its own copy.
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf2 := make([]byte, cert.Length)
copy(buf2, buf)
if c, err := ParseCertificate(buf2); err == nil {
roots.AddCert(c)
}
}
return roots, nil
}

View File

@ -0,0 +1,112 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"math/big"
"github.com/google/certificate-transparency-go/asn1"
)
const ecPrivKeyVersion = 1
// ecPrivateKey reflects an ASN.1 Elliptic Curve Private Key Structure.
// References:
// RFC 5915
// SEC1 - http://www.secg.org/sec1-v2.pdf
// Per RFC 5915 the NamedCurveOID is marked as ASN.1 OPTIONAL, however in
// most cases it is not.
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
// ParseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
func ParseECPrivateKey(der []byte) (*ecdsa.PrivateKey, error) {
return parseECPrivateKey(nil, der)
}
// MarshalECPrivateKey marshals an EC private key into ASN.1, DER format.
func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
oid, ok := OIDFromNamedCurve(key.Curve)
if !ok {
return nil, errors.New("x509: unknown elliptic curve")
}
return marshalECPrivateKeyWithOID(key, oid)
}
// marshalECPrivateKey marshals an EC private key into ASN.1, DER format and
// sets the curve ID to the given OID, or omits it if OID is nil.
func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) {
privateKeyBytes := key.D.Bytes()
paddedPrivateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8)
copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes)
return asn1.Marshal(ecPrivateKey{
Version: 1,
PrivateKey: paddedPrivateKey,
NamedCurveOID: oid,
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)},
})
}
// parseECPrivateKey parses an ASN.1 Elliptic Curve Private Key Structure.
// The OID for the named curve may be provided from another source (such as
// the PKCS8 container) - if it is provided then use this instead of the OID
// that may exist in the EC private key structure.
func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *ecdsa.PrivateKey, err error) {
var privKey ecPrivateKey
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("x509: failed to parse EC private key: " + err.Error())
}
if privKey.Version != ecPrivKeyVersion {
return nil, fmt.Errorf("x509: unknown EC private key version %d", privKey.Version)
}
var curve elliptic.Curve
if namedCurveOID != nil {
curve = namedCurveFromOID(*namedCurveOID)
} else {
curve = namedCurveFromOID(privKey.NamedCurveOID)
}
if curve == nil {
return nil, errors.New("x509: unknown elliptic curve")
}
k := new(big.Int).SetBytes(privKey.PrivateKey)
curveOrder := curve.Params().N
if k.Cmp(curveOrder) >= 0 {
return nil, errors.New("x509: invalid elliptic curve private key value")
}
priv := new(ecdsa.PrivateKey)
priv.Curve = curve
priv.D = k
privateKey := make([]byte, (curveOrder.BitLen()+7)/8)
// Some private keys have leading zero padding. This is invalid
// according to [SEC1], but this code will ignore it.
for len(privKey.PrivateKey) > len(privateKey) {
if privKey.PrivateKey[0] != 0 {
return nil, errors.New("x509: invalid private key length")
}
privKey.PrivateKey = privKey.PrivateKey[1:]
}
// Some private keys remove all leading zeros, this is also invalid
// according to [SEC1] but since OpenSSL used to do this, we ignore
// this too.
copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey)
priv.X, priv.Y = curve.ScalarBaseMult(privateKey)
return priv, nil
}

View File

@ -0,0 +1,44 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import (
"bytes"
"encoding/hex"
"testing"
)
var ecKeyTests = []struct {
derHex string
shouldReserialize bool
}{
// Generated using:
// openssl ecparam -genkey -name secp384r1 -outform PEM
{"3081a40201010430bdb9839c08ee793d1157886a7a758a3c8b2a17a4df48f17ace57c72c56b4723cf21dcda21d4e1ad57ff034f19fcfd98ea00706052b81040022a16403620004feea808b5ee2429cfcce13c32160e1c960990bd050bb0fdf7222f3decd0a55008e32a6aa3c9062051c4cba92a7a3b178b24567412d43cdd2f882fa5addddd726fe3e208d2c26d733a773a597abb749714df7256ead5105fa6e7b3650de236b50", true},
// This key was generated by GnuTLS and has illegal zero-padding of the
// private key. See https://golang.org/issues/13699.
{"3078020101042100f9f43a04b9bdc3ab01f53be6df80e7a7bc3eaf7b87fc24e630a4a0aa97633645a00a06082a8648ce3d030107a1440342000441a51bc318461b4c39a45048a16d4fc2a935b1ea7fe86e8c1fa219d6f2438f7c7fd62957d3442efb94b6a23eb0ea66dda663dc42f379cda6630b21b7888a5d3d", false},
// This was generated using an old version of OpenSSL and is missing a
// leading zero byte in the private key that should be present.
{"3081db0201010441607b4f985774ac21e633999794542e09312073480baa69550914d6d43d8414441e61b36650567901da714f94dffb3ce0e2575c31928a0997d51df5c440e983ca17a00706052b81040023a181890381860004001661557afedd7ac8d6b70e038e576558c626eb62edda36d29c3a1310277c11f67a8c6f949e5430a37dcfb95d902c1b5b5379c389873b9dd17be3bdb088a4774a7401072f830fb9a08d93bfa50a03dd3292ea07928724ddb915d831917a338f6b0aecfbc3cf5352c4a1295d356890c41c34116d29eeb93779aab9d9d78e2613437740f6", false},
}
func TestParseECPrivateKey(t *testing.T) {
for i, test := range ecKeyTests {
derBytes, _ := hex.DecodeString(test.derHex)
key, err := ParseECPrivateKey(derBytes)
if err != nil {
t.Fatalf("#%d: failed to decode EC private key: %s", i, err)
}
serialized, err := MarshalECPrivateKey(key)
if err != nil {
t.Fatalf("#%d: failed to encode EC private key: %s", i, err)
}
matches := bytes.Equal(serialized, derBytes)
if matches != test.shouldReserialize {
t.Fatalf("#%d: when serializing key: matches=%t, should match=%t: original %x, reserialized %x", i, matches, test.shouldReserialize, serialized, derBytes)
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x509
import "syscall"
func init() {
v, err := syscall.GetVersion()
if err != nil {
return
}
if major := byte(v); major < 6 {
// Windows XP SP2 and Windows 2003 do not support SHA2.
// http://blogs.technet.com/b/pki/archive/2010/09/30/sha2-and-windows.aspx
supportSHA2 = false
}
}

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIJAL8a/lsnspOqMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV
BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz
dHMxETAPBgNVBAMMCHRlc3QtZGlyMB4XDTE3MDIwMTIzNTAyN1oXDTI3MDEzMDIz
NTAyN1owTDELMAkGA1UEBhMCVUsxEzARBgNVBAgMClRlc3QtU3RhdGUxFTATBgNV
BAoMDEdvbGFuZyBUZXN0czERMA8GA1UEAwwIdGVzdC1kaXIwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDzBoi43Yn30KN13PKFHu8LA4UmgCRToTukLItM
WK2Je45grs/axg9n3YJOXC6hmsyrkOnyBcx1xVNgSrOAll7fSjtChRIX72Xrloxu
XewtWVIrijqz6oylbvEmbRT3O8uynu5rF82Pmdiy8oiSfdywjKuPnE0hjV1ZSCql
MYcXqA+f0JFD8kMv4pbtxjGH8f2DkYQz+hHXLrJH4/MEYdVMQXoz/GDzLyOkrXBN
hpMaBBqg1p0P+tRdfLXuliNzA9vbZylzpF1YZ0gvsr0S5Y6LVtv7QIRygRuLY4kF
k+UYuFq8NrV8TykS7FVnO3tf4XcYZ7r2KV5FjYSrJtNNo85BV5c3xMD3fJ2XcOWk
+oD1ATdgAM3aKmSOxNtNItKKxBe1mkqDH41NbWx7xMad78gDznyeT0tjEOltN2bM
uXU1R/jgR/vq5Ec0AhXJyL/ziIcmuV2fSl/ZxT4ARD+16tgPiIx+welTf0v27/JY
adlfkkL5XsPRrbSguISrj7JeaO/gjG3KnDVHcZvYBpDfHqRhCgrosfe26TZcTXx2
cRxOfvBjMz1zJAg+esuUzSkerreyRhzD7RpeZTwi6sxvx82MhYMbA3w1LtgdABio
9JRqZy3xqsIbNv7N46WO/qXL1UMRKb1UyHeW8g8btboz+B4zv1U0Nj+9qxPBbQui
dgL9LQIDAQABo1AwTjAdBgNVHQ4EFgQUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwHwYD
VR0jBBgwFoAUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAvEVnUYsIOt87rggmLPqEueynkuQ+562M8EDHSQl82zbe
xDCxeg3DvPgKb+RvaUdt1362z/szK10SoeMgx6+EQLoV9LiVqXwNqeYfixrhrdw3
ppAhYYhymdkbUQCEMHypmXP1vPhAz4o8Bs+eES1M+zO6ErBiD7SqkmBElT+GixJC
6epC9ZQFs+dw3lPlbiZSsGE85sqc3VAs0/JgpL/pb1/Eg4s0FUhZD2C2uWdSyZGc
g0/v3aXJCp4j/9VoNhI1WXz3M45nysZIL5OQgXymLqJElQa1pZ3Wa4i/nidvT4AT
Xlxc/qijM8set/nOqp7hVd5J0uG6qdwLRILUddZ6OpXd7ZNi1EXg+Bpc7ehzGsDt
3UFGzYXDjxYnK2frQfjLS8stOQIqSrGthW6x0fdkVx0y8BByvd5J6+JmZl4UZfzA
m99VxXSt4B9x6BvnY7ktzcFDOjtuLc4B/7yg9fv1eQuStA4cHGGAttsCg1X/Kx8W
PvkkeH0UWDZ9vhH9K36703z89da6MWF+bz92B0+4HoOmlVaXRkvblsNaynJnL0LC
Ayry7QBxuh5cMnDdRwJB3AVJIiJ1GVpb7aGvBOnx+s2lwRv9HWtghb+cbwwktx1M
JHyBf3GZNSWTpKY7cD8V+NnBv3UuioOVVo+XAU4LF/bYUjdRpxWADJizNtZrtFo=
-----END CERTIFICATE-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFbTCCA1WgAwIBAgIJAN338vEmMtLsMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV
BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz
dHMxEjAQBgNVBAMMCXRlc3QtZmlsZTAeFw0xNzAyMDEyMzUyMDhaFw0yNzAxMzAy
MzUyMDhaME0xCzAJBgNVBAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYD
VQQKDAxHb2xhbmcgVGVzdHMxEjAQBgNVBAMMCXRlc3QtZmlsZTCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAPMGiLjdiffQo3Xc8oUe7wsDhSaAJFOhO6Qs
i0xYrYl7jmCuz9rGD2fdgk5cLqGazKuQ6fIFzHXFU2BKs4CWXt9KO0KFEhfvZeuW
jG5d7C1ZUiuKOrPqjKVu8SZtFPc7y7Ke7msXzY+Z2LLyiJJ93LCMq4+cTSGNXVlI
KqUxhxeoD5/QkUPyQy/ilu3GMYfx/YORhDP6Edcuskfj8wRh1UxBejP8YPMvI6St
cE2GkxoEGqDWnQ/61F18te6WI3MD29tnKXOkXVhnSC+yvRLljotW2/tAhHKBG4tj
iQWT5Ri4Wrw2tXxPKRLsVWc7e1/hdxhnuvYpXkWNhKsm002jzkFXlzfEwPd8nZdw
5aT6gPUBN2AAzdoqZI7E200i0orEF7WaSoMfjU1tbHvExp3vyAPOfJ5PS2MQ6W03
Zsy5dTVH+OBH++rkRzQCFcnIv/OIhya5XZ9KX9nFPgBEP7Xq2A+IjH7B6VN/S/bv
8lhp2V+SQvlew9GttKC4hKuPsl5o7+CMbcqcNUdxm9gGkN8epGEKCuix97bpNlxN
fHZxHE5+8GMzPXMkCD56y5TNKR6ut7JGHMPtGl5lPCLqzG/HzYyFgxsDfDUu2B0A
GKj0lGpnLfGqwhs2/s3jpY7+pcvVQxEpvVTId5byDxu1ujP4HjO/VTQ2P72rE8Ft
C6J2Av0tAgMBAAGjUDBOMB0GA1UdDgQWBBTLT/RbyfBB/Pa07oBnaM+QSJPO9TAf
BgNVHSMEGDAWgBTLT/RbyfBB/Pa07oBnaM+QSJPO9TAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4ICAQB3sCntCcQwhMgRPPyvOCMyTcQ/Iv+cpfxz2Ck14nlx
AkEAH2CH0ov5GWTt07/ur3aa5x+SAKi0J3wTD1cdiw4U/6Uin6jWGKKxvoo4IaeK
SbM8w/6eKx6UbmHx7PA/eRABY9tTlpdPCVgw7/o3WDr03QM+IAtatzvaCPPczake
pbdLwmBZB/v8V+6jUajy6jOgdSH0PyffGnt7MWgDETmNC6p/Xigp5eh+C8Fb4NGT
xgHES5PBC+sruWp4u22bJGDKTvYNdZHsnw/CaKQWNsQqwisxa3/8N5v+PCff/pxl
r05pE3PdHn9JrCl4iWdVlgtiI9BoPtQyDfa/OEFaScE8KYR8LxaAgdgp3zYncWls
BpwQ6Y/A2wIkhlD9eEp5Ib2hz7isXOs9UwjdriKqrBXqcIAE5M+YIk3+KAQKxAtd
4YsK3CSJ010uphr12YKqlScj4vuKFjuOtd5RyyMIxUG3lrrhAu2AzCeKCLdVgA8+
75FrYMApUdvcjp4uzbBoED4XRQlx9kdFHVbYgmE/+yddBYJM8u4YlgAL0hW2/D8p
z9JWIfxVmjJnBnXaKGBuiUyZ864A3PJndP6EMMo7TzS2CDnfCYuJjvI0KvDjFNmc
rQA04+qfMSEz3nmKhbbZu4eYLzlADhfH8tT4GMtXf71WLA5AUHGf2Y4+HIHTsmHG
vQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIJAL8a/lsnspOqMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV
BAYTAlVLMRMwEQYDVQQIDApUZXN0LVN0YXRlMRUwEwYDVQQKDAxHb2xhbmcgVGVz
dHMxETAPBgNVBAMMCHRlc3QtZGlyMB4XDTE3MDIwMTIzNTAyN1oXDTI3MDEzMDIz
NTAyN1owTDELMAkGA1UEBhMCVUsxEzARBgNVBAgMClRlc3QtU3RhdGUxFTATBgNV
BAoMDEdvbGFuZyBUZXN0czERMA8GA1UEAwwIdGVzdC1kaXIwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDzBoi43Yn30KN13PKFHu8LA4UmgCRToTukLItM
WK2Je45grs/axg9n3YJOXC6hmsyrkOnyBcx1xVNgSrOAll7fSjtChRIX72Xrloxu
XewtWVIrijqz6oylbvEmbRT3O8uynu5rF82Pmdiy8oiSfdywjKuPnE0hjV1ZSCql
MYcXqA+f0JFD8kMv4pbtxjGH8f2DkYQz+hHXLrJH4/MEYdVMQXoz/GDzLyOkrXBN
hpMaBBqg1p0P+tRdfLXuliNzA9vbZylzpF1YZ0gvsr0S5Y6LVtv7QIRygRuLY4kF
k+UYuFq8NrV8TykS7FVnO3tf4XcYZ7r2KV5FjYSrJtNNo85BV5c3xMD3fJ2XcOWk
+oD1ATdgAM3aKmSOxNtNItKKxBe1mkqDH41NbWx7xMad78gDznyeT0tjEOltN2bM
uXU1R/jgR/vq5Ec0AhXJyL/ziIcmuV2fSl/ZxT4ARD+16tgPiIx+welTf0v27/JY
adlfkkL5XsPRrbSguISrj7JeaO/gjG3KnDVHcZvYBpDfHqRhCgrosfe26TZcTXx2
cRxOfvBjMz1zJAg+esuUzSkerreyRhzD7RpeZTwi6sxvx82MhYMbA3w1LtgdABio
9JRqZy3xqsIbNv7N46WO/qXL1UMRKb1UyHeW8g8btboz+B4zv1U0Nj+9qxPBbQui
dgL9LQIDAQABo1AwTjAdBgNVHQ4EFgQUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwHwYD
VR0jBBgwFoAUy0/0W8nwQfz2tO6AZ2jPkEiTzvUwDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAvEVnUYsIOt87rggmLPqEueynkuQ+562M8EDHSQl82zbe
xDCxeg3DvPgKb+RvaUdt1362z/szK10SoeMgx6+EQLoV9LiVqXwNqeYfixrhrdw3
ppAhYYhymdkbUQCEMHypmXP1vPhAz4o8Bs+eES1M+zO6ErBiD7SqkmBElT+GixJC
6epC9ZQFs+dw3lPlbiZSsGE85sqc3VAs0/JgpL/pb1/Eg4s0FUhZD2C2uWdSyZGc
g0/v3aXJCp4j/9VoNhI1WXz3M45nysZIL5OQgXymLqJElQa1pZ3Wa4i/nidvT4AT
Xlxc/qijM8set/nOqp7hVd5J0uG6qdwLRILUddZ6OpXd7ZNi1EXg+Bpc7ehzGsDt
3UFGzYXDjxYnK2frQfjLS8stOQIqSrGthW6x0fdkVx0y8BByvd5J6+JmZl4UZfzA
m99VxXSt4B9x6BvnY7ktzcFDOjtuLc4B/7yg9fv1eQuStA4cHGGAttsCg1X/Kx8W
PvkkeH0UWDZ9vhH9K36703z89da6MWF+bz92B0+4HoOmlVaXRkvblsNaynJnL0LC
Ayry7QBxuh5cMnDdRwJB3AVJIiJ1GVpb7aGvBOnx+s2lwRv9HWtghb+cbwwktx1M
JHyBf3GZNSWTpKY7cD8V+NnBv3UuioOVVo+XAU4LF/bYUjdRpxWADJizNtZrtFo=
-----END CERTIFICATE-----

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// This file is run by the x509 tests to ensure that a program with minimal
// imports can sign certificates without errors resulting from missing hash
// functions.
package main
import (
"crypto/rand"
// START CT CHANGES
"github.com/google/certificate-transparency-go/x509"
"github.com/google/certificate-transparency-go/x509/pkix"
// END CT CHANGES
"encoding/pem"
"math/big"
"time"
)
func main() {
block, _ := pem.Decode([]byte(pemPrivateKey))
rsaPriv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic("Failed to parse private key: " + err.Error())
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "test",
Organization: []string{"Σ Acme Co"},
},
NotBefore: time.Unix(1000, 0),
NotAfter: time.Unix(100000, 0),
KeyUsage: x509.KeyUsageCertSign,
}
if _, err = x509.CreateCertificate(rand.Reader, &template, &template, &rsaPriv.PublicKey, rsaPriv); err != nil {
panic("failed to create certificate with basic imports: " + err.Error())
}
}
var pemPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
-----END RSA PRIVATE KEY-----
`

View File

@ -0,0 +1,73 @@
// Copyright 2016 Google Inc. 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/ioutil"
"net/http"
"strings"
)
// 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 := ioutil.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 := ioutil.ReadAll(rsp.Body)
if err != nil {
return nil, fmt.Errorf("failed to ioutil.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
}

View File

@ -0,0 +1,26 @@
// Copyright 2017 Google Inc. 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
}

View File

@ -0,0 +1,169 @@
// Copyright 2017 Google Inc. 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" X509v3 Freshest CRL:"))
showCritical(critical)
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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: %d (%#[1]x)\n", c.SerialNumber))
result.WriteString(fmt.Sprintf(" Revocation Date : %v\n", c.RevocationTime))
count, critical = OIDInExtensions(x509.OIDExtensionCRLReasons, c.Extensions)
if count > 0 {
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" Invalidity Date:"))
showCritical(critical)
result.WriteString(fmt.Sprintf(" %s\n", c.InvalidityDate))
}
count, critical = OIDInExtensions(x509.OIDExtensionCertificateIssuer, c.Extensions)
if count > 0 {
result.WriteString(fmt.Sprintf(" 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()
}

View File

@ -0,0 +1,670 @@
// Copyright 2016 Google Inc. 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"
"strconv"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/asn1"
"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.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 {
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, byte := range data {
if ii%count == 0 {
if ii > 0 {
buf.WriteString("\n")
}
buf.WriteString(prefix)
}
buf.WriteString(fmt.Sprintf("%02x:", byte))
}
}
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
}
return fmt.Sprintf("%v", oid), -1
}
func publicKeyToString(algo 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 {
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(fmt.Sprintf("Certificate:\n"))
result.WriteString(fmt.Sprintf(" Data:\n"))
result.WriteString(fmt.Sprintf(" Version: %d (%#x)\n", cert.Version, cert.Version-1))
result.WriteString(fmt.Sprintf(" Serial Number: %d (%#[1]x)\n", cert.SerialNumber))
result.WriteString(fmt.Sprintf(" Signature Algorithm: %v\n", cert.SignatureAlgorithm))
result.WriteString(fmt.Sprintf(" Issuer: %v\n", NameToString(cert.Issuer)))
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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)
showCTPoison(&result, cert)
showCTSCT(&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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf("\n"))
}
}
func showSubjectAltName(result *bytes.Buffer, cert *x509.Certificate) {
count, critical := OIDInExtensions(x509.OIDExtensionSubjectAltName, cert.Extensions)
if count > 0 {
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" X509v3 Name Constraints:"))
showCritical(result, critical)
if len(cert.PermittedDNSDomains) > 0 {
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" X509v3 CRL Distribution Points:"))
showCritical(result, critical)
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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 showCTPoison(result *bytes.Buffer, cert *x509.Certificate) {
count, critical := OIDInExtensions(x509.OIDExtensionCTPoison, cert.Extensions)
if count > 0 {
result.WriteString(fmt.Sprintf(" 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(fmt.Sprintf(" 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(fmt.Sprintf(" Signature:\n"))
appendHexData(result, sct.Signature.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.OIDExtensionCTPoison) ||
oid.Equal(x509.OIDExtensionCTSCT) {
return true
}
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))
if block == nil {
return nil, errors.New("failed to decode PEM")
}
return x509.ParseCertificate(block.Bytes)
}