chore: vendor

This commit is contained in:
2024-08-04 11:06:58 +02:00
parent 2a5985e44e
commit 04aec8232f
3557 changed files with 981078 additions and 1 deletions

View File

@ -0,0 +1,30 @@
Copyright (c) 2015, Docker Inc.
Copyright (c) 2014-2015 Prime Directive, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Prime Directive, Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,6 @@
## Credits
This implementation was originally forked from [flynn/go-tuf](https://github.com/flynn/go-tuf)
This implementation retains the same 3 Clause BSD license present on
the original flynn implementation.

View File

@ -0,0 +1,732 @@
package tuf
import (
"fmt"
"github.com/docker/go/canonical/json"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/trustpinning"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/signed"
"github.com/theupdateframework/notary/tuf/utils"
)
// ErrBuildDone is returned when any functions are called on RepoBuilder, and it
// is already finished building
var ErrBuildDone = fmt.Errorf(
"the builder has finished building and cannot accept any more input or produce any more output")
// ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
// with the wrong type of metadata for the state that it's in
type ErrInvalidBuilderInput struct{ msg string }
func (e ErrInvalidBuilderInput) Error() string {
return e.msg
}
// ConsistentInfo is the consistent name and size of a role, or just the name
// of the role and a -1 if no file metadata for the role is known
type ConsistentInfo struct {
RoleName data.RoleName
fileMeta data.FileMeta
}
// ChecksumKnown determines whether or not we know enough to provide a size and
// consistent name
func (c ConsistentInfo) ChecksumKnown() bool {
// empty hash, no size : this is the zero value
return len(c.fileMeta.Hashes) > 0 || c.fileMeta.Length != 0
}
// ConsistentName returns the consistent name (rolename.sha256) for the role
// given this consistent information
func (c ConsistentInfo) ConsistentName() string {
return utils.ConsistentName(c.RoleName.String(), c.fileMeta.Hashes[notary.SHA256])
}
// Length returns the expected length of the role as per this consistent
// information - if no checksum information is known, the size is -1.
func (c ConsistentInfo) Length() int64 {
if c.ChecksumKnown() {
return c.fileMeta.Length
}
return -1
}
// RepoBuilder is an interface for an object which builds a tuf.Repo
type RepoBuilder interface {
Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error
LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error
GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
Finish() (*Repo, *Repo, error)
BootstrapNewBuilder() RepoBuilder
BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
// informative functions
IsLoaded(roleName data.RoleName) bool
GetLoadedVersion(roleName data.RoleName) int
GetConsistentInfo(roleName data.RoleName) ConsistentInfo
}
// finishedBuilder refuses any more input or output
type finishedBuilder struct{}
func (f finishedBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
return ErrBuildDone
}
func (f finishedBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
return ErrBuildDone
}
func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
return nil, 0, ErrBuildDone
}
func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
return nil, 0, ErrBuildDone
}
func (f finishedBuilder) Finish() (*Repo, *Repo, error) { return nil, nil, ErrBuildDone }
func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
return f
}
func (f finishedBuilder) IsLoaded(roleName data.RoleName) bool { return false }
func (f finishedBuilder) GetLoadedVersion(roleName data.RoleName) int { return 0 }
func (f finishedBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
return ConsistentInfo{RoleName: roleName}
}
// NewRepoBuilder is the only way to get a pre-built RepoBuilder
func NewRepoBuilder(gun data.GUN, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
}
// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
func NewBuilderFromRepo(gun data.GUN, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return &repoBuilderWrapper{
RepoBuilder: &repoBuilder{
repo: repo,
invalidRoles: NewRepo(nil),
gun: gun,
trustpin: trustpin,
loadedNotChecksummed: make(map[data.RoleName][]byte),
},
}
}
// repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
// the embed out with a finishedBuilder
type repoBuilderWrapper struct {
RepoBuilder
}
func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) {
switch rbw.RepoBuilder.(type) {
case finishedBuilder:
return rbw.RepoBuilder.Finish()
default:
old := rbw.RepoBuilder
rbw.RepoBuilder = finishedBuilder{}
return old.Finish()
}
}
// repoBuilder actually builds a tuf.Repo
type repoBuilder struct {
repo *Repo
invalidRoles *Repo
// needed for root trust pininng verification
gun data.GUN
trustpin trustpinning.TrustPinConfig
// in case we load root and/or targets before snapshot and timestamp (
// or snapshot and not timestamp), so we know what to verify when the
// data with checksums come in
loadedNotChecksummed map[data.RoleName][]byte
// bootstrapped values to validate a new root
prevRoot *data.SignedRoot
bootstrappedRootChecksum *data.FileMeta
// for bootstrapping the next builder
nextRootChecksum *data.FileMeta
}
func (rb *repoBuilder) Finish() (*Repo, *Repo, error) {
return rb.repo, rb.invalidRoles, nil
}
func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
repo: NewRepo(rb.repo.cryptoService),
invalidRoles: NewRepo(nil),
gun: rb.gun,
loadedNotChecksummed: make(map[data.RoleName][]byte),
trustpin: rb.trustpin,
prevRoot: rb.repo.Root,
bootstrappedRootChecksum: rb.nextRootChecksum,
}}
}
func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
repo: NewRepo(rb.repo.cryptoService),
gun: rb.gun,
loadedNotChecksummed: make(map[data.RoleName][]byte),
trustpin: trustpin,
prevRoot: rb.repo.Root,
bootstrappedRootChecksum: rb.nextRootChecksum,
}}
}
// IsLoaded returns whether a particular role has already been loaded
func (rb *repoBuilder) IsLoaded(roleName data.RoleName) bool {
switch roleName {
case data.CanonicalRootRole:
return rb.repo.Root != nil
case data.CanonicalSnapshotRole:
return rb.repo.Snapshot != nil
case data.CanonicalTimestampRole:
return rb.repo.Timestamp != nil
default:
return rb.repo.Targets[roleName] != nil
}
}
// GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the
// minimum valid version number) otherwise
func (rb *repoBuilder) GetLoadedVersion(roleName data.RoleName) int {
switch {
case roleName == data.CanonicalRootRole && rb.repo.Root != nil:
return rb.repo.Root.Signed.Version
case roleName == data.CanonicalSnapshotRole && rb.repo.Snapshot != nil:
return rb.repo.Snapshot.Signed.Version
case roleName == data.CanonicalTimestampRole && rb.repo.Timestamp != nil:
return rb.repo.Timestamp.Signed.Version
default:
if tgts, ok := rb.repo.Targets[roleName]; ok {
return tgts.Signed.Version
}
}
return 1
}
// GetConsistentInfo returns the consistent name and size of a role, if it is known,
// otherwise just the rolename and a -1 for size (both of which are inside a
// ConsistentInfo object)
func (rb *repoBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta
switch roleName {
case data.CanonicalTimestampRole:
// we do not want to get a consistent timestamp, but we do want to
// limit its size
info.fileMeta.Length = notary.MaxTimestampSize
case data.CanonicalSnapshotRole:
if rb.repo.Timestamp != nil {
info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName.String()]
}
case data.CanonicalRootRole:
switch {
case rb.bootstrappedRootChecksum != nil:
info.fileMeta = *rb.bootstrappedRootChecksum
case rb.repo.Snapshot != nil:
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
}
default:
if rb.repo.Snapshot != nil {
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
}
}
return info
}
func (rb *repoBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
return rb.loadOptions(roleName, content, minVersion, allowExpired, false, false)
}
// LoadRootForUpdate adds additional flags for updating the root.json file
func (rb *repoBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
if err := rb.loadOptions(data.CanonicalRootRole, content, minVersion, !isFinal, !isFinal, true); err != nil {
return err
}
if !isFinal {
rb.prevRoot = rb.repo.Root
}
return nil
}
// loadOptions adds additional flags that should only be used for updating the root.json
func (rb *repoBuilder) loadOptions(roleName data.RoleName, content []byte, minVersion int, allowExpired, skipChecksum, allowLoaded bool) error {
if !data.ValidRole(roleName) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
}
if !allowLoaded && rb.IsLoaded(roleName) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)}
}
var err error
switch roleName {
case data.CanonicalRootRole:
break
case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole:
err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole})
default: // delegations
err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalTargetsRole})
}
if err != nil {
return err
}
switch roleName {
case data.CanonicalRootRole:
return rb.loadRoot(content, minVersion, allowExpired, skipChecksum)
case data.CanonicalSnapshotRole:
return rb.loadSnapshot(content, minVersion, allowExpired)
case data.CanonicalTimestampRole:
return rb.loadTimestamp(content, minVersion, allowExpired)
case data.CanonicalTargetsRole:
return rb.loadTargets(content, minVersion, allowExpired)
default:
return rb.loadDelegation(roleName, content, minVersion, allowExpired)
}
}
func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []data.RoleName) error {
for _, req := range prereqRoles {
if !rb.IsLoaded(req) {
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)}
}
}
return nil
}
// GenerateSnapshot generates a new snapshot given a previous (optional) snapshot
// We can't just load the previous snapshot, because it may have been signed by a different
// snapshot key (maybe from a previous root version). Note that we need the root role and
// targets role to be loaded, because we need to generate metadata for both (and we need
// the root to be loaded so we can get the snapshot role to sign with)
func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
switch {
case rb.repo.cryptoService == nil:
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot without a cryptoservice"}
case rb.IsLoaded(data.CanonicalSnapshotRole):
return nil, 0, ErrInvalidBuilderInput{msg: "snapshot has already been loaded"}
case rb.IsLoaded(data.CanonicalTimestampRole):
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"}
}
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole}); err != nil {
return nil, 0, err
}
// If there is no previous snapshot, we need to generate one, and so the targets must
// have already been loaded. Otherwise, so long as the previous snapshot structure is
// valid (it has a targets meta), we're good.
switch prev {
case nil:
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalTargetsRole}); err != nil {
return nil, 0, err
}
if err := rb.repo.InitSnapshot(); err != nil {
rb.repo.Snapshot = nil
return nil, 0, err
}
default:
if err := data.IsValidSnapshotStructure(prev.Signed); err != nil {
return nil, 0, err
}
rb.repo.Snapshot = prev
}
sgnd, err := rb.repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
if err != nil {
rb.repo.Snapshot = nil
return nil, 0, err
}
sgndJSON, err := json.Marshal(sgnd)
if err != nil {
rb.repo.Snapshot = nil
return nil, 0, err
}
// loadedNotChecksummed should currently contain the root awaiting checksumming,
// since it has to have been loaded. Since the snapshot was generated using
// the root and targets data (there may not be any) that have been loaded,
// remove all of them from rb.loadedNotChecksummed
for tgtName := range rb.repo.Targets {
delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
}
delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
// The timestamp can't have been loaded yet, so we want to cache the snapshot
// bytes so we can validate the checksum when a timestamp gets generated or
// loaded later.
rb.loadedNotChecksummed[data.CanonicalSnapshotRole] = sgndJSON
return sgndJSON, rb.repo.Snapshot.Signed.Version, nil
}
// GenerateTimestamp generates a new timestamp given a previous (optional) timestamp
// We can't just load the previous timestamp, because it may have been signed by a different
// timestamp key (maybe from a previous root version)
func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
switch {
case rb.repo.cryptoService == nil:
return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate timestamp without a cryptoservice"}
case rb.IsLoaded(data.CanonicalTimestampRole):
return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
}
// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
// have the snapshot loaded first
if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
return nil, 0, err
}
switch prev {
case nil:
if err := rb.repo.InitTimestamp(); err != nil {
rb.repo.Timestamp = nil
return nil, 0, err
}
default:
if err := data.IsValidTimestampStructure(prev.Signed); err != nil {
return nil, 0, err
}
rb.repo.Timestamp = prev
}
sgnd, err := rb.repo.SignTimestamp(data.DefaultExpires(data.CanonicalTimestampRole))
if err != nil {
rb.repo.Timestamp = nil
return nil, 0, err
}
sgndJSON, err := json.Marshal(sgnd)
if err != nil {
rb.repo.Timestamp = nil
return nil, 0, err
}
// The snapshot should have been loaded (and not checksummed, since a timestamp
// cannot have been loaded), so it is awaiting checksumming. Since this
// timestamp was generated using the snapshot awaiting checksumming, we can
// remove it from rb.loadedNotChecksummed. There should be no other items
// awaiting checksumming now since loading/generating a snapshot should have
// cleared out everything else in `loadNotChecksummed`.
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
return sgndJSON, rb.repo.Timestamp.Signed.Version, nil
}
// loadRoot loads a root if one has not been loaded
func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired, skipChecksum bool) error {
roleName := data.CanonicalRootRole
signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole, skipChecksum)
if err != nil {
return err
}
// ValidateRoot validates against the previous root's role, as well as validates that the root
// itself is self-consistent with its own signatures and thresholds.
// This assumes that ValidateRoot calls data.RootFromSigned, which validates
// the metadata, rather than just unmarshalling signedObject into a SignedRoot object itself.
signedRoot, err := trustpinning.ValidateRoot(rb.prevRoot, signedObj, rb.gun, rb.trustpin)
if err != nil {
return err
}
if err := signed.VerifyVersion(&(signedRoot.Signed.SignedCommon), minVersion); err != nil {
return err
}
if !allowExpired { // check must go at the end because all other validation should pass
if err := signed.VerifyExpiry(&(signedRoot.Signed.SignedCommon), roleName); err != nil {
return err
}
}
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
if err != nil { // this should never happen since the root has been validated
return err
}
rb.repo.Root = signedRoot
rb.repo.originalRootRole = rootRole
return nil
}
func (rb *repoBuilder) loadTimestamp(content []byte, minVersion int, allowExpired bool) error {
roleName := data.CanonicalTimestampRole
timestampRole, err := rb.repo.Root.BuildBaseRole(roleName)
if err != nil { // this should never happen, since it's already been validated
return err
}
signedObj, err := rb.bytesToSignedAndValidateSigs(timestampRole, content)
if err != nil {
return err
}
signedTimestamp, err := data.TimestampFromSigned(signedObj)
if err != nil {
return err
}
if err := signed.VerifyVersion(&(signedTimestamp.Signed.SignedCommon), minVersion); err != nil {
return err
}
if !allowExpired { // check must go at the end because all other validation should pass
if err := signed.VerifyExpiry(&(signedTimestamp.Signed.SignedCommon), roleName); err != nil {
return err
}
}
if err := rb.validateChecksumsFromTimestamp(signedTimestamp); err != nil {
return err
}
rb.repo.Timestamp = signedTimestamp
return nil
}
func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired bool) error {
roleName := data.CanonicalSnapshotRole
snapshotRole, err := rb.repo.Root.BuildBaseRole(roleName)
if err != nil { // this should never happen, since it's already been validated
return err
}
signedObj, err := rb.bytesToSignedAndValidateSigs(snapshotRole, content)
if err != nil {
return err
}
signedSnapshot, err := data.SnapshotFromSigned(signedObj)
if err != nil {
return err
}
if err := signed.VerifyVersion(&(signedSnapshot.Signed.SignedCommon), minVersion); err != nil {
return err
}
if !allowExpired { // check must go at the end because all other validation should pass
if err := signed.VerifyExpiry(&(signedSnapshot.Signed.SignedCommon), roleName); err != nil {
return err
}
}
// at this point, the only thing left to validate is existing checksums - we can use
// this snapshot to bootstrap the next builder if needed - and we don't need to do
// the 2-value assignment since we've already validated the signedSnapshot, which MUST
// have root metadata
rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole.String()]
rb.nextRootChecksum = &rootMeta
if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil {
return err
}
rb.repo.Snapshot = signedSnapshot
return nil
}
func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired bool) error {
roleName := data.CanonicalTargetsRole
targetsRole, err := rb.repo.Root.BuildBaseRole(roleName)
if err != nil { // this should never happen, since it's already been validated
return err
}
signedObj, err := rb.bytesToSignedAndValidateSigs(targetsRole, content)
if err != nil {
return err
}
signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
if err != nil {
return err
}
if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
return err
}
if !allowExpired { // check must go at the end because all other validation should pass
if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
return err
}
}
signedTargets.Signatures = signedObj.Signatures
rb.repo.Targets[roleName] = signedTargets
return nil
}
func (rb *repoBuilder) loadDelegation(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
delegationRole, err := rb.repo.GetDelegationRole(roleName)
if err != nil {
return err
}
// bytesToSigned checks checksum
signedObj, err := rb.bytesToSigned(content, roleName, false)
if err != nil {
return err
}
signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
if err != nil {
return err
}
if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
// don't capture in invalidRoles because the role we received is a rollback
return err
}
// verify signature
if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
rb.invalidRoles.Targets[roleName] = signedTargets
return err
}
if !allowExpired { // check must go at the end because all other validation should pass
if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
rb.invalidRoles.Targets[roleName] = signedTargets
return err
}
}
signedTargets.Signatures = signedObj.Signatures
rb.repo.Targets[roleName] = signedTargets
return nil
}
func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp) error {
sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
if ok {
// by this point, the SignedTimestamp has been validated so it must have a snapshot hash
snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
if err := data.CheckHashes(sn, data.CanonicalSnapshotRole.String(), snMeta); err != nil {
return err
}
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
}
return nil
}
func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error {
var goodRoles []data.RoleName
for roleName, loadedBytes := range rb.loadedNotChecksummed {
switch roleName {
case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
break
default:
if err := data.CheckHashes(loadedBytes, roleName.String(), sn.Signed.Meta[roleName.String()].Hashes); err != nil {
return err
}
goodRoles = append(goodRoles, roleName)
}
}
for _, roleName := range goodRoles {
delete(rb.loadedNotChecksummed, roleName)
}
return nil
}
func (rb *repoBuilder) validateChecksumFor(content []byte, roleName data.RoleName) error {
// validate the bootstrap checksum for root, if provided
if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
if err := data.CheckHashes(content, roleName.String(), rb.bootstrappedRootChecksum.Hashes); err != nil {
return err
}
}
// but we also want to cache the root content, so that when the snapshot is
// loaded it is validated (to make sure everything in the repo is self-consistent)
checksums := rb.getChecksumsFor(roleName)
if checksums != nil { // as opposed to empty, in which case hash check should fail
if err := data.CheckHashes(content, roleName.String(), *checksums); err != nil {
return err
}
} else if roleName != data.CanonicalTimestampRole {
// timestamp is the only role which does not need to be checksummed, but
// for everything else, cache the contents in the list of roles that have
// not been checksummed by the snapshot/timestamp yet
rb.loadedNotChecksummed[roleName] = content
}
return nil
}
// Checksums the given bytes, and if they validate, convert to a data.Signed object.
// If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that
// haven't been checksummed (unless it's a timestamp, which has no checksum reference).
func (rb *repoBuilder) bytesToSigned(content []byte, roleName data.RoleName, skipChecksum bool) (*data.Signed, error) {
if !skipChecksum {
if err := rb.validateChecksumFor(content, roleName); err != nil {
return nil, err
}
}
// unmarshal to signed
signedObj := &data.Signed{}
if err := json.Unmarshal(content, signedObj); err != nil {
return nil, err
}
return signedObj, nil
}
func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) {
signedObj, err := rb.bytesToSigned(content, role.Name, false)
if err != nil {
return nil, err
}
// verify signature
if err := signed.VerifySignatures(signedObj, role); err != nil {
return nil, err
}
return signedObj, nil
}
// If the checksum reference (the loaded timestamp for the snapshot role, and
// the loaded snapshot for every other role except timestamp and snapshot) is nil,
// then return nil for the checksums, meaning that the checksum is not yet
// available. If the checksum reference *is* loaded, then always returns the
// Hashes object for the given role - if it doesn't exist, returns an empty Hash
// object (against which any checksum validation would fail).
func (rb *repoBuilder) getChecksumsFor(role data.RoleName) *data.Hashes {
var hashes data.Hashes
switch role {
case data.CanonicalTimestampRole:
return nil
case data.CanonicalSnapshotRole:
if rb.repo.Timestamp == nil {
return nil
}
hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
default:
if rb.repo.Snapshot == nil {
return nil
}
hashes = rb.repo.Snapshot.Signed.Meta[role.String()].Hashes
}
return &hashes
}

View File

@ -0,0 +1,53 @@
package data
import "fmt"
// ErrInvalidMetadata is the error to be returned when metadata is invalid
type ErrInvalidMetadata struct {
role RoleName
msg string
}
func (e ErrInvalidMetadata) Error() string {
return fmt.Sprintf("%s type metadata invalid: %s", e.role.String(), e.msg)
}
// ErrMissingMeta - couldn't find the FileMeta object for the given Role, or
// the FileMeta object contained no supported checksums
type ErrMissingMeta struct {
Role string
}
func (e ErrMissingMeta) Error() string {
return fmt.Sprintf("no checksums for supported algorithms were provided for %s", e.Role)
}
// ErrInvalidChecksum is the error to be returned when checksum is invalid
type ErrInvalidChecksum struct {
alg string
}
func (e ErrInvalidChecksum) Error() string {
return fmt.Sprintf("%s checksum invalid", e.alg)
}
// ErrMismatchedChecksum is the error to be returned when checksum is mismatched
type ErrMismatchedChecksum struct {
alg string
name string
expected string
}
func (e ErrMismatchedChecksum) Error() string {
return fmt.Sprintf("%s checksum for %s did not match: expected %s", e.alg, e.name,
e.expected)
}
// ErrCertExpired is the error to be returned when a certificate has expired
type ErrCertExpired struct {
CN string
}
func (e ErrCertExpired) Error() string {
return fmt.Sprintf("certificate with CN %s is expired", e.CN)
}

View File

@ -0,0 +1,529 @@
package data
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/hex"
"errors"
"io"
"math/big"
"github.com/docker/go/canonical/json"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ed25519"
)
// PublicKey is the necessary interface for public keys
type PublicKey interface {
ID() string
Algorithm() string
Public() []byte
}
// PrivateKey adds the ability to access the private key
type PrivateKey interface {
PublicKey
Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error)
Private() []byte
CryptoSigner() crypto.Signer
SignatureAlgorithm() SigAlgorithm
}
// KeyPair holds the public and private key bytes
type KeyPair struct {
Public []byte `json:"public"`
Private []byte `json:"private"`
}
// Keys represents a map of key ID to PublicKey object. It's necessary
// to allow us to unmarshal into an interface via the json.Unmarshaller
// interface
type Keys map[string]PublicKey
// UnmarshalJSON implements the json.Unmarshaller interface
func (ks *Keys) UnmarshalJSON(data []byte) error {
parsed := make(map[string]TUFKey)
err := json.Unmarshal(data, &parsed)
if err != nil {
return err
}
final := make(map[string]PublicKey)
for k, tk := range parsed {
final[k] = typedPublicKey(tk)
}
*ks = final
return nil
}
// KeyList represents a list of keys
type KeyList []PublicKey
// UnmarshalJSON implements the json.Unmarshaller interface
func (ks *KeyList) UnmarshalJSON(data []byte) error {
parsed := make([]TUFKey, 0, 1)
err := json.Unmarshal(data, &parsed)
if err != nil {
return err
}
final := make([]PublicKey, 0, len(parsed))
for _, tk := range parsed {
final = append(final, typedPublicKey(tk))
}
*ks = final
return nil
}
// IDs generates a list of the hex encoded key IDs in the KeyList
func (ks KeyList) IDs() []string {
keyIDs := make([]string, 0, len(ks))
for _, k := range ks {
keyIDs = append(keyIDs, k.ID())
}
return keyIDs
}
func typedPublicKey(tk TUFKey) PublicKey {
switch tk.Algorithm() {
case ECDSAKey:
return &ECDSAPublicKey{TUFKey: tk}
case ECDSAx509Key:
return &ECDSAx509PublicKey{TUFKey: tk}
case RSAKey:
return &RSAPublicKey{TUFKey: tk}
case RSAx509Key:
return &RSAx509PublicKey{TUFKey: tk}
case ED25519Key:
return &ED25519PublicKey{TUFKey: tk}
}
return &UnknownPublicKey{TUFKey: tk}
}
func typedPrivateKey(tk TUFKey) (PrivateKey, error) {
private := tk.Value.Private
tk.Value.Private = nil
switch tk.Algorithm() {
case ECDSAKey:
return NewECDSAPrivateKey(
&ECDSAPublicKey{
TUFKey: tk,
},
private,
)
case ECDSAx509Key:
return NewECDSAPrivateKey(
&ECDSAx509PublicKey{
TUFKey: tk,
},
private,
)
case RSAKey:
return NewRSAPrivateKey(
&RSAPublicKey{
TUFKey: tk,
},
private,
)
case RSAx509Key:
return NewRSAPrivateKey(
&RSAx509PublicKey{
TUFKey: tk,
},
private,
)
case ED25519Key:
return NewED25519PrivateKey(
ED25519PublicKey{
TUFKey: tk,
},
private,
)
}
return &UnknownPrivateKey{
TUFKey: tk,
privateKey: privateKey{private: private},
}, nil
}
// NewPublicKey creates a new, correctly typed PublicKey, using the
// UnknownPublicKey catchall for unsupported ciphers
func NewPublicKey(alg string, public []byte) PublicKey {
tk := TUFKey{
Type: alg,
Value: KeyPair{
Public: public,
},
}
return typedPublicKey(tk)
}
// NewPrivateKey creates a new, correctly typed PrivateKey, using the
// UnknownPrivateKey catchall for unsupported ciphers
func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
tk := TUFKey{
Type: pubKey.Algorithm(),
Value: KeyPair{
Public: pubKey.Public(),
Private: private, // typedPrivateKey moves this value
},
}
return typedPrivateKey(tk)
}
// UnmarshalPublicKey is used to parse individual public keys in JSON
func UnmarshalPublicKey(data []byte) (PublicKey, error) {
var parsed TUFKey
err := json.Unmarshal(data, &parsed)
if err != nil {
return nil, err
}
return typedPublicKey(parsed), nil
}
// UnmarshalPrivateKey is used to parse individual private keys in JSON
func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
var parsed TUFKey
err := json.Unmarshal(data, &parsed)
if err != nil {
return nil, err
}
return typedPrivateKey(parsed)
}
// TUFKey is the structure used for both public and private keys in TUF.
// Normally it would make sense to use a different structures for public and
// private keys, but that would change the key ID algorithm (since the canonical
// JSON would be different). This structure should normally be accessed through
// the PublicKey or PrivateKey interfaces.
type TUFKey struct {
id string
Type string `json:"keytype"`
Value KeyPair `json:"keyval"`
}
// Algorithm returns the algorithm of the key
func (k TUFKey) Algorithm() string {
return k.Type
}
// ID efficiently generates if necessary, and caches the ID of the key
func (k *TUFKey) ID() string {
if k.id == "" {
pubK := TUFKey{
Type: k.Algorithm(),
Value: KeyPair{
Public: k.Public(),
Private: nil,
},
}
data, err := json.MarshalCanonical(&pubK)
if err != nil {
logrus.Error("Error generating key ID:", err)
}
digest := sha256.Sum256(data)
k.id = hex.EncodeToString(digest[:])
}
return k.id
}
// Public returns the public bytes
func (k TUFKey) Public() []byte {
return k.Value.Public
}
// Public key types
// ECDSAPublicKey represents an ECDSA key using a raw serialization
// of the public key
type ECDSAPublicKey struct {
TUFKey
}
// ECDSAx509PublicKey represents an ECDSA key using an x509 cert
// as the serialized format of the public key
type ECDSAx509PublicKey struct {
TUFKey
}
// RSAPublicKey represents an RSA key using a raw serialization
// of the public key
type RSAPublicKey struct {
TUFKey
}
// RSAx509PublicKey represents an RSA key using an x509 cert
// as the serialized format of the public key
type RSAx509PublicKey struct {
TUFKey
}
// ED25519PublicKey represents an ED25519 key using a raw serialization
// of the public key
type ED25519PublicKey struct {
TUFKey
}
// UnknownPublicKey is a catchall for key types that are not supported
type UnknownPublicKey struct {
TUFKey
}
// NewECDSAPublicKey initializes a new public key with the ECDSAKey type
func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
return &ECDSAPublicKey{
TUFKey: TUFKey{
Type: ECDSAKey,
Value: KeyPair{
Public: public,
Private: nil,
},
},
}
}
// NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
return &ECDSAx509PublicKey{
TUFKey: TUFKey{
Type: ECDSAx509Key,
Value: KeyPair{
Public: public,
Private: nil,
},
},
}
}
// NewRSAPublicKey initializes a new public key with the RSA type
func NewRSAPublicKey(public []byte) *RSAPublicKey {
return &RSAPublicKey{
TUFKey: TUFKey{
Type: RSAKey,
Value: KeyPair{
Public: public,
Private: nil,
},
},
}
}
// NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
return &RSAx509PublicKey{
TUFKey: TUFKey{
Type: RSAx509Key,
Value: KeyPair{
Public: public,
Private: nil,
},
},
}
}
// NewED25519PublicKey initializes a new public key with the ED25519Key type
func NewED25519PublicKey(public []byte) *ED25519PublicKey {
return &ED25519PublicKey{
TUFKey: TUFKey{
Type: ED25519Key,
Value: KeyPair{
Public: public,
Private: nil,
},
},
}
}
// Private key types
type privateKey struct {
private []byte
}
type signer struct {
signer crypto.Signer
}
// ECDSAPrivateKey represents a private ECDSA key
type ECDSAPrivateKey struct {
PublicKey
privateKey
signer
}
// RSAPrivateKey represents a private RSA key
type RSAPrivateKey struct {
PublicKey
privateKey
signer
}
// ED25519PrivateKey represents a private ED25519 key
type ED25519PrivateKey struct {
ED25519PublicKey
privateKey
}
// UnknownPrivateKey is a catchall for unsupported key types
type UnknownPrivateKey struct {
TUFKey
privateKey
}
// NewECDSAPrivateKey initializes a new ECDSA private key
func NewECDSAPrivateKey(public PublicKey, private []byte) (*ECDSAPrivateKey, error) {
switch public.(type) {
case *ECDSAPublicKey, *ECDSAx509PublicKey:
default:
return nil, errors.New("invalid public key type provided to NewECDSAPrivateKey")
}
ecdsaPrivKey, err := x509.ParseECPrivateKey(private)
if err != nil {
return nil, err
}
return &ECDSAPrivateKey{
PublicKey: public,
privateKey: privateKey{private: private},
signer: signer{signer: ecdsaPrivKey},
}, nil
}
// NewRSAPrivateKey initialized a new RSA private key
func NewRSAPrivateKey(public PublicKey, private []byte) (*RSAPrivateKey, error) {
switch public.(type) {
case *RSAPublicKey, *RSAx509PublicKey:
default:
return nil, errors.New("invalid public key type provided to NewRSAPrivateKey")
}
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(private)
if err != nil {
return nil, err
}
return &RSAPrivateKey{
PublicKey: public,
privateKey: privateKey{private: private},
signer: signer{signer: rsaPrivKey},
}, nil
}
// NewED25519PrivateKey initialized a new ED25519 private key
func NewED25519PrivateKey(public ED25519PublicKey, private []byte) (*ED25519PrivateKey, error) {
return &ED25519PrivateKey{
ED25519PublicKey: public,
privateKey: privateKey{private: private},
}, nil
}
// Private return the serialized private bytes of the key
func (k privateKey) Private() []byte {
return k.private
}
// CryptoSigner returns the underlying crypto.Signer for use cases where we need the default
// signature or public key functionality (like when we generate certificates)
func (s signer) CryptoSigner() crypto.Signer {
return s.signer
}
// CryptoSigner returns the ED25519PrivateKey which already implements crypto.Signer
func (k ED25519PrivateKey) CryptoSigner() crypto.Signer {
return nil
}
// CryptoSigner returns the UnknownPrivateKey which already implements crypto.Signer
func (k UnknownPrivateKey) CryptoSigner() crypto.Signer {
return nil
}
type ecdsaSig struct {
R *big.Int
S *big.Int
}
// Sign creates an ecdsa signature
func (k ECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
ecdsaPrivKey, ok := k.CryptoSigner().(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("signer was based on the wrong key type")
}
hashed := sha256.Sum256(msg)
sigASN1, err := ecdsaPrivKey.Sign(rand, hashed[:], opts)
if err != nil {
return nil, err
}
sig := ecdsaSig{}
_, err = asn1.Unmarshal(sigASN1, &sig)
if err != nil {
return nil, err
}
rBytes, sBytes := sig.R.Bytes(), sig.S.Bytes()
octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3
// MUST include leading zeros in the output
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
rBuf = append(rBuf, rBytes...)
sBuf = append(sBuf, sBytes...)
return append(rBuf, sBuf...), nil
}
// Sign creates an rsa signature
func (k RSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
hashed := sha256.Sum256(msg)
if opts == nil {
opts = &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
Hash: crypto.SHA256,
}
}
return k.CryptoSigner().Sign(rand, hashed[:], opts)
}
// Sign creates an ed25519 signature
func (k ED25519PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
priv := make([]byte, ed25519.PrivateKeySize)
// The ed25519 key is serialized as public key then private key, so just use private key here.
copy(priv, k.private[ed25519.PublicKeySize:])
return ed25519.Sign(ed25519.PrivateKey(priv), msg)[:], nil
}
// Sign on an UnknownPrivateKey raises an error because the client does not
// know how to sign with this key type.
func (k UnknownPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
return nil, errors.New("unknown key type, cannot sign")
}
// SignatureAlgorithm returns the SigAlgorithm for a ECDSAPrivateKey
func (k ECDSAPrivateKey) SignatureAlgorithm() SigAlgorithm {
return ECDSASignature
}
// SignatureAlgorithm returns the SigAlgorithm for a RSAPrivateKey
func (k RSAPrivateKey) SignatureAlgorithm() SigAlgorithm {
return RSAPSSSignature
}
// SignatureAlgorithm returns the SigAlgorithm for a ED25519PrivateKey
func (k ED25519PrivateKey) SignatureAlgorithm() SigAlgorithm {
return EDDSASignature
}
// SignatureAlgorithm returns the SigAlgorithm for an UnknownPrivateKey
func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm {
return ""
}
// PublicKeyFromPrivate returns a new TUFKey based on a private key, with
// the private key bytes guaranteed to be nil.
func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
return typedPublicKey(TUFKey{
Type: pk.Algorithm(),
Value: KeyPair{
Public: pk.Public(),
Private: nil,
},
})
}

View File

@ -0,0 +1,339 @@
package data
import (
"fmt"
"path"
"regexp"
"strings"
"github.com/sirupsen/logrus"
)
// Canonical base role names
var (
CanonicalRootRole RoleName = "root"
CanonicalTargetsRole RoleName = "targets"
CanonicalSnapshotRole RoleName = "snapshot"
CanonicalTimestampRole RoleName = "timestamp"
)
// BaseRoles is an easy to iterate list of the top level
// roles.
var BaseRoles = []RoleName{
CanonicalRootRole,
CanonicalTargetsRole,
CanonicalSnapshotRole,
CanonicalTimestampRole,
}
// Regex for validating delegation names
var delegationRegexp = regexp.MustCompile("^[-a-z0-9_/]+$")
// ErrNoSuchRole indicates the roles doesn't exist
type ErrNoSuchRole struct {
Role RoleName
}
func (e ErrNoSuchRole) Error() string {
return fmt.Sprintf("role does not exist: %s", e.Role)
}
// ErrInvalidRole represents an error regarding a role. Typically
// something like a role for which sone of the public keys were
// not found in the TUF repo.
type ErrInvalidRole struct {
Role RoleName
Reason string
}
func (e ErrInvalidRole) Error() string {
if e.Reason != "" {
return fmt.Sprintf("tuf: invalid role %s. %s", e.Role, e.Reason)
}
return fmt.Sprintf("tuf: invalid role %s.", e.Role)
}
// ValidRole only determines the name is semantically
// correct. For target delegated roles, it does NOT check
// the appropriate parent roles exist.
func ValidRole(name RoleName) bool {
if IsDelegation(name) {
return true
}
for _, v := range BaseRoles {
if name == v {
return true
}
}
return false
}
// IsDelegation checks if the role is a delegation or a root role
func IsDelegation(role RoleName) bool {
strRole := role.String()
targetsBase := CanonicalTargetsRole + "/"
whitelistedChars := delegationRegexp.MatchString(strRole)
// Limit size of full role string to 255 chars for db column size limit
correctLength := len(role) < 256
// Removes ., .., extra slashes, and trailing slash
isClean := path.Clean(strRole) == strRole
return strings.HasPrefix(strRole, targetsBase.String()) &&
whitelistedChars &&
correctLength &&
isClean
}
// IsBaseRole checks if the role is a base role
func IsBaseRole(role RoleName) bool {
for _, baseRole := range BaseRoles {
if role == baseRole {
return true
}
}
return false
}
// IsWildDelegation determines if a role represents a valid wildcard delegation
// path, i.e. targets/*, targets/foo/*.
// The wildcard may only appear as the final part of the delegation and must
// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
func IsWildDelegation(role RoleName) bool {
if path.Clean(role.String()) != role.String() {
return false
}
base := role.Parent()
if !(IsDelegation(base) || base == CanonicalTargetsRole) {
return false
}
return role[len(role)-2:] == "/*"
}
// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
type BaseRole struct {
Keys map[string]PublicKey
Name RoleName
Threshold int
}
// NewBaseRole creates a new BaseRole object with the provided parameters
func NewBaseRole(name RoleName, threshold int, keys ...PublicKey) BaseRole {
r := BaseRole{
Name: name,
Threshold: threshold,
Keys: make(map[string]PublicKey),
}
for _, k := range keys {
r.Keys[k.ID()] = k
}
return r
}
// ListKeys retrieves the public keys valid for this role
func (b BaseRole) ListKeys() KeyList {
return listKeys(b.Keys)
}
// ListKeyIDs retrieves the list of key IDs valid for this role
func (b BaseRole) ListKeyIDs() []string {
return listKeyIDs(b.Keys)
}
// Equals returns whether this BaseRole equals another BaseRole
func (b BaseRole) Equals(o BaseRole) bool {
if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
return false
}
for keyID, key := range b.Keys {
oKey, ok := o.Keys[keyID]
if !ok || key.ID() != oKey.ID() {
return false
}
}
return true
}
// DelegationRole is an internal representation of a delegation role, with its public keys included
type DelegationRole struct {
BaseRole
Paths []string
}
func listKeys(keyMap map[string]PublicKey) KeyList {
keys := KeyList{}
for _, key := range keyMap {
keys = append(keys, key)
}
return keys
}
func listKeyIDs(keyMap map[string]PublicKey) []string {
keyIDs := []string{}
for id := range keyMap {
keyIDs = append(keyIDs, id)
}
return keyIDs
}
// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
// returning a copy of the role with validated paths as if it was a direct child
func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
if !d.IsParentOf(child) {
return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
}
return DelegationRole{
BaseRole: BaseRole{
Keys: child.Keys,
Name: child.Name,
Threshold: child.Threshold,
},
Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
}, nil
}
// IsParentOf returns whether the passed in delegation role is the direct child of this role,
// determined by delegation name.
// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
func (d DelegationRole) IsParentOf(child DelegationRole) bool {
return path.Dir(child.Name.String()) == d.Name.String()
}
// CheckPaths checks if a given path is valid for the role
func (d DelegationRole) CheckPaths(path string) bool {
return checkPaths(path, d.Paths)
}
func checkPaths(path string, permitted []string) bool {
for _, p := range permitted {
if strings.HasPrefix(path, p) {
return true
}
}
return false
}
// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
validPaths := []string{}
if len(delegationPaths) == 0 {
return validPaths
}
// Validate each individual delegation path
for _, delgPath := range delegationPaths {
isPrefixed := false
for _, parentPath := range parentPaths {
if strings.HasPrefix(delgPath, parentPath) {
isPrefixed = true
break
}
}
// If the delegation path did not match prefix against any parent path, it is not valid
if isPrefixed {
validPaths = append(validPaths, delgPath)
}
}
return validPaths
}
// RootRole is a cut down role as it appears in the root.json
// Eventually should only be used for immediately before and after serialization/deserialization
type RootRole struct {
KeyIDs []string `json:"keyids"`
Threshold int `json:"threshold"`
}
// Role is a more verbose role as they appear in targets delegations
// Eventually should only be used for immediately before and after serialization/deserialization
type Role struct {
RootRole
Name RoleName `json:"name"`
Paths []string `json:"paths,omitempty"`
}
// NewRole creates a new Role object from the given parameters
func NewRole(name RoleName, threshold int, keyIDs, paths []string) (*Role, error) {
if IsDelegation(name) {
if len(paths) == 0 {
logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
}
}
if threshold < 1 {
return nil, ErrInvalidRole{Role: name}
}
if !ValidRole(name) {
return nil, ErrInvalidRole{Role: name}
}
return &Role{
RootRole: RootRole{
KeyIDs: keyIDs,
Threshold: threshold,
},
Name: name,
Paths: paths,
}, nil
}
// CheckPaths checks if a given path is valid for the role
func (r Role) CheckPaths(path string) bool {
return checkPaths(path, r.Paths)
}
// AddKeys merges the ids into the current list of role key ids
func (r *Role) AddKeys(ids []string) {
r.KeyIDs = mergeStrSlices(r.KeyIDs, ids)
}
// AddPaths merges the paths into the current list of role paths
func (r *Role) AddPaths(paths []string) error {
if len(paths) == 0 {
return nil
}
r.Paths = mergeStrSlices(r.Paths, paths)
return nil
}
// RemoveKeys removes the ids from the current list of key ids
func (r *Role) RemoveKeys(ids []string) {
r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
}
// RemovePaths removes the paths from the current list of role paths
func (r *Role) RemovePaths(paths []string) {
r.Paths = subtractStrSlices(r.Paths, paths)
}
func mergeStrSlices(orig, new []string) []string {
have := make(map[string]bool)
for _, e := range orig {
have[e] = true
}
merged := make([]string, len(orig), len(orig)+len(new))
copy(merged, orig)
for _, e := range new {
if !have[e] {
merged = append(merged, e)
}
}
return merged
}
func subtractStrSlices(orig, remove []string) []string {
kill := make(map[string]bool)
for _, e := range remove {
kill[e] = true
}
var keep []string
for _, e := range orig {
if !kill[e] {
keep = append(keep, e)
}
}
return keep
}

View File

@ -0,0 +1,171 @@
package data
import (
"fmt"
"github.com/docker/go/canonical/json"
)
// SignedRoot is a fully unpacked root.json
type SignedRoot struct {
Signatures []Signature
Signed Root
Dirty bool
}
// Root is the Signed component of a root.json
type Root struct {
SignedCommon
Keys Keys `json:"keys"`
Roles map[RoleName]*RootRole `json:"roles"`
ConsistentSnapshot bool `json:"consistent_snapshot"`
}
// isValidRootStructure returns an error, or nil, depending on whether the content of the struct
// is valid for root metadata. This does not check signatures or expiry, just that
// the metadata content is valid.
func isValidRootStructure(r Root) error {
expectedType := TUFTypes[CanonicalRootRole]
if r.Type != expectedType {
return ErrInvalidMetadata{
role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
}
if r.Version < 1 {
return ErrInvalidMetadata{
role: CanonicalRootRole, msg: "version cannot be less than 1"}
}
// all the base roles MUST appear in the root.json - other roles are allowed,
// but other than the mirror role (not currently supported) are out of spec
for _, roleName := range BaseRoles {
roleObj, ok := r.Roles[roleName]
if !ok || roleObj == nil {
return ErrInvalidMetadata{
role: CanonicalRootRole, msg: fmt.Sprintf("missing %s role specification", roleName)}
}
if err := isValidRootRoleStructure(CanonicalRootRole, roleName, *roleObj, r.Keys); err != nil {
return err
}
}
return nil
}
func isValidRootRoleStructure(metaContainingRole, rootRoleName RoleName, r RootRole, validKeys Keys) error {
if r.Threshold < 1 {
return ErrInvalidMetadata{
role: metaContainingRole,
msg: fmt.Sprintf("invalid threshold specified for %s: %v ", rootRoleName, r.Threshold),
}
}
for _, keyID := range r.KeyIDs {
if _, ok := validKeys[keyID]; !ok {
return ErrInvalidMetadata{
role: metaContainingRole,
msg: fmt.Sprintf("key ID %s specified in %s without corresponding key", keyID, rootRoleName),
}
}
}
return nil
}
// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
func NewRoot(keys map[string]PublicKey, roles map[RoleName]*RootRole, consistent bool) (*SignedRoot, error) {
signedRoot := &SignedRoot{
Signatures: make([]Signature, 0),
Signed: Root{
SignedCommon: SignedCommon{
Type: TUFTypes[CanonicalRootRole],
Version: 0,
Expires: DefaultExpires(CanonicalRootRole),
},
Keys: keys,
Roles: roles,
ConsistentSnapshot: consistent,
},
Dirty: true,
}
return signedRoot, nil
}
// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name.
// Will error for invalid role name or key metadata within this SignedRoot
func (r SignedRoot) BuildBaseRole(roleName RoleName) (BaseRole, error) {
roleData, ok := r.Signed.Roles[roleName]
if !ok {
return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"}
}
// Get all public keys for the base role from TUF metadata
keyIDs := roleData.KeyIDs
pubKeys := make(map[string]PublicKey)
for _, keyID := range keyIDs {
pubKey, ok := r.Signed.Keys[keyID]
if !ok {
return BaseRole{}, ErrInvalidRole{
Role: roleName,
Reason: fmt.Sprintf("key with ID %s was not found in root metadata", keyID),
}
}
pubKeys[keyID] = pubKey
}
return BaseRole{
Name: roleName,
Keys: pubKeys,
Threshold: roleData.Threshold,
}, nil
}
// ToSigned partially serializes a SignedRoot for further signing
func (r SignedRoot) ToSigned() (*Signed, error) {
s, err := defaultSerializer.MarshalCanonical(r.Signed)
if err != nil {
return nil, err
}
// cast into a json.RawMessage
signed := json.RawMessage{}
err = signed.UnmarshalJSON(s)
if err != nil {
return nil, err
}
sigs := make([]Signature, len(r.Signatures))
copy(sigs, r.Signatures)
return &Signed{
Signatures: sigs,
Signed: &signed,
}, nil
}
// MarshalJSON returns the serialized form of SignedRoot as bytes
func (r SignedRoot) MarshalJSON() ([]byte, error) {
signed, err := r.ToSigned()
if err != nil {
return nil, err
}
return defaultSerializer.Marshal(signed)
}
// RootFromSigned fully unpacks a Signed object into a SignedRoot and ensures
// that it is a valid SignedRoot
func RootFromSigned(s *Signed) (*SignedRoot, error) {
r := Root{}
if s.Signed == nil {
return nil, ErrInvalidMetadata{
role: CanonicalRootRole,
msg: "root file contained an empty payload",
}
}
if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
return nil, err
}
if err := isValidRootStructure(r); err != nil {
return nil, err
}
sigs := make([]Signature, len(s.Signatures))
copy(sigs, s.Signatures)
return &SignedRoot{
Signatures: sigs,
Signed: r,
}, nil
}

View File

@ -0,0 +1,36 @@
package data
import "github.com/docker/go/canonical/json"
// Serializer is an interface that can marshal and unmarshal TUF data. This
// is expected to be a canonical JSON marshaller
type serializer interface {
MarshalCanonical(from interface{}) ([]byte, error)
Marshal(from interface{}) ([]byte, error)
Unmarshal(from []byte, to interface{}) error
}
// CanonicalJSON marshals to and from canonical JSON
type canonicalJSON struct{}
// MarshalCanonical returns the canonical JSON form of a thing
func (c canonicalJSON) MarshalCanonical(from interface{}) ([]byte, error) {
return json.MarshalCanonical(from)
}
// Marshal returns the regular non-canonical JSON form of a thing
func (c canonicalJSON) Marshal(from interface{}) ([]byte, error) {
return json.Marshal(from)
}
// Unmarshal unmarshals some JSON bytes
func (c canonicalJSON) Unmarshal(from []byte, to interface{}) error {
return json.Unmarshal(from, to)
}
// defaultSerializer is a canonical JSON serializer
var defaultSerializer serializer = canonicalJSON{}
func setDefaultSerializer(s serializer) {
defaultSerializer = s
}

View File

@ -0,0 +1,169 @@
package data
import (
"bytes"
"fmt"
"github.com/docker/go/canonical/json"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary"
)
// SignedSnapshot is a fully unpacked snapshot.json
type SignedSnapshot struct {
Signatures []Signature
Signed Snapshot
Dirty bool
}
// Snapshot is the Signed component of a snapshot.json
type Snapshot struct {
SignedCommon
Meta Files `json:"meta"`
}
// IsValidSnapshotStructure returns an error, or nil, depending on whether the content of the
// struct is valid for snapshot metadata. This does not check signatures or expiry, just that
// the metadata content is valid.
func IsValidSnapshotStructure(s Snapshot) error {
expectedType := TUFTypes[CanonicalSnapshotRole]
if s.Type != expectedType {
return ErrInvalidMetadata{
role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
}
if s.Version < 1 {
return ErrInvalidMetadata{
role: CanonicalSnapshotRole, msg: "version cannot be less than one"}
}
for _, file := range []RoleName{CanonicalRootRole, CanonicalTargetsRole} {
// Meta is a map of FileMeta, so if the role isn't in the map it returns
// an empty FileMeta, which has an empty map, and you can check on keys
// from an empty map.
//
// For now sha256 is required and sha512 is not.
if _, ok := s.Meta[file.String()].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{
role: CanonicalSnapshotRole,
msg: fmt.Sprintf("missing %s sha256 checksum information", file.String()),
}
}
if err := CheckValidHashStructures(s.Meta[file.String()].Hashes); err != nil {
return ErrInvalidMetadata{
role: CanonicalSnapshotRole,
msg: fmt.Sprintf("invalid %s checksum information, %v", file.String(), err),
}
}
}
return nil
}
// NewSnapshot initializes a SignedSnapshot with a given top level root
// and targets objects
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
logrus.Debug("generating new snapshot...")
targetsJSON, err := json.Marshal(targets)
if err != nil {
logrus.Debug("Error Marshalling Targets")
return nil, err
}
rootJSON, err := json.Marshal(root)
if err != nil {
logrus.Debug("Error Marshalling Root")
return nil, err
}
rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), NotaryDefaultHashes...)
if err != nil {
return nil, err
}
targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), NotaryDefaultHashes...)
if err != nil {
return nil, err
}
return &SignedSnapshot{
Signatures: make([]Signature, 0),
Signed: Snapshot{
SignedCommon: SignedCommon{
Type: TUFTypes[CanonicalSnapshotRole],
Version: 0,
Expires: DefaultExpires(CanonicalSnapshotRole),
},
Meta: Files{
CanonicalRootRole.String(): rootMeta,
CanonicalTargetsRole.String(): targetsMeta,
},
},
}, nil
}
// ToSigned partially serializes a SignedSnapshot for further signing
func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
s, err := defaultSerializer.MarshalCanonical(sp.Signed)
if err != nil {
return nil, err
}
signed := json.RawMessage{}
err = signed.UnmarshalJSON(s)
if err != nil {
return nil, err
}
sigs := make([]Signature, len(sp.Signatures))
copy(sigs, sp.Signatures)
return &Signed{
Signatures: sigs,
Signed: &signed,
}, nil
}
// AddMeta updates a role in the snapshot with new meta
func (sp *SignedSnapshot) AddMeta(role RoleName, meta FileMeta) {
sp.Signed.Meta[role.String()] = meta
sp.Dirty = true
}
// GetMeta gets the metadata for a particular role, returning an error if it's
// not found
func (sp *SignedSnapshot) GetMeta(role RoleName) (*FileMeta, error) {
if meta, ok := sp.Signed.Meta[role.String()]; ok {
if _, ok := meta.Hashes["sha256"]; ok {
return &meta, nil
}
}
return nil, ErrMissingMeta{Role: role.String()}
}
// DeleteMeta removes a role from the snapshot. If the role doesn't
// exist in the snapshot, it's a noop.
func (sp *SignedSnapshot) DeleteMeta(role RoleName) {
if _, ok := sp.Signed.Meta[role.String()]; ok {
delete(sp.Signed.Meta, role.String())
sp.Dirty = true
}
}
// MarshalJSON returns the serialized form of SignedSnapshot as bytes
func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
signed, err := sp.ToSigned()
if err != nil {
return nil, err
}
return defaultSerializer.Marshal(signed)
}
// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
sp := Snapshot{}
if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
return nil, err
}
if err := IsValidSnapshotStructure(sp); err != nil {
return nil, err
}
sigs := make([]Signature, len(s.Signatures))
copy(sigs, s.Signatures)
return &SignedSnapshot{
Signatures: sigs,
Signed: sp,
}, nil
}

View File

@ -0,0 +1,201 @@
package data
import (
"errors"
"fmt"
"path"
"github.com/docker/go/canonical/json"
)
// SignedTargets is a fully unpacked targets.json, or target delegation
// json file
type SignedTargets struct {
Signatures []Signature
Signed Targets
Dirty bool
}
// Targets is the Signed components of a targets.json or delegation json file
type Targets struct {
SignedCommon
Targets Files `json:"targets"`
Delegations Delegations `json:"delegations,omitempty"`
}
// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
// is valid for targets metadata. This does not check signatures or expiry, just that
// the metadata content is valid.
func isValidTargetsStructure(t Targets, roleName RoleName) error {
if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
return ErrInvalidRole{Role: roleName}
}
// even if it's a delegated role, the metadata type is "Targets"
expectedType := TUFTypes[CanonicalTargetsRole]
if t.Type != expectedType {
return ErrInvalidMetadata{
role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
}
if t.Version < 1 {
return ErrInvalidMetadata{role: roleName, msg: "version cannot be less than one"}
}
for _, roleObj := range t.Delegations.Roles {
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name.String()) != roleName.String() {
return ErrInvalidMetadata{
role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
}
if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
return err
}
}
return nil
}
// NewTargets initializes a new empty SignedTargets object
func NewTargets() *SignedTargets {
return &SignedTargets{
Signatures: make([]Signature, 0),
Signed: Targets{
SignedCommon: SignedCommon{
Type: TUFTypes["targets"],
Version: 0,
Expires: DefaultExpires("targets"),
},
Targets: make(Files),
Delegations: *NewDelegations(),
},
Dirty: true,
}
}
// GetMeta attempts to find the targets entry for the path. It
// will return nil in the case of the target not being found.
func (t SignedTargets) GetMeta(path string) *FileMeta {
for p, meta := range t.Signed.Targets {
if p == path {
return &meta
}
}
return nil
}
// GetValidDelegations filters the delegation roles specified in the signed targets, and
// only returns roles that are direct children and restricts their paths
func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
roles := t.buildDelegationRoles()
result := []DelegationRole{}
for _, r := range roles {
validRole, err := parent.Restrict(r)
if err != nil {
continue
}
result = append(result, validRole)
}
return result
}
// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated.
func (t *SignedTargets) BuildDelegationRole(roleName RoleName) (DelegationRole, error) {
for _, role := range t.Signed.Delegations.Roles {
if role.Name == roleName {
pubKeys := make(map[string]PublicKey)
for _, keyID := range role.KeyIDs {
pubKey, ok := t.Signed.Delegations.Keys[keyID]
if !ok {
// Couldn't retrieve all keys, so stop walking and return invalid role
return DelegationRole{}, ErrInvalidRole{
Role: roleName,
Reason: "role lists unknown key " + keyID + " as a signing key",
}
}
pubKeys[keyID] = pubKey
}
return DelegationRole{
BaseRole: BaseRole{
Name: role.Name,
Keys: pubKeys,
Threshold: role.Threshold,
},
Paths: role.Paths,
}, nil
}
}
return DelegationRole{}, ErrNoSuchRole{Role: roleName}
}
// helper function to create DelegationRole structures from all delegations in a SignedTargets,
// these delegations are read directly from the SignedTargets and not modified or validated
func (t SignedTargets) buildDelegationRoles() []DelegationRole {
var roles []DelegationRole
for _, roleData := range t.Signed.Delegations.Roles {
delgRole, err := t.BuildDelegationRole(roleData.Name)
if err != nil {
continue
}
roles = append(roles, delgRole)
}
return roles
}
// AddTarget adds or updates the meta for the given path
func (t *SignedTargets) AddTarget(path string, meta FileMeta) {
t.Signed.Targets[path] = meta
t.Dirty = true
}
// AddDelegation will add a new delegated role with the given keys,
// ensuring the keys either already exist, or are added to the map
// of delegation keys
func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
return errors.New("Not Implemented")
}
// ToSigned partially serializes a SignedTargets for further signing
func (t *SignedTargets) ToSigned() (*Signed, error) {
s, err := defaultSerializer.MarshalCanonical(t.Signed)
if err != nil {
return nil, err
}
signed := json.RawMessage{}
err = signed.UnmarshalJSON(s)
if err != nil {
return nil, err
}
sigs := make([]Signature, len(t.Signatures))
copy(sigs, t.Signatures)
return &Signed{
Signatures: sigs,
Signed: &signed,
}, nil
}
// MarshalJSON returns the serialized form of SignedTargets as bytes
func (t *SignedTargets) MarshalJSON() ([]byte, error) {
signed, err := t.ToSigned()
if err != nil {
return nil, err
}
return defaultSerializer.Marshal(signed)
}
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
// a role name (so it can validate the SignedTargets object)
func TargetsFromSigned(s *Signed, roleName RoleName) (*SignedTargets, error) {
t := Targets{}
if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
return nil, err
}
if err := isValidTargetsStructure(t, roleName); err != nil {
return nil, err
}
sigs := make([]Signature, len(s.Signatures))
copy(sigs, s.Signatures)
return &SignedTargets{
Signatures: sigs,
Signed: t,
}, nil
}

View File

@ -0,0 +1,136 @@
package data
import (
"bytes"
"fmt"
"github.com/docker/go/canonical/json"
"github.com/theupdateframework/notary"
)
// SignedTimestamp is a fully unpacked timestamp.json
type SignedTimestamp struct {
Signatures []Signature
Signed Timestamp
Dirty bool
}
// Timestamp is the Signed component of a timestamp.json
type Timestamp struct {
SignedCommon
Meta Files `json:"meta"`
}
// IsValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
// is valid for timestamp metadata. This does not check signatures or expiry, just that
// the metadata content is valid.
func IsValidTimestampStructure(t Timestamp) error {
expectedType := TUFTypes[CanonicalTimestampRole]
if t.Type != expectedType {
return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
}
if t.Version < 1 {
return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: "version cannot be less than one"}
}
// Meta is a map of FileMeta, so if the role isn't in the map it returns
// an empty FileMeta, which has an empty map, and you can check on keys
// from an empty map.
//
// For now sha256 is required and sha512 is not.
if _, ok := t.Meta[CanonicalSnapshotRole.String()].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"}
}
if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole.String()].Hashes); err != nil {
return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)}
}
return nil
}
// NewTimestamp initializes a timestamp with an existing snapshot
func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
snapshotJSON, err := json.Marshal(snapshot)
if err != nil {
return nil, err
}
snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), NotaryDefaultHashes...)
if err != nil {
return nil, err
}
return &SignedTimestamp{
Signatures: make([]Signature, 0),
Signed: Timestamp{
SignedCommon: SignedCommon{
Type: TUFTypes[CanonicalTimestampRole],
Version: 0,
Expires: DefaultExpires(CanonicalTimestampRole),
},
Meta: Files{
CanonicalSnapshotRole.String(): snapshotMeta,
},
},
}, nil
}
// ToSigned partially serializes a SignedTimestamp such that it can
// be signed
func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
s, err := defaultSerializer.MarshalCanonical(ts.Signed)
if err != nil {
return nil, err
}
signed := json.RawMessage{}
err = signed.UnmarshalJSON(s)
if err != nil {
return nil, err
}
sigs := make([]Signature, len(ts.Signatures))
copy(sigs, ts.Signatures)
return &Signed{
Signatures: sigs,
Signed: &signed,
}, nil
}
// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata,
// or nil if it doesn't exist
func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) {
snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole.String()]
if !ok {
return nil, ErrMissingMeta{Role: CanonicalSnapshotRole.String()}
}
return &snapshotExpected, nil
}
// MarshalJSON returns the serialized form of SignedTimestamp as bytes
func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
signed, err := ts.ToSigned()
if err != nil {
return nil, err
}
return defaultSerializer.Marshal(signed)
}
// TimestampFromSigned parsed a Signed object into a fully unpacked
// SignedTimestamp
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
ts := Timestamp{}
if err := defaultSerializer.Unmarshal(*s.Signed, &ts); err != nil {
return nil, err
}
if err := IsValidTimestampStructure(ts); err != nil {
return nil, err
}
sigs := make([]Signature, len(s.Signatures))
copy(sigs, s.Signatures)
return &SignedTimestamp{
Signatures: sigs,
Signed: ts,
}, nil
}

View File

@ -0,0 +1,390 @@
package data
import (
"bytes"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/hex"
"fmt"
"hash"
"io"
"io/ioutil"
"path"
"strings"
"time"
"github.com/docker/go/canonical/json"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary"
)
// GUN is a Globally Unique Name. It is used to identify trust collections.
// An example usage of this is for container image repositories.
// For example: myregistry.io/myuser/myimage
type GUN string
func (g GUN) String() string {
return string(g)
}
// RoleName type for specifying role
type RoleName string
func (r RoleName) String() string {
return string(r)
}
// Parent provides the parent path role from the provided child role
func (r RoleName) Parent() RoleName {
return RoleName(path.Dir(r.String()))
}
// MetadataRoleMapToStringMap generates a map string of bytes from a map RoleName of bytes
func MetadataRoleMapToStringMap(roles map[RoleName][]byte) map[string][]byte {
metadata := make(map[string][]byte)
for k, v := range roles {
metadata[k.String()] = v
}
return metadata
}
// NewRoleList generates an array of RoleName objects from a slice of strings
func NewRoleList(roles []string) []RoleName {
var roleNames []RoleName
for _, role := range roles {
roleNames = append(roleNames, RoleName(role))
}
return roleNames
}
// RolesListToStringList generates an array of string objects from a slice of roles
func RolesListToStringList(roles []RoleName) []string {
var roleNames []string
for _, role := range roles {
roleNames = append(roleNames, role.String())
}
return roleNames
}
// SigAlgorithm for types of signatures
type SigAlgorithm string
func (k SigAlgorithm) String() string {
return string(k)
}
const defaultHashAlgorithm = "sha256"
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
// the various role files.
var NotaryDefaultExpiries = map[RoleName]time.Duration{
CanonicalRootRole: notary.NotaryRootExpiry,
CanonicalTargetsRole: notary.NotaryTargetsExpiry,
CanonicalSnapshotRole: notary.NotarySnapshotExpiry,
CanonicalTimestampRole: notary.NotaryTimestampExpiry,
}
// Signature types
const (
EDDSASignature SigAlgorithm = "eddsa"
RSAPSSSignature SigAlgorithm = "rsapss"
RSAPKCS1v15Signature SigAlgorithm = "rsapkcs1v15"
ECDSASignature SigAlgorithm = "ecdsa"
PyCryptoSignature SigAlgorithm = "pycrypto-pkcs#1 pss"
)
// Key types
const (
ED25519Key = "ed25519"
RSAKey = "rsa"
RSAx509Key = "rsa-x509"
ECDSAKey = "ecdsa"
ECDSAx509Key = "ecdsa-x509"
)
// TUFTypes is the set of metadata types
var TUFTypes = map[RoleName]string{
CanonicalRootRole: "Root",
CanonicalTargetsRole: "Targets",
CanonicalSnapshotRole: "Snapshot",
CanonicalTimestampRole: "Timestamp",
}
// ValidTUFType checks if the given type is valid for the role
func ValidTUFType(typ string, role RoleName) bool {
if ValidRole(role) {
// All targets delegation roles must have
// the valid type is for targets.
if role == "" {
// role is unknown and does not map to
// a type
return false
}
if strings.HasPrefix(role.String(), CanonicalTargetsRole.String()+"/") {
role = CanonicalTargetsRole
}
}
// most people will just use the defaults so have this optimal check
// first. Do comparison just in case there is some unknown vulnerability
// if a key and value in the map differ.
if v, ok := TUFTypes[role]; ok {
return typ == v
}
return false
}
// Signed is the high level, partially deserialized metadata object
// used to verify signatures before fully unpacking, or to add signatures
// before fully packing
type Signed struct {
Signed *json.RawMessage `json:"signed"`
Signatures []Signature `json:"signatures"`
}
// SignedCommon contains the fields common to the Signed component of all
// TUF metadata files
type SignedCommon struct {
Type string `json:"_type"`
Expires time.Time `json:"expires"`
Version int `json:"version"`
}
// SignedMeta is used in server validation where we only need signatures
// and common fields
type SignedMeta struct {
Signed SignedCommon `json:"signed"`
Signatures []Signature `json:"signatures"`
}
// Signature is a signature on a piece of metadata
type Signature struct {
KeyID string `json:"keyid"`
Method SigAlgorithm `json:"method"`
Signature []byte `json:"sig"`
IsValid bool `json:"-"`
}
// Files is the map of paths to file meta container in targets and delegations
// metadata files
type Files map[string]FileMeta
// Hashes is the map of hash type to digest created for each metadata
// and target file
type Hashes map[string][]byte
// NotaryDefaultHashes contains the default supported hash algorithms.
var NotaryDefaultHashes = []string{notary.SHA256, notary.SHA512}
// FileMeta contains the size and hashes for a metadata or target file. Custom
// data can be optionally added.
type FileMeta struct {
Length int64 `json:"length"`
Hashes Hashes `json:"hashes"`
Custom *json.RawMessage `json:"custom,omitempty"`
}
// Equals returns true if the other FileMeta object is equivalent to this one
func (f FileMeta) Equals(o FileMeta) bool {
if o.Length != f.Length || len(o.Hashes) != len(f.Hashes) {
return false
}
if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil {
return false
}
// we don't care if these are valid hashes, just that they are equal
for key, val := range f.Hashes {
if !bytes.Equal(val, o.Hashes[key]) {
return false
}
}
if f.Custom == nil && o.Custom == nil {
return true
}
fBytes, err := f.Custom.MarshalJSON()
if err != nil {
return false
}
oBytes, err := o.Custom.MarshalJSON()
if err != nil {
return false
}
return bytes.Equal(fBytes, oBytes)
}
// CheckHashes verifies all the checksums specified by the "hashes" of the payload.
func CheckHashes(payload []byte, name string, hashes Hashes) error {
cnt := 0
// k, v indicate the hash algorithm and the corresponding value
for k, v := range hashes {
switch k {
case notary.SHA256:
checksum := sha256.Sum256(payload)
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
return ErrMismatchedChecksum{alg: notary.SHA256, name: name, expected: hex.EncodeToString(v)}
}
cnt++
case notary.SHA512:
checksum := sha512.Sum512(payload)
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
return ErrMismatchedChecksum{alg: notary.SHA512, name: name, expected: hex.EncodeToString(v)}
}
cnt++
}
}
if cnt == 0 {
return ErrMissingMeta{Role: name}
}
return nil
}
// CompareMultiHashes verifies that the two Hashes passed in can represent the same data.
// This means that both maps must have at least one key defined for which they map, and no conflicts.
// Note that we check the intersection of map keys, which adds support for non-default hash algorithms in notary
func CompareMultiHashes(hashes1, hashes2 Hashes) error {
// First check if the two hash structures are valid
if err := CheckValidHashStructures(hashes1); err != nil {
return err
}
if err := CheckValidHashStructures(hashes2); err != nil {
return err
}
// Check if they have at least one matching hash, and no conflicts
cnt := 0
for hashAlg, hash1 := range hashes1 {
hash2, ok := hashes2[hashAlg]
if !ok {
continue
}
if subtle.ConstantTimeCompare(hash1[:], hash2[:]) == 0 {
return fmt.Errorf("mismatched %s checksum", hashAlg)
}
// If we reached here, we had a match
cnt++
}
if cnt == 0 {
return fmt.Errorf("at least one matching hash needed")
}
return nil
}
// CheckValidHashStructures returns an error, or nil, depending on whether
// the content of the hashes is valid or not.
func CheckValidHashStructures(hashes Hashes) error {
cnt := 0
for k, v := range hashes {
switch k {
case notary.SHA256:
if len(v) != sha256.Size {
return ErrInvalidChecksum{alg: notary.SHA256}
}
cnt++
case notary.SHA512:
if len(v) != sha512.Size {
return ErrInvalidChecksum{alg: notary.SHA512}
}
cnt++
}
}
if cnt == 0 {
return fmt.Errorf("at least one supported hash needed")
}
return nil
}
// NewFileMeta generates a FileMeta object from the reader, using the
// hash algorithms provided
func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) {
if len(hashAlgorithms) == 0 {
hashAlgorithms = []string{defaultHashAlgorithm}
}
hashes := make(map[string]hash.Hash, len(hashAlgorithms))
for _, hashAlgorithm := range hashAlgorithms {
var h hash.Hash
switch hashAlgorithm {
case notary.SHA256:
h = sha256.New()
case notary.SHA512:
h = sha512.New()
default:
return FileMeta{}, fmt.Errorf("Unknown hash algorithm: %s", hashAlgorithm)
}
hashes[hashAlgorithm] = h
r = io.TeeReader(r, h)
}
n, err := io.Copy(ioutil.Discard, r)
if err != nil {
return FileMeta{}, err
}
m := FileMeta{Length: n, Hashes: make(Hashes, len(hashes))}
for hashAlgorithm, h := range hashes {
m.Hashes[hashAlgorithm] = h.Sum(nil)
}
return m, nil
}
// Delegations holds a tier of targets delegations
type Delegations struct {
Keys Keys `json:"keys"`
Roles []*Role `json:"roles"`
}
// NewDelegations initializes an empty Delegations object
func NewDelegations() *Delegations {
return &Delegations{
Keys: make(map[string]PublicKey),
Roles: make([]*Role, 0),
}
}
// These values are recommended TUF expiry times.
var defaultExpiryTimes = map[RoleName]time.Duration{
CanonicalRootRole: notary.Year,
CanonicalTargetsRole: 90 * notary.Day,
CanonicalSnapshotRole: 7 * notary.Day,
CanonicalTimestampRole: notary.Day,
}
// SetDefaultExpiryTimes allows one to change the default expiries.
func SetDefaultExpiryTimes(times map[RoleName]time.Duration) {
for key, value := range times {
if _, ok := defaultExpiryTimes[key]; !ok {
logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key.String())
continue
}
defaultExpiryTimes[key] = value
}
}
// DefaultExpires gets the default expiry time for the given role
func DefaultExpires(role RoleName) time.Time {
if d, ok := defaultExpiryTimes[role]; ok {
return time.Now().Add(d)
}
var t time.Time
return t.UTC().Round(time.Second)
}
type unmarshalledSignature Signature
// UnmarshalJSON does a custom unmarshalling of the signature JSON
func (s *Signature) UnmarshalJSON(data []byte) error {
uSignature := unmarshalledSignature{}
err := json.Unmarshal(data, &uSignature)
if err != nil {
return err
}
uSignature.Method = SigAlgorithm(strings.ToLower(string(uSignature.Method)))
*s = Signature(uSignature)
return nil
}

View File

@ -0,0 +1,111 @@
package signed
import (
"crypto/rand"
"errors"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/utils"
)
type edCryptoKey struct {
role data.RoleName
privKey data.PrivateKey
}
// Ed25519 implements a simple in memory cryptosystem for ED25519 keys
type Ed25519 struct {
keys map[string]edCryptoKey
}
// NewEd25519 initializes a new empty Ed25519 CryptoService that operates
// entirely in memory
func NewEd25519() *Ed25519 {
return &Ed25519{
make(map[string]edCryptoKey),
}
}
// AddKey allows you to add a private key
func (e *Ed25519) AddKey(role data.RoleName, gun data.GUN, k data.PrivateKey) error {
e.addKey(role, k)
return nil
}
// addKey allows you to add a private key
func (e *Ed25519) addKey(role data.RoleName, k data.PrivateKey) {
e.keys[k.ID()] = edCryptoKey{
role: role,
privKey: k,
}
}
// RemoveKey deletes a key from the signer
func (e *Ed25519) RemoveKey(keyID string) error {
delete(e.keys, keyID)
return nil
}
// ListKeys returns the list of keys IDs for the role
func (e *Ed25519) ListKeys(role data.RoleName) []string {
keyIDs := make([]string, 0, len(e.keys))
for id, edCryptoKey := range e.keys {
if edCryptoKey.role == role {
keyIDs = append(keyIDs, id)
}
}
return keyIDs
}
// ListAllKeys returns the map of keys IDs to role
func (e *Ed25519) ListAllKeys() map[string]data.RoleName {
keys := make(map[string]data.RoleName)
for id, edKey := range e.keys {
keys[id] = edKey.role
}
return keys
}
// Create generates a new key and returns the public part
func (e *Ed25519) Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error) {
if algorithm != data.ED25519Key {
return nil, errors.New("only ED25519 supported by this cryptoservice")
}
private, err := utils.GenerateED25519Key(rand.Reader)
if err != nil {
return nil, err
}
e.addKey(role, private)
return data.PublicKeyFromPrivate(private), nil
}
// PublicKeys returns a map of public keys for the ids provided, when those IDs are found
// in the store.
func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) {
k := make(map[string]data.PublicKey)
for _, keyID := range keyIDs {
if edKey, ok := e.keys[keyID]; ok {
k[keyID] = data.PublicKeyFromPrivate(edKey.privKey)
}
}
return k, nil
}
// GetKey returns a single public key based on the ID
func (e *Ed25519) GetKey(keyID string) data.PublicKey {
if privKey, _, err := e.GetPrivateKey(keyID); err == nil {
return data.PublicKeyFromPrivate(privKey)
}
return nil
}
// GetPrivateKey returns a single private key and role if present, based on the ID
func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, data.RoleName, error) {
if k, ok := e.keys[keyID]; ok {
return k.privKey, k.role, nil
}
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
}

View File

@ -0,0 +1,98 @@
package signed
import (
"fmt"
"strings"
"github.com/theupdateframework/notary/tuf/data"
)
// ErrInsufficientSignatures - can not create enough signatures on a piece of
// metadata
type ErrInsufficientSignatures struct {
FoundKeys int
NeededKeys int
MissingKeyIDs []string
}
func (e ErrInsufficientSignatures) Error() string {
candidates := ""
if len(e.MissingKeyIDs) > 0 {
candidates = fmt.Sprintf(" (%s)", strings.Join(e.MissingKeyIDs, ", "))
}
if e.FoundKeys == 0 {
return fmt.Sprintf("signing keys not available: need %d keys from %d possible keys%s",
e.NeededKeys, len(e.MissingKeyIDs), candidates)
}
return fmt.Sprintf("not enough signing keys: found %d of %d needed keys - %d other possible keys%s",
e.FoundKeys, e.NeededKeys, len(e.MissingKeyIDs), candidates)
}
// ErrExpired indicates a piece of metadata has expired
type ErrExpired struct {
Role data.RoleName
Expired string
}
func (e ErrExpired) Error() string {
return fmt.Sprintf("%s expired at %v", e.Role.String(), e.Expired)
}
// ErrLowVersion indicates the piece of metadata has a version number lower than
// a version number we're already seen for this role
type ErrLowVersion struct {
Actual int
Current int
}
func (e ErrLowVersion) Error() string {
return fmt.Sprintf("version %d is lower than current version %d", e.Actual, e.Current)
}
// ErrRoleThreshold indicates we did not validate enough signatures to meet the threshold
type ErrRoleThreshold struct {
Msg string
}
func (e ErrRoleThreshold) Error() string {
if e.Msg == "" {
return "valid signatures did not meet threshold"
}
return e.Msg
}
// ErrInvalidKeyType indicates the types for the key and signature it's associated with are
// mismatched. Probably a sign of malicious behaviour
type ErrInvalidKeyType struct{}
func (e ErrInvalidKeyType) Error() string {
return "key type is not valid for signature"
}
// ErrInvalidKeyID indicates the specified key ID was incorrect for its associated data
type ErrInvalidKeyID struct{}
func (e ErrInvalidKeyID) Error() string {
return "key ID is not valid for key content"
}
// ErrInvalidKeyLength indicates that while we may support the cipher, the provided
// key length is not specifically supported, i.e. we support RSA, but not 1024 bit keys
type ErrInvalidKeyLength struct {
msg string
}
func (e ErrInvalidKeyLength) Error() string {
return fmt.Sprintf("key length is not supported: %s", e.msg)
}
// ErrNoKeys indicates no signing keys were found when trying to sign
type ErrNoKeys struct {
KeyIDs []string
}
func (e ErrNoKeys) Error() string {
return fmt.Sprintf("could not find necessary signing keys, at least one of these keys must be available: %s",
strings.Join(e.KeyIDs, ", "))
}

View File

@ -0,0 +1,47 @@
package signed
import "github.com/theupdateframework/notary/tuf/data"
// KeyService provides management of keys locally. It will never
// accept or provide private keys. Communication between the KeyService
// and a SigningService happen behind the Create function.
type KeyService interface {
// Create issues a new key pair and is responsible for loading
// the private key into the appropriate signing service.
Create(role data.RoleName, gun data.GUN, algorithm string) (data.PublicKey, error)
// AddKey adds a private key to the specified role and gun
AddKey(role data.RoleName, gun data.GUN, key data.PrivateKey) error
// GetKey retrieves the public key if present, otherwise it returns nil
GetKey(keyID string) data.PublicKey
// GetPrivateKey retrieves the private key and role if present and retrievable,
// otherwise it returns nil and an error
GetPrivateKey(keyID string) (data.PrivateKey, data.RoleName, error)
// RemoveKey deletes the specified key, and returns an error only if the key
// removal fails. If the key doesn't exist, no error should be returned.
RemoveKey(keyID string) error
// ListKeys returns a list of key IDs for the role, or an empty list or
// nil if there are no keys.
ListKeys(role data.RoleName) []string
// ListAllKeys returns a map of all available signing key IDs to role, or
// an empty map or nil if there are no keys.
ListAllKeys() map[string]data.RoleName
}
// CryptoService is deprecated and all instances of its use should be
// replaced with KeyService
type CryptoService interface {
KeyService
}
// Verifier defines an interface for verifying signatures. An implementer
// of this interface should verify signatures for one and only one
// signing scheme.
type Verifier interface {
Verify(key data.PublicKey, sig []byte, msg []byte) error
}

View File

@ -0,0 +1,114 @@
package signed
// The Sign function is a choke point for all code paths that do signing.
// We use this fact to do key ID translation. There are 2 types of key ID:
// - Scoped: the key ID based purely on the data that appears in the TUF
// files. This may be wrapped by a certificate that scopes the
// key to be used in a specific context.
// - Canonical: the key ID based purely on the public key bytes. This is
// used by keystores to easily identify keys that may be reused
// in many scoped locations.
// Currently these types only differ in the context of Root Keys in Notary
// for which the root key is wrapped using an x509 certificate.
import (
"crypto/rand"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/utils"
)
// Sign takes a data.Signed and a cryptoservice containing private keys,
// calculates and adds at least minSignature signatures using signingKeys the
// data.Signed. It will also clean up any signatures that are not in produced
// by either a signingKey or an otherWhitelistedKey.
// Note that in most cases, otherWhitelistedKeys should probably be null. They
// are for keys you don't want to sign with, but you also don't want to remove
// existing signatures by those keys. For instance, if you want to call Sign
// multiple times with different sets of signing keys without undoing removing
// signatures produced by the previous call to Sign.
func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey,
minSignatures int, otherWhitelistedKeys []data.PublicKey) error {
logrus.Debugf("sign called with %d/%d required keys", minSignatures, len(signingKeys))
signatures := make([]data.Signature, 0, len(s.Signatures)+1)
signingKeyIDs := make(map[string]struct{})
tufIDs := make(map[string]data.PublicKey)
privKeys := make(map[string]data.PrivateKey)
// Get all the private key objects related to the public keys
missingKeyIDs := []string{}
for _, key := range signingKeys {
canonicalID, err := utils.CanonicalKeyID(key)
tufIDs[key.ID()] = key
if err != nil {
return err
}
k, _, err := service.GetPrivateKey(canonicalID)
if err != nil {
if _, ok := err.(trustmanager.ErrKeyNotFound); ok {
missingKeyIDs = append(missingKeyIDs, canonicalID)
continue
}
return err
}
privKeys[key.ID()] = k
}
// include the list of otherWhitelistedKeys
for _, key := range otherWhitelistedKeys {
if _, ok := tufIDs[key.ID()]; !ok {
tufIDs[key.ID()] = key
}
}
// Check to ensure we have enough signing keys
if len(privKeys) < minSignatures {
return ErrInsufficientSignatures{FoundKeys: len(privKeys),
NeededKeys: minSignatures, MissingKeyIDs: missingKeyIDs}
}
emptyStruct := struct{}{}
// Do signing and generate list of signatures
for keyID, pk := range privKeys {
sig, err := pk.Sign(rand.Reader, *s.Signed, nil)
if err != nil {
logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err)
return err
}
signingKeyIDs[keyID] = emptyStruct
signatures = append(signatures, data.Signature{
KeyID: keyID,
Method: pk.SignatureAlgorithm(),
Signature: sig[:],
})
}
for i := range s.Signatures {
sig := s.Signatures[i]
if _, ok := signingKeyIDs[sig.KeyID]; ok {
// key is in the set of key IDs for which a signature has been created
continue
}
var (
k data.PublicKey
ok bool
)
if k, ok = tufIDs[sig.KeyID]; !ok {
// key is no longer a valid signing key
continue
}
if err := VerifySignature(*s.Signed, &sig, k); err != nil {
// signature is no longer valid
continue
}
// keep any signatures that still represent valid keys and are
// themselves valid
signatures = append(signatures, sig)
}
s.Signatures = signatures
return nil
}

View File

@ -0,0 +1,264 @@
package signed
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary/tuf/data"
"golang.org/x/crypto/ed25519"
)
const (
minRSAKeySizeBit = 2048 // 2048 bits = 256 bytes
minRSAKeySizeByte = minRSAKeySizeBit / 8
)
// Verifiers serves as a map of all verifiers available on the system and
// can be injected into a verificationService. For testing and configuration
// purposes, it will not be used by default.
var Verifiers = map[data.SigAlgorithm]Verifier{
data.RSAPSSSignature: RSAPSSVerifier{},
data.RSAPKCS1v15Signature: RSAPKCS1v15Verifier{},
data.PyCryptoSignature: RSAPyCryptoVerifier{},
data.ECDSASignature: ECDSAVerifier{},
data.EDDSASignature: Ed25519Verifier{},
}
// Ed25519Verifier used to verify Ed25519 signatures
type Ed25519Verifier struct{}
// Verify checks that an ed25519 signature is valid
func (v Ed25519Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error {
if key.Algorithm() != data.ED25519Key {
return ErrInvalidKeyType{}
}
sigBytes := make([]byte, ed25519.SignatureSize)
if len(sig) != ed25519.SignatureSize {
logrus.Debugf("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig))
return ErrInvalid
}
copy(sigBytes, sig)
keyBytes := make([]byte, ed25519.PublicKeySize)
pub := key.Public()
if len(pub) != ed25519.PublicKeySize {
logrus.Errorf("public key is incorrect size, must be %d, was %d.", ed25519.PublicKeySize, len(pub))
return ErrInvalidKeyLength{msg: fmt.Sprintf("ed25519 public key must be %d bytes.", ed25519.PublicKeySize)}
}
n := copy(keyBytes, key.Public())
if n < ed25519.PublicKeySize {
logrus.Errorf("failed to copy the key, must have %d bytes, copied %d bytes.", ed25519.PublicKeySize, n)
return ErrInvalid
}
if !ed25519.Verify(ed25519.PublicKey(keyBytes), msg, sigBytes) {
logrus.Debugf("failed ed25519 verification")
return ErrInvalid
}
return nil
}
func verifyPSS(key interface{}, digest, sig []byte) error {
rsaPub, ok := key.(*rsa.PublicKey)
if !ok {
logrus.Debugf("value was not an RSA public key")
return ErrInvalid
}
if rsaPub.N.BitLen() < minRSAKeySizeBit {
logrus.Debugf("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen())
return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)}
}
if len(sig) < minRSAKeySizeByte {
logrus.Debugf("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig))
return ErrInvalid
}
opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}
if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, digest[:], sig, &opts); err != nil {
logrus.Debugf("failed RSAPSS verification: %s", err)
return ErrInvalid
}
return nil
}
func getRSAPubKey(key data.PublicKey) (crypto.PublicKey, error) {
algorithm := key.Algorithm()
var pubKey crypto.PublicKey
switch algorithm {
case data.RSAx509Key:
pemCert, _ := pem.Decode([]byte(key.Public()))
if pemCert == nil {
logrus.Debugf("failed to decode PEM-encoded x509 certificate")
return nil, ErrInvalid
}
cert, err := x509.ParseCertificate(pemCert.Bytes)
if err != nil {
logrus.Debugf("failed to parse x509 certificate: %s\n", err)
return nil, ErrInvalid
}
pubKey = cert.PublicKey
case data.RSAKey:
var err error
pubKey, err = x509.ParsePKIXPublicKey(key.Public())
if err != nil {
logrus.Debugf("failed to parse public key: %s\n", err)
return nil, ErrInvalid
}
default:
// only accept RSA keys
logrus.Debugf("invalid key type for RSAPSS verifier: %s", algorithm)
return nil, ErrInvalidKeyType{}
}
return pubKey, nil
}
// RSAPSSVerifier checks RSASSA-PSS signatures
type RSAPSSVerifier struct{}
// Verify does the actual check.
func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error {
// will return err if keytype is not a recognized RSA type
pubKey, err := getRSAPubKey(key)
if err != nil {
return err
}
digest := sha256.Sum256(msg)
return verifyPSS(pubKey, digest[:], sig)
}
// RSAPKCS1v15Verifier checks RSA PKCS1v15 signatures
type RSAPKCS1v15Verifier struct{}
// Verify does the actual verification
func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error {
// will return err if keytype is not a recognized RSA type
pubKey, err := getRSAPubKey(key)
if err != nil {
return err
}
digest := sha256.Sum256(msg)
rsaPub, ok := pubKey.(*rsa.PublicKey)
if !ok {
logrus.Debugf("value was not an RSA public key")
return ErrInvalid
}
if rsaPub.N.BitLen() < minRSAKeySizeBit {
logrus.Debugf("RSA keys less than 2048 bits are not acceptable, provided key has length %d.", rsaPub.N.BitLen())
return ErrInvalidKeyLength{msg: fmt.Sprintf("RSA key must be at least %d bits.", minRSAKeySizeBit)}
}
if len(sig) < minRSAKeySizeByte {
logrus.Debugf("RSA keys less than 2048 bits are not acceptable, provided signature has length %d.", len(sig))
return ErrInvalid
}
if err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig); err != nil {
logrus.Errorf("Failed verification: %s", err.Error())
return ErrInvalid
}
return nil
}
// RSAPyCryptoVerifier checks RSASSA-PSS signatures
type RSAPyCryptoVerifier struct{}
// Verify does the actual check.
// N.B. We have not been able to make this work in a way that is compatible
// with PyCrypto.
func (v RSAPyCryptoVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error {
digest := sha256.Sum256(msg)
if key.Algorithm() != data.RSAKey {
return ErrInvalidKeyType{}
}
k, _ := pem.Decode([]byte(key.Public()))
if k == nil {
logrus.Debugf("failed to decode PEM-encoded x509 certificate")
return ErrInvalid
}
pub, err := x509.ParsePKIXPublicKey(k.Bytes)
if err != nil {
logrus.Debugf("failed to parse public key: %s\n", err)
return ErrInvalid
}
return verifyPSS(pub, digest[:], sig)
}
// ECDSAVerifier checks ECDSA signatures, decoding the keyType appropriately
type ECDSAVerifier struct{}
// Verify does the actual check.
func (v ECDSAVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error {
algorithm := key.Algorithm()
var pubKey crypto.PublicKey
switch algorithm {
case data.ECDSAx509Key:
pemCert, _ := pem.Decode([]byte(key.Public()))
if pemCert == nil {
logrus.Debugf("failed to decode PEM-encoded x509 certificate for keyID: %s", key.ID())
logrus.Debugf("certificate bytes: %s", string(key.Public()))
return ErrInvalid
}
cert, err := x509.ParseCertificate(pemCert.Bytes)
if err != nil {
logrus.Debugf("failed to parse x509 certificate: %s\n", err)
return ErrInvalid
}
pubKey = cert.PublicKey
case data.ECDSAKey:
var err error
pubKey, err = x509.ParsePKIXPublicKey(key.Public())
if err != nil {
logrus.Debugf("Failed to parse private key for keyID: %s, %s\n", key.ID(), err)
return ErrInvalid
}
default:
// only accept ECDSA keys.
logrus.Debugf("invalid key type for ECDSA verifier: %s", algorithm)
return ErrInvalidKeyType{}
}
ecdsaPubKey, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
logrus.Debugf("value isn't an ECDSA public key")
return ErrInvalid
}
sigLength := len(sig)
expectedOctetLength := 2 * ((ecdsaPubKey.Params().BitSize + 7) >> 3)
if sigLength != expectedOctetLength {
logrus.Debugf("signature had an unexpected length")
return ErrInvalid
}
rBytes, sBytes := sig[:sigLength/2], sig[sigLength/2:]
r := new(big.Int).SetBytes(rBytes)
s := new(big.Int).SetBytes(sBytes)
digest := sha256.Sum256(msg)
if !ecdsa.Verify(ecdsaPubKey, digest[:], r, s) {
logrus.Debugf("failed ECDSA signature validation")
return ErrInvalid
}
return nil
}

View File

@ -0,0 +1,123 @@
package signed
import (
"errors"
"fmt"
"strings"
"time"
"github.com/docker/go/canonical/json"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/utils"
)
// Various basic signing errors
var (
ErrNoSignatures = errors.New("tuf: data has no signatures")
ErrInvalid = errors.New("tuf: signature verification failed")
ErrWrongType = errors.New("tuf: meta file has wrong type")
)
// IsExpired checks if the given time passed before the present time
func IsExpired(t time.Time) bool {
return t.Before(time.Now())
}
// VerifyExpiry returns ErrExpired if the metadata is expired
func VerifyExpiry(s *data.SignedCommon, role data.RoleName) error {
if IsExpired(s.Expires) {
logrus.Errorf("Metadata for %s expired", role)
return ErrExpired{Role: role, Expired: s.Expires.Format("Mon Jan 2 15:04:05 MST 2006")}
}
return nil
}
// VerifyVersion returns ErrLowVersion if the metadata version is lower than the min version
func VerifyVersion(s *data.SignedCommon, minVersion int) error {
if s.Version < minVersion {
return ErrLowVersion{Actual: s.Version, Current: minVersion}
}
return nil
}
// VerifySignatures checks the we have sufficient valid signatures for the given role
func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
if len(s.Signatures) == 0 {
return ErrNoSignatures
}
if roleData.Threshold < 1 {
return ErrRoleThreshold{}
}
logrus.Debugf("%s role has key IDs: %s", roleData.Name, strings.Join(roleData.ListKeyIDs(), ","))
// remarshal the signed part so we can verify the signature, since the signature has
// to be of a canonically marshalled signed object
var decoded map[string]interface{}
if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
return err
}
msg, err := json.MarshalCanonical(decoded)
if err != nil {
return err
}
valid := make(map[string]struct{})
for i := range s.Signatures {
sig := &(s.Signatures[i])
logrus.Debug("verifying signature for key ID: ", sig.KeyID)
key, ok := roleData.Keys[sig.KeyID]
if !ok {
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
continue
}
// Check that the signature key ID actually matches the content ID of the key
if key.ID() != sig.KeyID {
return ErrInvalidKeyID{}
}
if err := VerifySignature(msg, sig, key); err != nil {
logrus.Debugf("continuing b/c %s", err.Error())
continue
}
valid[sig.KeyID] = struct{}{}
}
if len(valid) < roleData.Threshold {
return ErrRoleThreshold{
Msg: fmt.Sprintf("valid signatures did not meet threshold for %s", roleData.Name),
}
}
return nil
}
// VerifySignature checks a single signature and public key against a payload
// If the signature is verified, the signature's is valid field will actually
// be mutated to be equal to the boolean true
func VerifySignature(msg []byte, sig *data.Signature, pk data.PublicKey) error {
// method lookup is consistent due to Unmarshal JSON doing lower case for us.
method := sig.Method
verifier, ok := Verifiers[method]
if !ok {
return fmt.Errorf("signing method is not supported: %s", sig.Method)
}
if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
return fmt.Errorf("signature was invalid")
}
sig.IsValid = true
return nil
}
// VerifyPublicKeyMatchesPrivateKey checks if the private key and the public keys forms valid key pairs.
// Supports both x509 certificate PublicKeys and non-certificate PublicKeys
func VerifyPublicKeyMatchesPrivateKey(privKey data.PrivateKey, pubKey data.PublicKey) error {
pubKeyID, err := utils.CanonicalKeyID(pubKey)
if err != nil {
return fmt.Errorf("could not verify key pair: %v", err)
}
if privKey == nil || pubKeyID != privKey.ID() {
return fmt.Errorf("private key is nil or does not match public key")
}
return nil
}

1072
vendor/github.com/theupdateframework/notary/tuf/tuf.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,341 @@
// Package utils contains tuf related utility functions however this file is hard
// forked from https://github.com/youmark/pkcs8 package. It has been further modified
// based on the requirements of Notary. For converting keys into PKCS#8 format,
// original package expected *crypto.PrivateKey interface, which then type inferred
// to either *rsa.PrivateKey or *ecdsa.PrivateKey depending on the need and later
// converted to ASN.1 DER encoded form, this whole process was superfluous here as
// keys are already being kept in ASN.1 DER format wrapped in data.PrivateKey
// structure. With these changes, package has became tightly coupled with notary as
// most of the method signatures have been updated. Moreover support for ED25519
// keys has been added as well. License for original package is following:
//
// The MIT License (MIT)
//
// # Copyright (c) 2014 youmark
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1" // #nosec
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
"github.com/theupdateframework/notary/tuf/data"
)
// Copy from crypto/x509
var (
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1}
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
// crypto/x509 doesn't have support for ED25519
// http://www.oid-info.com/get/1.3.6.1.4.1.11591.15.1
oidPublicKeyED25519 = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 15, 1}
)
// Copy from crypto/x509
var (
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)
// Copy from crypto/x509
func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) {
switch curve {
case elliptic.P224():
return oidNamedCurveP224, true
case elliptic.P256():
return oidNamedCurveP256, true
case elliptic.P384():
return oidNamedCurveP384, true
case elliptic.P521():
return oidNamedCurveP521, true
}
return nil, false
}
// Unecrypted PKCS8
var (
oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12}
oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
)
type ecPrivateKey struct {
Version int
PrivateKey []byte
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
type privateKeyInfo struct {
Version int
PrivateKeyAlgorithm []asn1.ObjectIdentifier
PrivateKey []byte
}
// Encrypted PKCS8
type pbkdf2Params struct {
Salt []byte
IterationCount int
}
type pbkdf2Algorithms struct {
IDPBKDF2 asn1.ObjectIdentifier
PBKDF2Params pbkdf2Params
}
type pbkdf2Encs struct {
EncryAlgo asn1.ObjectIdentifier
IV []byte
}
type pbes2Params struct {
KeyDerivationFunc pbkdf2Algorithms
EncryptionScheme pbkdf2Encs
}
type pbes2Algorithms struct {
IDPBES2 asn1.ObjectIdentifier
PBES2Params pbes2Params
}
type encryptedPrivateKeyInfo struct {
EncryptionAlgorithm pbes2Algorithms
EncryptedData []byte
}
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey.
// copied from https://github.com/golang/go/blob/964639cc338db650ccadeafb7424bc8ebb2c0f6c/src/crypto/x509/pkcs8.go#L17
type pkcs8 struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
}
func parsePKCS8ToTufKey(der []byte) (data.PrivateKey, error) {
var key pkcs8
if _, err := asn1.Unmarshal(der, &key); err != nil {
if _, ok := err.(asn1.StructuralError); ok {
return nil, errors.New("could not decrypt private key")
}
return nil, err
}
if key.Algo.Algorithm.Equal(oidPublicKeyED25519) {
tufED25519PrivateKey, err := ED25519ToPrivateKey(key.PrivateKey)
if err != nil {
return nil, fmt.Errorf("could not convert ed25519.PrivateKey to data.PrivateKey: %v", err)
}
return tufED25519PrivateKey, nil
}
privKey, err := x509.ParsePKCS8PrivateKey(der)
if err != nil {
return nil, err
}
switch priv := privKey.(type) {
case *rsa.PrivateKey:
tufRSAPrivateKey, err := RSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufRSAPrivateKey, nil
case *ecdsa.PrivateKey:
tufECDSAPrivateKey, err := ECDSAToPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufECDSAPrivateKey, nil
}
return nil, errors.New("unsupported key type")
}
// ParsePKCS8ToTufKey requires PKCS#8 key in DER format and returns data.PrivateKey
// Password should be provided in case of Encrypted PKCS#8 key, else it should be nil.
func ParsePKCS8ToTufKey(der []byte, password []byte) (data.PrivateKey, error) {
if password == nil {
return parsePKCS8ToTufKey(der)
}
var privKey encryptedPrivateKeyInfo
if _, err := asn1.Unmarshal(der, &privKey); err != nil {
return nil, errors.New("pkcs8: only PKCS #5 v2.0 supported")
}
if !privKey.EncryptionAlgorithm.IDPBES2.Equal(oidPBES2) {
return nil, errors.New("pkcs8: only PBES2 supported")
}
if !privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.IDPBKDF2.Equal(oidPKCS5PBKDF2) {
return nil, errors.New("pkcs8: only PBKDF2 supported")
}
encParam := privKey.EncryptionAlgorithm.PBES2Params.EncryptionScheme
kdfParam := privKey.EncryptionAlgorithm.PBES2Params.KeyDerivationFunc.PBKDF2Params
switch {
case encParam.EncryAlgo.Equal(oidAES256CBC):
iv := encParam.IV
salt := kdfParam.Salt
iter := kdfParam.IterationCount
encryptedKey := privKey.EncryptedData
symkey := pbkdf2.Key(password, salt, iter, 32, sha1.New)
block, err := aes.NewCipher(symkey)
if err != nil {
return nil, err
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(encryptedKey, encryptedKey)
// no need to explicitly remove padding, as ASN.1 unmarshalling will automatically discard it
key, err := parsePKCS8ToTufKey(encryptedKey)
if err != nil {
return nil, errors.New("pkcs8: incorrect password")
}
return key, nil
default:
return nil, errors.New("pkcs8: only AES-256-CBC supported")
}
}
func convertTUFKeyToPKCS8(priv data.PrivateKey) ([]byte, error) {
var pkey privateKeyInfo
switch priv.Algorithm() {
case data.RSAKey, data.RSAx509Key:
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyRSA
pkey.PrivateKey = priv.Private()
case data.ECDSAKey, data.ECDSAx509Key:
// To extract Curve value, parsing ECDSA key to *ecdsa.PrivateKey
eckey, err := x509.ParseECPrivateKey(priv.Private())
if err != nil {
return nil, err
}
oidNamedCurve, ok := oidFromNamedCurve(eckey.Curve)
if !ok {
return nil, errors.New("pkcs8: unknown elliptic curve")
}
// Per RFC5958, if publicKey is present, then version is set to v2(1) else version is set to v1(0).
// But openssl set to v1 even publicKey is present
pkey.Version = 1
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 2)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyECDSA
pkey.PrivateKeyAlgorithm[1] = oidNamedCurve
pkey.PrivateKey = priv.Private()
case data.ED25519Key:
pkey.Version = 0
pkey.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
pkey.PrivateKeyAlgorithm[0] = oidPublicKeyED25519
pkey.PrivateKey = priv.Private()
default:
return nil, fmt.Errorf("algorithm %s not supported", priv.Algorithm())
}
return asn1.Marshal(pkey)
}
func convertTUFKeyToPKCS8Encrypted(priv data.PrivateKey, password []byte) ([]byte, error) {
// Convert private key into PKCS8 format
pkey, err := convertTUFKeyToPKCS8(priv)
if err != nil {
return nil, err
}
// Calculate key from password based on PKCS5 algorithm
// Use 8 byte salt, 16 byte IV, and 2048 iteration
iter := 2048
salt := make([]byte, 8)
iv := make([]byte, 16)
_, err = rand.Reader.Read(salt)
if err != nil {
return nil, err
}
_, err = rand.Reader.Read(iv)
if err != nil {
return nil, err
}
key := pbkdf2.Key(password, salt, iter, 32, sha1.New)
// Use AES256-CBC mode, pad plaintext with PKCS5 padding scheme
padding := aes.BlockSize - len(pkey)%aes.BlockSize
if padding > 0 {
n := len(pkey)
pkey = append(pkey, make([]byte, padding)...)
for i := 0; i < padding; i++ {
pkey[n+i] = byte(padding)
}
}
encryptedKey := make([]byte, len(pkey))
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(encryptedKey, pkey)
pbkdf2algo := pbkdf2Algorithms{oidPKCS5PBKDF2, pbkdf2Params{salt, iter}}
pbkdf2encs := pbkdf2Encs{oidAES256CBC, iv}
pbes2algo := pbes2Algorithms{oidPBES2, pbes2Params{pbkdf2algo, pbkdf2encs}}
encryptedPkey := encryptedPrivateKeyInfo{pbes2algo, encryptedKey}
return asn1.Marshal(encryptedPkey)
}
// ConvertTUFKeyToPKCS8 converts a private key (data.Private) to PKCS#8 and returns in DER format
// if password is not nil, it would convert the Private Key to Encrypted PKCS#8.
func ConvertTUFKeyToPKCS8(priv data.PrivateKey, password []byte) ([]byte, error) {
if password == nil {
return convertTUFKeyToPKCS8(priv)
}
return convertTUFKeyToPKCS8Encrypted(priv, password)
}

View File

@ -0,0 +1,31 @@
package utils
import (
"strings"
)
// RoleList is a list of roles
type RoleList []string
// Len returns the length of the list
func (r RoleList) Len() int {
return len(r)
}
// Less returns true if the item at i should be sorted
// before the item at j. It's an unstable partial ordering
// based on the number of segments, separated by "/", in
// the role name
func (r RoleList) Less(i, j int) bool {
segsI := strings.Split(r[i], "/")
segsJ := strings.Split(r[j], "/")
if len(segsI) == len(segsJ) {
return r[i] < r[j]
}
return len(segsI) < len(segsJ)
}
// Swap the items at 2 locations in the list
func (r RoleList) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}

View File

@ -0,0 +1,85 @@
package utils
import (
"fmt"
"sync"
)
// ErrEmptyStack is used when an action that requires some
// content is invoked and the stack is empty
type ErrEmptyStack struct {
action string
}
func (err ErrEmptyStack) Error() string {
return fmt.Sprintf("attempted to %s with empty stack", err.action)
}
// ErrBadTypeCast is used by PopX functions when the item
// cannot be typed to X
type ErrBadTypeCast struct{}
func (err ErrBadTypeCast) Error() string {
return "attempted to do a typed pop and item was not of type"
}
// Stack is a simple type agnostic stack implementation
type Stack struct {
s []interface{}
l sync.Mutex
}
// NewStack create a new stack
func NewStack() *Stack {
s := &Stack{
s: make([]interface{}, 0),
}
return s
}
// Push adds an item to the top of the stack.
func (s *Stack) Push(item interface{}) {
s.l.Lock()
defer s.l.Unlock()
s.s = append(s.s, item)
}
// Pop removes and returns the top item on the stack, or returns
// ErrEmptyStack if the stack has no content
func (s *Stack) Pop() (interface{}, error) {
s.l.Lock()
defer s.l.Unlock()
l := len(s.s)
if l > 0 {
item := s.s[l-1]
s.s = s.s[:l-1]
return item, nil
}
return nil, ErrEmptyStack{action: "Pop"}
}
// PopString attempts to cast the top item on the stack to the string type.
// If this succeeds, it removes and returns the top item. If the item
// is not of the string type, ErrBadTypeCast is returned. If the stack
// is empty, ErrEmptyStack is returned
func (s *Stack) PopString() (string, error) {
s.l.Lock()
defer s.l.Unlock()
l := len(s.s)
if l > 0 {
item := s.s[l-1]
if item, ok := item.(string); ok {
s.s = s.s[:l-1]
return item, nil
}
return "", ErrBadTypeCast{}
}
return "", ErrEmptyStack{action: "PopString"}
}
// Empty returns true if the stack is empty
func (s *Stack) Empty() bool {
s.l.Lock()
defer s.l.Unlock()
return len(s.s) == 0
}

View File

@ -0,0 +1,119 @@
package utils
import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"io"
"github.com/theupdateframework/notary/tuf/data"
)
// StrSliceContains checks if the given string appears in the slice
func StrSliceContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// RoleNameSliceContains checks if the given string appears in the slice
func RoleNameSliceContains(ss []data.RoleName, s data.RoleName) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// RoleNameSliceRemove removes the given RoleName from the slice, returning a new slice
func RoleNameSliceRemove(ss []data.RoleName, s data.RoleName) []data.RoleName {
res := []data.RoleName{}
for _, v := range ss {
if v != s {
res = append(res, v)
}
}
return res
}
// NoopCloser is a simple Reader wrapper that does nothing when Close is
// called
type NoopCloser struct {
io.Reader
}
// Close does nothing for a NoopCloser
func (nc *NoopCloser) Close() error {
return nil
}
// DoHash returns the digest of d using the hashing algorithm named
// in alg
func DoHash(alg string, d []byte) []byte {
switch alg {
case "sha256":
digest := sha256.Sum256(d)
return digest[:]
case "sha512":
digest := sha512.Sum512(d)
return digest[:]
}
return nil
}
// UnusedDelegationKeys prunes a list of keys, returning those that are no
// longer in use for a given targets file
func UnusedDelegationKeys(t data.SignedTargets) []string {
// compare ids to all still active key ids in all active roles
// with the targets file
found := make(map[string]bool)
for _, r := range t.Signed.Delegations.Roles {
for _, id := range r.KeyIDs {
found[id] = true
}
}
var discard []string
for id := range t.Signed.Delegations.Keys {
if !found[id] {
discard = append(discard, id)
}
}
return discard
}
// RemoveUnusedKeys determines which keys in the slice of IDs are no longer
// used in the given targets file and removes them from the delegated keys
// map
func RemoveUnusedKeys(t *data.SignedTargets) {
unusedIDs := UnusedDelegationKeys(*t)
for _, id := range unusedIDs {
delete(t.Signed.Delegations.Keys, id)
}
}
// FindRoleIndex returns the index of the role named <name> or -1 if no
// matching role is found.
func FindRoleIndex(rs []*data.Role, name data.RoleName) int {
for i, r := range rs {
if r.Name == name {
return i
}
}
return -1
}
// ConsistentName generates the appropriate HTTP URL path for the role,
// based on whether the repo is marked as consistent. The RemoteStore
// is responsible for adding file extensions.
func ConsistentName(role string, hashSHA256 []byte) string {
if len(hashSHA256) > 0 {
hash := hex.EncodeToString(hashSHA256)
return fmt.Sprintf("%s.%s", role, hash)
}
return role
}

View File

@ -0,0 +1,564 @@
package utils
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"time"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/tuf/data"
"golang.org/x/crypto/ed25519"
)
// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
// On regular RSA/ECDSA TUF keys, this is just the key ID. On X509 RSA/ECDSA
// TUF keys, this is the key ID of the public key part of the key in the leaf cert
func CanonicalKeyID(k data.PublicKey) (string, error) {
if k == nil {
return "", errors.New("public key is nil")
}
switch k.Algorithm() {
case data.ECDSAx509Key, data.RSAx509Key:
return X509PublicKeyID(k)
default:
return k.ID(), nil
}
}
// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
for len(pemBytes) > 0 {
var block *pem.Block
block, pemBytes = pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no certificates found in PEM data")
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
return cert, nil
}
return nil, errors.New("no certificates found in PEM data")
}
// X509PublicKeyID returns a public key ID as a string, given a
// data.PublicKey that contains an X509 Certificate
func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
// Note that this only loads the first certificate from the public key
cert, err := LoadCertFromPEM(certPubKey.Public())
if err != nil {
return "", err
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
return "", err
}
var key data.PublicKey
switch certPubKey.Algorithm() {
case data.ECDSAx509Key:
key = data.NewECDSAPublicKey(pubKeyBytes)
case data.RSAx509Key:
key = data.NewRSAPublicKey(pubKeyBytes)
}
return key.ID(), nil
}
func parseLegacyPrivateKey(block *pem.Block, passphrase string) (data.PrivateKey, error) {
var privKeyBytes []byte
var err error
if x509.IsEncryptedPEMBlock(block) {
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
if err != nil {
return nil, errors.New("could not decrypt private key")
}
} else {
privKeyBytes = block.Bytes
}
switch block.Type {
case "RSA PRIVATE KEY":
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
}
tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
if err != nil {
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufRSAPrivateKey, nil
case "EC PRIVATE KEY":
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
}
tufECDSAPrivateKey, err := ECDSAToPrivateKey(ecdsaPrivKey)
if err != nil {
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufECDSAPrivateKey, nil
case "ED25519 PRIVATE KEY":
// We serialize ED25519 keys by concatenating the private key
// to the public key and encoding with PEM. See the
// ED25519ToPrivateKey function.
tufECDSAPrivateKey, err := ED25519ToPrivateKey(privKeyBytes)
if err != nil {
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
}
return tufECDSAPrivateKey, nil
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
// supports PKCS#8 as well as RSA/ECDSA (PKCS#1) only in non-FIPS mode and
// attempts to decrypt using the passphrase, if encrypted.
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) {
return parsePEMPrivateKey(pemBytes, passphrase, notary.FIPSEnabled())
}
func parsePEMPrivateKey(pemBytes []byte, passphrase string, fips bool) (data.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid private key found")
}
switch block.Type {
case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY":
if fips {
return nil, fmt.Errorf("%s not supported in FIPS mode", block.Type)
}
return parseLegacyPrivateKey(block, passphrase)
case "ENCRYPTED PRIVATE KEY", "PRIVATE KEY":
if passphrase == "" {
return ParsePKCS8ToTufKey(block.Bytes, nil)
}
return ParsePKCS8ToTufKey(block.Bytes, []byte(passphrase))
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
// CertToPEM is a utility function returns a PEM encoded x509 Certificate
func CertToPEM(cert *x509.Certificate) []byte {
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
return pemCert
}
// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
var pemBytes bytes.Buffer
for _, cert := range certChain {
if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
return nil, err
}
}
return pemBytes.Bytes(), nil
}
// LoadCertFromFile loads the first certificate from the file provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
certs, err := LoadCertBundleFromFile(filename)
if err != nil {
return nil, err
}
return certs[0], nil
}
// LoadCertBundleFromFile loads certificates from the []byte provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertBundleFromFile(filename string) ([]*x509.Certificate, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return LoadCertBundleFromPEM(b)
}
// LoadCertBundleFromPEM loads certificates from the []byte provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertBundleFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
certificates := []*x509.Certificate{}
var block *pem.Block
block, pemBytes = pem.Decode(pemBytes)
for ; block != nil; block, pemBytes = pem.Decode(pemBytes) {
if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, cert)
} else {
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
}
}
if len(certificates) == 0 {
return nil, fmt.Errorf("no valid certificates found")
}
return certificates, nil
}
// GetLeafCerts parses a list of x509 Certificates and returns all of them
// that aren't CA
func GetLeafCerts(certs []*x509.Certificate) []*x509.Certificate {
var leafCerts []*x509.Certificate
for _, cert := range certs {
if cert.IsCA {
continue
}
leafCerts = append(leafCerts, cert)
}
return leafCerts
}
// GetIntermediateCerts parses a list of x509 Certificates and returns all of the
// ones marked as a CA, to be used as intermediates
func GetIntermediateCerts(certs []*x509.Certificate) []*x509.Certificate {
var intCerts []*x509.Certificate
for _, cert := range certs {
if cert.IsCA {
intCerts = append(intCerts, cert)
}
}
return intCerts
}
// ParsePEMPublicKey returns a data.PublicKey from a PEM encoded public key or certificate.
func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
pemBlock, _ := pem.Decode(pubKeyBytes)
if pemBlock == nil {
return nil, errors.New("no valid public key found")
}
switch pemBlock.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("could not parse provided certificate: %v", err)
}
err = ValidateCertificate(cert, true)
if err != nil {
return nil, fmt.Errorf("invalid certificate: %v", err)
}
return CertToKey(cert), nil
case "PUBLIC KEY":
keyType, err := keyTypeForPublicKey(pemBlock.Bytes)
if err != nil {
return nil, err
}
return data.NewPublicKey(keyType, pemBlock.Bytes), nil
default:
return nil, fmt.Errorf("unsupported PEM block type %q, expected CERTIFICATE or PUBLIC KEY", pemBlock.Type)
}
}
func keyTypeForPublicKey(pubKeyBytes []byte) (string, error) {
pub, err := x509.ParsePKIXPublicKey(pubKeyBytes)
if err != nil {
return "", fmt.Errorf("unable to parse pem encoded public key: %v", err)
}
switch pub.(type) {
case *ecdsa.PublicKey:
return data.ECDSAKey, nil
case *rsa.PublicKey:
return data.RSAKey, nil
}
return "", fmt.Errorf("unknown public key format")
}
// ValidateCertificate returns an error if the certificate is not valid for notary
// Currently this is only ensuring the public key has a large enough modulus if RSA,
// using a non SHA1 signature algorithm, and an optional time expiry check
func ValidateCertificate(c *x509.Certificate, checkExpiry bool) error {
if (c.NotBefore).After(c.NotAfter) {
return fmt.Errorf("certificate validity window is invalid")
}
// Can't have SHA1 sig algorithm
if c.SignatureAlgorithm == x509.SHA1WithRSA || c.SignatureAlgorithm == x509.DSAWithSHA1 || c.SignatureAlgorithm == x509.ECDSAWithSHA1 {
return fmt.Errorf("certificate with CN %s uses invalid SHA1 signature algorithm", c.Subject.CommonName)
}
// If we have an RSA key, make sure it's long enough
if c.PublicKeyAlgorithm == x509.RSA {
rsaKey, ok := c.PublicKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("unable to parse RSA public key")
}
if rsaKey.N.BitLen() < notary.MinRSABitSize {
return fmt.Errorf("RSA bit length is too short")
}
}
if checkExpiry {
now := time.Now()
tomorrow := now.AddDate(0, 0, 1)
// Give one day leeway on creation "before" time, check "after" against today
if (tomorrow).Before(c.NotBefore) || now.After(c.NotAfter) {
return data.ErrCertExpired{CN: c.Subject.CommonName}
}
// If this certificate is expiring within 6 months, put out a warning
if (c.NotAfter).Before(time.Now().AddDate(0, 6, 0)) {
logrus.Warnf("certificate with CN %s is near expiry", c.Subject.CommonName)
}
}
return nil
}
// GenerateKey returns a new private key using the provided algorithm or an
// error detailing why the key could not be generated
func GenerateKey(algorithm string) (data.PrivateKey, error) {
switch algorithm {
case data.ECDSAKey:
return GenerateECDSAKey(rand.Reader)
case data.ED25519Key:
return GenerateED25519Key(rand.Reader)
}
return nil, fmt.Errorf("private key type not supported for key generation: %s", algorithm)
}
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (data.PrivateKey, error) {
// Get a DER-encoded representation of the PublicKey
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to marshal public key: %v", err)
}
// Get a DER-encoded representation of the PrivateKey
rsaPrivBytes := x509.MarshalPKCS1PrivateKey(rsaPrivKey)
pubKey := data.NewRSAPublicKey(rsaPubBytes)
return data.NewRSAPrivateKey(pubKey, rsaPrivBytes)
}
// GenerateECDSAKey generates an ECDSA Private key and returns a TUF PrivateKey
func GenerateECDSAKey(random io.Reader) (data.PrivateKey, error) {
ecdsaPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), random)
if err != nil {
return nil, err
}
tufPrivKey, err := ECDSAToPrivateKey(ecdsaPrivKey)
if err != nil {
return nil, err
}
logrus.Debugf("generated ECDSA key with keyID: %s", tufPrivKey.ID())
return tufPrivKey, nil
}
// GenerateED25519Key generates an ED25519 private key and returns a TUF
// PrivateKey. The serialization format we use is just the public key bytes
// followed by the private key bytes
func GenerateED25519Key(random io.Reader) (data.PrivateKey, error) {
pub, priv, err := ed25519.GenerateKey(random)
if err != nil {
return nil, err
}
var serialized [ed25519.PublicKeySize + ed25519.PrivateKeySize]byte
copy(serialized[:], pub[:])
copy(serialized[ed25519.PublicKeySize:], priv[:])
tufPrivKey, err := ED25519ToPrivateKey(serialized[:])
if err != nil {
return nil, err
}
logrus.Debugf("generated ED25519 key with keyID: %s", tufPrivKey.ID())
return tufPrivKey, nil
}
// ECDSAToPrivateKey converts an ecdsa.Private key to a TUF data.PrivateKey type
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey) (data.PrivateKey, error) {
// Get a DER-encoded representation of the PublicKey
ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPrivKey.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to marshal public key: %v", err)
}
// Get a DER-encoded representation of the PrivateKey
ecdsaPrivKeyBytes, err := x509.MarshalECPrivateKey(ecdsaPrivKey)
if err != nil {
return nil, fmt.Errorf("failed to marshal private key: %v", err)
}
pubKey := data.NewECDSAPublicKey(ecdsaPubBytes)
return data.NewECDSAPrivateKey(pubKey, ecdsaPrivKeyBytes)
}
// ED25519ToPrivateKey converts a serialized ED25519 key to a TUF
// data.PrivateKey type
func ED25519ToPrivateKey(privKeyBytes []byte) (data.PrivateKey, error) {
if len(privKeyBytes) != ed25519.PublicKeySize+ed25519.PrivateKeySize {
return nil, errors.New("malformed ed25519 private key")
}
pubKey := data.NewED25519PublicKey(privKeyBytes[:ed25519.PublicKeySize])
return data.NewED25519PrivateKey(*pubKey, privKeyBytes)
}
// ExtractPrivateKeyAttributes extracts role and gun values from private key bytes
func ExtractPrivateKeyAttributes(pemBytes []byte) (data.RoleName, data.GUN, error) {
return extractPrivateKeyAttributes(pemBytes, notary.FIPSEnabled())
}
func extractPrivateKeyAttributes(pemBytes []byte, fips bool) (data.RoleName, data.GUN, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return "", "", errors.New("PEM block is empty")
}
switch block.Type {
case "RSA PRIVATE KEY", "EC PRIVATE KEY", "ED25519 PRIVATE KEY":
if fips {
return "", "", fmt.Errorf("%s not supported in FIPS mode", block.Type)
}
case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY":
// do nothing for PKCS#8 keys
default:
return "", "", errors.New("unknown key format")
}
return data.RoleName(block.Headers["role"]), data.GUN(block.Headers["gun"]), nil
}
// ConvertPrivateKeyToPKCS8 converts a data.PrivateKey to PKCS#8 Format
func ConvertPrivateKeyToPKCS8(key data.PrivateKey, role data.RoleName, gun data.GUN, passphrase string) ([]byte, error) {
var (
err error
der []byte
blockType = "PRIVATE KEY"
)
if passphrase == "" {
der, err = ConvertTUFKeyToPKCS8(key, nil)
} else {
blockType = "ENCRYPTED PRIVATE KEY"
der, err = ConvertTUFKeyToPKCS8(key, []byte(passphrase))
}
if err != nil {
return nil, fmt.Errorf("unable to convert to PKCS8 key")
}
headers := make(map[string]string)
if role != "" {
headers["role"] = role.String()
}
if gun != "" {
headers["gun"] = gun.String()
}
return pem.EncodeToMemory(&pem.Block{Bytes: der, Type: blockType, Headers: headers}), nil
}
// CertToKey transforms a single input certificate into its corresponding
// PublicKey
func CertToKey(cert *x509.Certificate) data.PublicKey {
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
pemdata := pem.EncodeToMemory(&block)
switch cert.PublicKeyAlgorithm {
case x509.RSA:
return data.NewRSAx509PublicKey(pemdata)
case x509.ECDSA:
return data.NewECDSAx509PublicKey(pemdata)
default:
logrus.Debugf("Unknown key type parsed from certificate: %v", cert.PublicKeyAlgorithm)
return nil
}
}
// CertsToKeys transforms each of the input certificate chains into its corresponding
// PublicKey
func CertsToKeys(leafCerts map[string]*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
keys := make(map[string]data.PublicKey)
for id, leafCert := range leafCerts {
if key, err := CertBundleToKey(leafCert, intCerts[id]); err == nil {
keys[key.ID()] = key
}
}
return keys
}
// CertBundleToKey creates a TUF key from a leaf certs and a list of
// intermediates
func CertBundleToKey(leafCert *x509.Certificate, intCerts []*x509.Certificate) (data.PublicKey, error) {
certBundle := []*x509.Certificate{leafCert}
certBundle = append(certBundle, intCerts...)
certChainPEM, err := CertChainToPEM(certBundle)
if err != nil {
return nil, err
}
var newKey data.PublicKey
// Use the leaf cert's public key algorithm for typing
switch leafCert.PublicKeyAlgorithm {
case x509.RSA:
newKey = data.NewRSAx509PublicKey(certChainPEM)
case x509.ECDSA:
newKey = data.NewECDSAx509PublicKey(certChainPEM)
default:
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
return nil, x509.ErrUnsupportedAlgorithm
}
return newKey, nil
}
// NewCertificate returns an X509 Certificate following a template, given a Common Name and validity interval.
func NewCertificate(commonName string, startTime, endTime time.Time) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate new certificate: %v", err)
}
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: commonName,
},
NotBefore: startTime,
NotAfter: endTime,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
BasicConstraintsValid: true,
}, nil
}

View File

@ -0,0 +1,126 @@
package validation
import (
"encoding/json"
"fmt"
)
// VALIDATION ERRORS
// ErrValidation represents a general validation error
type ErrValidation struct {
Msg string
}
func (err ErrValidation) Error() string {
return fmt.Sprintf("An error occurred during validation: %s", err.Msg)
}
// ErrBadHierarchy represents missing metadata. Currently: a missing snapshot
// at this current time. When delegations are implemented it will also
// represent a missing delegation parent
type ErrBadHierarchy struct {
Missing string
Msg string
}
func (err ErrBadHierarchy) Error() string {
return fmt.Sprintf("Metadata hierarchy is incomplete: %s", err.Msg)
}
// ErrBadRoot represents a failure validating the root
type ErrBadRoot struct {
Msg string
}
func (err ErrBadRoot) Error() string {
return fmt.Sprintf("The root metadata is invalid: %s", err.Msg)
}
// ErrBadTargets represents a failure to validate a targets (incl delegations)
type ErrBadTargets struct {
Msg string
}
func (err ErrBadTargets) Error() string {
return fmt.Sprintf("The targets metadata is invalid: %s", err.Msg)
}
// ErrBadSnapshot represents a failure to validate the snapshot
type ErrBadSnapshot struct {
Msg string
}
func (err ErrBadSnapshot) Error() string {
return fmt.Sprintf("The snapshot metadata is invalid: %s", err.Msg)
}
// END VALIDATION ERRORS
// SerializableError is a struct that can be used to serialize an error as JSON
type SerializableError struct {
Name string
Error error
}
// UnmarshalJSON attempts to unmarshal the error into the right type
func (s *SerializableError) UnmarshalJSON(text []byte) (err error) {
var x struct{ Name string }
err = json.Unmarshal(text, &x)
if err != nil {
return
}
var theError error
switch x.Name {
case "ErrValidation":
var e struct{ Error ErrValidation }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadHierarchy":
var e struct{ Error ErrBadHierarchy }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadRoot":
var e struct{ Error ErrBadRoot }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadTargets":
var e struct{ Error ErrBadTargets }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadSnapshot":
var e struct{ Error ErrBadSnapshot }
err = json.Unmarshal(text, &e)
theError = e.Error
default:
err = fmt.Errorf("do not know how to unmarshal %s", x.Name)
return
}
if err != nil {
return
}
s.Name = x.Name
s.Error = theError
return nil
}
// NewSerializableError serializes one of the above errors into JSON
func NewSerializableError(err error) (*SerializableError, error) {
// make sure it's one of our errors
var name string
switch err.(type) {
case ErrValidation:
name = "ErrValidation"
case ErrBadHierarchy:
name = "ErrBadHierarchy"
case ErrBadRoot:
name = "ErrBadRoot"
case ErrBadTargets:
name = "ErrBadTargets"
case ErrBadSnapshot:
name = "ErrBadSnapshot"
default:
return nil, fmt.Errorf("does not support serializing non-validation errors")
}
return &SerializableError{Name: name, Error: err}, nil
}