mirror of https://github.com/docker/cli.git
vndr docker/docker to ea220e7 to bring in fix for arm
Primarily to bring in fix for "Clear Architecture field in platform constraint for arm architectures". Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
This commit is contained in:
parent
aae519d0f6
commit
ef027b6d72
|
@ -4,7 +4,7 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
||||||
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
||||||
github.com/docker/docker 184cea5ff710abde25547749e5608b24a255ba09
|
github.com/docker/docker ea220e70a13963da544645376cd9331021eec6b4
|
||||||
github.com/docker/docker-credential-helpers v0.5.1
|
github.com/docker/docker-credential-helpers v0.5.1
|
||||||
|
|
||||||
# the docker/go package contains a customized version of canonical/json
|
# the docker/go package contains a customized version of canonical/json
|
||||||
|
|
|
@ -1,17 +1,5 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
"github.com/docker/libtrust"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
// DefaultVersion of Current REST API
|
// DefaultVersion of Current REST API
|
||||||
|
@ -21,45 +9,3 @@ const (
|
||||||
// command to specify that no base image is to be used.
|
// command to specify that no base image is to be used.
|
||||||
NoBaseImageSpecifier string = "scratch"
|
NoBaseImageSpecifier string = "scratch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
|
||||||
// otherwise generates a new one
|
|
||||||
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
|
||||||
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
|
|
||||||
if err == libtrust.ErrKeyFileDoesNotExist {
|
|
||||||
trustKey, err = libtrust.GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error generating key: %s", err)
|
|
||||||
}
|
|
||||||
encodedKey, err := serializePrivateKey(trustKey, filepath.Ext(trustKeyPath))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error serializing key: %s", err)
|
|
||||||
}
|
|
||||||
if err := ioutils.AtomicWriteFile(trustKeyPath, encodedKey, os.FileMode(0600)); err != nil {
|
|
||||||
return nil, fmt.Errorf("Error saving key file: %s", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
|
|
||||||
}
|
|
||||||
return trustKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializePrivateKey(key libtrust.PrivateKey, ext string) (encoded []byte, err error) {
|
|
||||||
if ext == ".json" || ext == ".jwk" {
|
|
||||||
encoded, err = json.Marshal(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to encode private key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to encode private key PEM: %s", err)
|
|
||||||
}
|
|
||||||
encoded = pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
// RestrictedNameChars collects the characters allowed to represent a name, normally used to validate container and volume names.
|
|
||||||
const RestrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
|
|
||||||
|
|
||||||
// RestrictedNamePattern is a regular expression to validate names against the collection of restricted characters.
|
|
||||||
var RestrictedNamePattern = regexp.MustCompile(`^` + RestrictedNameChars + `+$`)
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/tlsconfig"
|
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -115,7 +114,7 @@ func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Con
|
||||||
// from the hostname we're connecting to.
|
// from the hostname we're connecting to.
|
||||||
if config.ServerName == "" {
|
if config.ServerName == "" {
|
||||||
// Make a copy to avoid polluting argument or default.
|
// Make a copy to avoid polluting argument or default.
|
||||||
config = tlsconfig.Clone(config)
|
config = tlsConfigClone(config)
|
||||||
config.ServerName = hostname
|
config.ServerName = hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@ package client
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -85,7 +86,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageDigestAndPlatforms(ctx context.Context, cli *Client, image, encodedAuth string) (string, []swarm.Platform, error) {
|
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
||||||
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
|
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
|
||||||
imageWithDigest := image
|
imageWithDigest := image
|
||||||
var platforms []swarm.Platform
|
var platforms []swarm.Platform
|
||||||
|
@ -98,6 +99,16 @@ func imageDigestAndPlatforms(ctx context.Context, cli *Client, image, encodedAut
|
||||||
if len(distributionInspect.Platforms) > 0 {
|
if len(distributionInspect.Platforms) > 0 {
|
||||||
platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
|
platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
|
||||||
for _, p := range distributionInspect.Platforms {
|
for _, p := range distributionInspect.Platforms {
|
||||||
|
// clear architecture field for arm. This is a temporary patch to address
|
||||||
|
// https://github.com/docker/swarmkit/issues/2294. The issue is that while
|
||||||
|
// image manifests report "arm" as the architecture, the node reports
|
||||||
|
// something like "armv7l" (includes the variant), which causes arm images
|
||||||
|
// to stop working with swarm mode. This patch removes the architecture
|
||||||
|
// constraint for arm images to ensure tasks get scheduled.
|
||||||
|
arch := strings.ToLower(p.Architecture)
|
||||||
|
if arch == "arm" {
|
||||||
|
arch = ""
|
||||||
|
}
|
||||||
platforms = append(platforms, swarm.Platform{
|
platforms = append(platforms, swarm.Platform{
|
||||||
Architecture: p.Architecture,
|
Architecture: p.Architecture,
|
||||||
OS: p.OS,
|
OS: p.OS,
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
// tlsConfigClone returns a clone of tls.Config. This function is provided for
|
||||||
|
// compatibility for go1.7 that doesn't include this method in stdlib.
|
||||||
|
func tlsConfigClone(c *tls.Config) *tls.Config {
|
||||||
|
return c.Clone()
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
// +build go1.7,!go1.8
|
// +build go1.7,!go1.8
|
||||||
|
|
||||||
package tlsconfig
|
package client
|
||||||
|
|
||||||
import "crypto/tls"
|
import "crypto/tls"
|
||||||
|
|
||||||
// Clone returns a clone of tls.Config. This function is provided for
|
// tlsConfigClone returns a clone of tls.Config. This function is provided for
|
||||||
// compatibility for go1.7 that doesn't include this method in stdlib.
|
// compatibility for go1.7 that doesn't include this method in stdlib.
|
||||||
func Clone(c *tls.Config) *tls.Config {
|
func tlsConfigClone(c *tls.Config) *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
Time: c.Time,
|
Time: c.Time,
|
|
@ -50,8 +50,8 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (
|
||||||
// Currently go does not fill in the major/minors
|
// Currently go does not fill in the major/minors
|
||||||
if s.Mode&unix.S_IFBLK != 0 ||
|
if s.Mode&unix.S_IFBLK != 0 ||
|
||||||
s.Mode&unix.S_IFCHR != 0 {
|
s.Mode&unix.S_IFCHR != 0 {
|
||||||
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
hdr.Devmajor = int64(major(uint64(s.Rdev))) // nolint: unconvert
|
||||||
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
hdr.Devminor = int64(minor(uint64(s.Rdev))) // nolint: unconvert
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) {
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
s, ok := stat.(*syscall.Stat_t)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
inode = uint64(s.Ino)
|
inode = s.Ino
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -294,7 +294,7 @@ func OverlayChanges(layers []string, rw string) ([]Change, error) {
|
||||||
func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
|
func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
|
||||||
if fi.Mode()&os.ModeCharDevice != 0 {
|
if fi.Mode()&os.ModeCharDevice != 0 {
|
||||||
s := fi.Sys().(*syscall.Stat_t)
|
s := fi.Sys().(*syscall.Stat_t)
|
||||||
if major(uint64(s.Rdev)) == 0 && minor(uint64(s.Rdev)) == 0 {
|
if major(s.Rdev) == 0 && minor(s.Rdev) == 0 {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func (info *FileInfo) isDir() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIno(fi os.FileInfo) uint64 {
|
func getIno(fi os.FileInfo) uint64 {
|
||||||
return uint64(fi.Sys().(*syscall.Stat_t).Ino)
|
return fi.Sys().(*syscall.Stat_t).Ino
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasHardlinks(fi os.FileInfo) bool {
|
func hasHardlinks(fi os.FileInfo) bool {
|
||||||
|
|
|
@ -5,10 +5,10 @@ import "syscall"
|
||||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||||
return &StatT{size: s.Size,
|
return &StatT{size: s.Size,
|
||||||
mode: uint32(s.Mode),
|
mode: s.Mode,
|
||||||
uid: s.Uid,
|
uid: s.Uid,
|
||||||
gid: s.Gid,
|
gid: s.Gid,
|
||||||
rdev: uint64(s.Rdev),
|
rdev: s.Rdev,
|
||||||
mtim: s.Mtim}, nil
|
mtim: s.Mtim}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ next:
|
||||||
return nil, fmt.Errorf("Unknown character: '%s'", key)
|
return nil, fmt.Errorf("Unknown character: '%s'", key)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
codes = append(codes, byte(key[0]))
|
codes = append(codes, key[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return codes, nil
|
return codes, nil
|
||||||
|
|
|
@ -3,28 +3,18 @@
|
||||||
package term
|
package term
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetWinsize returns the window size based on the specified file descriptor.
|
// GetWinsize returns the window size based on the specified file descriptor.
|
||||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
ws := &Winsize{}
|
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
|
||||||
// Skipp errno = 0
|
|
||||||
if err == 0 {
|
|
||||||
return ws, nil
|
|
||||||
}
|
|
||||||
return ws, err
|
return ws, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWinsize tries to set the specified window size for the specified file descriptor.
|
// SetWinsize tries to set the specified window size for the specified file descriptor.
|
||||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
|
||||||
// Skipp errno = 0
|
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws)
|
||||||
if err == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package tlsconfig
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Clone returns a clone of tls.Config. This function is provided for
|
|
||||||
// compatibility for go1.7 that doesn't include this method in stdlib.
|
|
||||||
func Clone(c *tls.Config) *tls.Config {
|
|
||||||
return c.Clone()
|
|
||||||
}
|
|
|
@ -247,6 +247,7 @@ func (err PingResponseError) Error() string {
|
||||||
// challenge manager for the supported authentication types and
|
// challenge manager for the supported authentication types and
|
||||||
// whether v2 was confirmed by the response. If a response is received but
|
// whether v2 was confirmed by the response. If a response is received but
|
||||||
// cannot be interpreted a PingResponseError will be returned.
|
// cannot be interpreted a PingResponseError will be returned.
|
||||||
|
// nolint: interfacer
|
||||||
func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, bool, error) {
|
func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, bool, error) {
|
||||||
var (
|
var (
|
||||||
foundV2 = false
|
foundV2 = false
|
||||||
|
|
|
@ -28,7 +28,7 @@ github.com/imdario/mergo 0.2.1
|
||||||
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
|
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
|
||||||
|
|
||||||
#get libnetwork packages
|
#get libnetwork packages
|
||||||
github.com/docker/libnetwork 5b28c0ec98236c489e39ae6a9e1aeb802e071681
|
github.com/docker/libnetwork d5c822319097cc01cc9bd5ffedd74c7ce7c894f2
|
||||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||||
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
||||||
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
Copyright 2014 Docker, Inc.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,18 +0,0 @@
|
||||||
# libtrust
|
|
||||||
|
|
||||||
Libtrust is library for managing authentication and authorization using public key cryptography.
|
|
||||||
|
|
||||||
Authentication is handled using the identity attached to the public key.
|
|
||||||
Libtrust provides multiple methods to prove possession of the private key associated with an identity.
|
|
||||||
- TLS x509 certificates
|
|
||||||
- Signature verification
|
|
||||||
- Key Challenge
|
|
||||||
|
|
||||||
Authorization and access control is managed through a distributed trust graph.
|
|
||||||
Trust servers are used as the authorities of the trust graph and allow caching portions of the graph for faster access.
|
|
||||||
|
|
||||||
## Copyright and license
|
|
||||||
|
|
||||||
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
|
|
||||||
Docs released under Creative commons.
|
|
||||||
|
|
|
@ -1,175 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type certTemplateInfo struct {
|
|
||||||
commonName string
|
|
||||||
domains []string
|
|
||||||
ipAddresses []net.IP
|
|
||||||
isCA bool
|
|
||||||
clientAuth bool
|
|
||||||
serverAuth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCertTemplate(info *certTemplateInfo) *x509.Certificate {
|
|
||||||
// Generate a certificate template which is valid from the past week to
|
|
||||||
// 10 years from now. The usage of the certificate depends on the
|
|
||||||
// specified fields in the given certTempInfo object.
|
|
||||||
var (
|
|
||||||
keyUsage x509.KeyUsage
|
|
||||||
extKeyUsage []x509.ExtKeyUsage
|
|
||||||
)
|
|
||||||
|
|
||||||
if info.isCA {
|
|
||||||
keyUsage = x509.KeyUsageCertSign
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.clientAuth {
|
|
||||||
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.serverAuth {
|
|
||||||
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(0),
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: info.commonName,
|
|
||||||
},
|
|
||||||
NotBefore: time.Now().Add(-time.Hour * 24 * 7),
|
|
||||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
|
|
||||||
DNSNames: info.domains,
|
|
||||||
IPAddresses: info.ipAddresses,
|
|
||||||
IsCA: info.isCA,
|
|
||||||
KeyUsage: keyUsage,
|
|
||||||
ExtKeyUsage: extKeyUsage,
|
|
||||||
BasicConstraintsValid: info.isCA,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCert(pub PublicKey, priv PrivateKey, subInfo, issInfo *certTemplateInfo) (cert *x509.Certificate, err error) {
|
|
||||||
pubCertTemplate := generateCertTemplate(subInfo)
|
|
||||||
privCertTemplate := generateCertTemplate(issInfo)
|
|
||||||
|
|
||||||
certDER, err := x509.CreateCertificate(
|
|
||||||
rand.Reader, pubCertTemplate, privCertTemplate,
|
|
||||||
pub.CryptoPublicKey(), priv.CryptoPrivateKey(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err = x509.ParseCertificate(certDER)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSelfSignedServerCert creates a self-signed certificate for the
|
|
||||||
// given key which is to be used for TLS servers with the given domains and
|
|
||||||
// IP addresses.
|
|
||||||
func GenerateSelfSignedServerCert(key PrivateKey, domains []string, ipAddresses []net.IP) (*x509.Certificate, error) {
|
|
||||||
info := &certTemplateInfo{
|
|
||||||
commonName: key.KeyID(),
|
|
||||||
domains: domains,
|
|
||||||
ipAddresses: ipAddresses,
|
|
||||||
serverAuth: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(key.PublicKey(), key, info, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSelfSignedClientCert creates a self-signed certificate for the
|
|
||||||
// given key which is to be used for TLS clients.
|
|
||||||
func GenerateSelfSignedClientCert(key PrivateKey) (*x509.Certificate, error) {
|
|
||||||
info := &certTemplateInfo{
|
|
||||||
commonName: key.KeyID(),
|
|
||||||
clientAuth: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(key.PublicKey(), key, info, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateCACert creates a certificate which can be used as a trusted
|
|
||||||
// certificate authority.
|
|
||||||
func GenerateCACert(signer PrivateKey, trustedKey PublicKey) (*x509.Certificate, error) {
|
|
||||||
subjectInfo := &certTemplateInfo{
|
|
||||||
commonName: trustedKey.KeyID(),
|
|
||||||
isCA: true,
|
|
||||||
}
|
|
||||||
issuerInfo := &certTemplateInfo{
|
|
||||||
commonName: signer.KeyID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(trustedKey, signer, subjectInfo, issuerInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateCACertPool creates a certificate authority pool to be used for a
|
|
||||||
// TLS configuration. Any self-signed certificates issued by the specified
|
|
||||||
// trusted keys will be verified during a TLS handshake
|
|
||||||
func GenerateCACertPool(signer PrivateKey, trustedKeys []PublicKey) (*x509.CertPool, error) {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
|
|
||||||
for _, trustedKey := range trustedKeys {
|
|
||||||
cert, err := GenerateCACert(signer, trustedKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate CA certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
return certPool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertificateBundle loads certificates from the given file. The file should be pem encoded
|
|
||||||
// containing one or more certificates. The expected pem type is "CERTIFICATE".
|
|
||||||
func LoadCertificateBundle(filename string) ([]*x509.Certificate, error) {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certificates := []*x509.Certificate{}
|
|
||||||
var block *pem.Block
|
|
||||||
block, b = pem.Decode(b)
|
|
||||||
for ; block != nil; block, b = pem.Decode(b) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certificates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertificatePool loads a CA pool from the given file. The file should be pem encoded
|
|
||||||
// containing one or more certificates. The expected pem type is "CERTIFICATE".
|
|
||||||
func LoadCertificatePool(filename string) (*x509.CertPool, error) {
|
|
||||||
certs, err := LoadCertificateBundle(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
for _, cert := range certs {
|
|
||||||
pool.AddCert(cert)
|
|
||||||
}
|
|
||||||
return pool, nil
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
Package libtrust provides an interface for managing authentication and
|
|
||||||
authorization using public key cryptography. Authentication is handled
|
|
||||||
using the identity attached to the public key and verified through TLS
|
|
||||||
x509 certificates, a key challenge, or signature. Authorization and
|
|
||||||
access control is managed through a trust graph distributed between
|
|
||||||
both remote trust servers and locally cached and managed data.
|
|
||||||
*/
|
|
||||||
package libtrust
|
|
|
@ -1,428 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* EC DSA PUBLIC KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital
|
|
||||||
// signature algorithms.
|
|
||||||
type ecPublicKey struct {
|
|
||||||
*ecdsa.PublicKey
|
|
||||||
curveName string
|
|
||||||
signatureAlgorithm *signatureAlgorithm
|
|
||||||
extended map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) {
|
|
||||||
curve := cryptoPublicKey.Curve
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case curve == elliptic.P256():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil
|
|
||||||
case curve == elliptic.P384():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil
|
|
||||||
case curve == elliptic.P521():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported elliptic curve")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyType returns the key type for elliptic curve keys, i.e., "EC".
|
|
||||||
func (k *ecPublicKey) KeyType() string {
|
|
||||||
return "EC"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurveName returns the elliptic curve identifier.
|
|
||||||
// Possible values are "P-256", "P-384", and "P-521".
|
|
||||||
func (k *ecPublicKey) CurveName() string {
|
|
||||||
return k.curveName
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
func (k *ecPublicKey) KeyID() string {
|
|
||||||
return keyIDFromCryptoKey(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) String() string {
|
|
||||||
return fmt.Sprintf("EC Public Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this
|
|
||||||
// PublicKey. The alg parameter should identify the digital signature
|
|
||||||
// algorithm which was used to produce the signature and should be supported
|
|
||||||
// by this public key. Returns a nil error if the signature is valid.
|
|
||||||
func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
|
|
||||||
// For EC keys there is only one supported signature algorithm depending
|
|
||||||
// on the curve parameters.
|
|
||||||
if k.signatureAlgorithm.HeaderParam() != alg {
|
|
||||||
return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// signature is the concatenation of (r, s), base64Url encoded.
|
|
||||||
sigLength := len(signature)
|
|
||||||
expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3)
|
|
||||||
if sigLength != expectedOctetLength {
|
|
||||||
return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:]
|
|
||||||
r := new(big.Int).SetBytes(rBytes)
|
|
||||||
s := new(big.Int).SetBytes(sBytes)
|
|
||||||
|
|
||||||
hasher := k.signatureAlgorithm.HashID().New()
|
|
||||||
_, err := io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
if !ecdsa.Verify(k.PublicKey, hash, r, s) {
|
|
||||||
return errors.New("invalid signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return k.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) toMap() map[string]interface{} {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
for k, v := range k.extended {
|
|
||||||
jwk[k] = v
|
|
||||||
}
|
|
||||||
jwk["kty"] = k.KeyType()
|
|
||||||
jwk["kid"] = k.KeyID()
|
|
||||||
jwk["crv"] = k.CurveName()
|
|
||||||
|
|
||||||
xBytes := k.X.Bytes()
|
|
||||||
yBytes := k.Y.Bytes()
|
|
||||||
octetLength := (k.Params().BitSize + 7) >> 3
|
|
||||||
// MUST include leading zeros in the output so that x, y are each
|
|
||||||
// *octetLength* bytes long.
|
|
||||||
xBuf := make([]byte, octetLength-len(xBytes), octetLength)
|
|
||||||
yBuf := make([]byte, octetLength-len(yBytes), octetLength)
|
|
||||||
xBuf = append(xBuf, xBytes...)
|
|
||||||
yBuf = append(yBuf, yBytes...)
|
|
||||||
|
|
||||||
jwk["x"] = joseBase64UrlEncode(xBuf)
|
|
||||||
jwk["y"] = joseBase64UrlEncode(yBuf)
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
|
|
||||||
// elliptic curve keys.
|
|
||||||
func (k *ecPublicKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
|
|
||||||
func (k *ecPublicKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["kid"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) AddExtendedField(field string, value interface{}) {
|
|
||||||
k.extended[field] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) GetExtendedField(field string) interface{} {
|
|
||||||
v, ok := k.extended[field]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) {
|
|
||||||
// JWK key type (kty) has already been determined to be "EC".
|
|
||||||
// Need to extract 'crv', 'x', 'y', and 'kid' and check for
|
|
||||||
// consistency.
|
|
||||||
|
|
||||||
// Get the curve identifier value.
|
|
||||||
crv, err := stringFromMap(jwk, "crv")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
curve elliptic.Curve
|
|
||||||
sigAlg *signatureAlgorithm
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case crv == "P-256":
|
|
||||||
curve = elliptic.P256()
|
|
||||||
sigAlg = es256
|
|
||||||
case crv == "P-384":
|
|
||||||
curve = elliptic.P384()
|
|
||||||
sigAlg = es384
|
|
||||||
case crv == "P-521":
|
|
||||||
curve = elliptic.P521()
|
|
||||||
sigAlg = es512
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the X and Y coordinates for the public key point.
|
|
||||||
xB64Url, err := stringFromMap(jwk, "x")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
x, err := parseECCoordinate(xB64Url, curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
yB64Url, err := stringFromMap(jwk, "y")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
y, err := parseECCoordinate(yB64Url, curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &ecPublicKey{
|
|
||||||
PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y},
|
|
||||||
curveName: crv, signatureAlgorithm: sigAlg,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key ID is optional too, but if it exists, it should match the key.
|
|
||||||
_, ok := jwk["kid"]
|
|
||||||
if ok {
|
|
||||||
kid, err := stringFromMap(jwk, "kid")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key ID: %s", err)
|
|
||||||
}
|
|
||||||
if kid != key.KeyID() {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key.extended = jwk
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* EC DSA PRIVATE KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature
|
|
||||||
// algorithms.
|
|
||||||
type ecPrivateKey struct {
|
|
||||||
ecPublicKey
|
|
||||||
*ecdsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) {
|
|
||||||
publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey returns the Public Key data associated with this Private Key.
|
|
||||||
func (k *ecPrivateKey) PublicKey() PublicKey {
|
|
||||||
return &k.ecPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPrivateKey) String() string {
|
|
||||||
return fmt.Sprintf("EC Private Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm supported
|
|
||||||
// by the elliptic curve private key. If the specified hashing algorithm is
|
|
||||||
// supported by this key, that hash function is used to generate the signature
|
|
||||||
// otherwise the the default hashing algorithm for this key is used. Returns
|
|
||||||
// the signature and the name of the JWK signature algorithm used, e.g.,
|
|
||||||
// "ES256", "ES384", "ES512".
|
|
||||||
func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
|
|
||||||
// Generate a signature of the data using the internal alg.
|
|
||||||
// The given hashId is only a suggestion, and since EC keys only support
|
|
||||||
// on signature/hash algorithm given the curve name, we disregard it for
|
|
||||||
// the elliptic curve JWK signature implementation.
|
|
||||||
hasher := k.signatureAlgorithm.HashID().New()
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
r, s, err := ecdsa.Sign(rand.Reader, k.PrivateKey, hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error producing signature: %s", err)
|
|
||||||
}
|
|
||||||
rBytes, sBytes := r.Bytes(), s.Bytes()
|
|
||||||
octetLength := (k.ecPublicKey.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...)
|
|
||||||
|
|
||||||
signature = append(rBuf, sBuf...)
|
|
||||||
alg = k.signatureAlgorithm.HeaderParam()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
|
|
||||||
return k.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPrivateKey) toMap() map[string]interface{} {
|
|
||||||
jwk := k.ecPublicKey.toMap()
|
|
||||||
|
|
||||||
dBytes := k.D.Bytes()
|
|
||||||
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
|
||||||
// octets (where n is the order of the curve). This is because the private
|
|
||||||
// key d must be in the interval [1, n-1] so the bitlength of d should be
|
|
||||||
// no larger than the bitlength of n-1. The easiest way to find the octet
|
|
||||||
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
|
|
||||||
// bit sequence right by 3, which is essentially dividing by 8 and adding
|
|
||||||
// 1 if there is any remainder. Thus, the private key value d should be
|
|
||||||
// output to (bitlength(n-1)+7)>>3 octets.
|
|
||||||
n := k.ecPublicKey.Params().N
|
|
||||||
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
|
|
||||||
// Create a buffer with the necessary zero-padding.
|
|
||||||
dBuf := make([]byte, octetLength-len(dBytes), octetLength)
|
|
||||||
dBuf = append(dBuf, dBytes...)
|
|
||||||
|
|
||||||
jwk["d"] = joseBase64UrlEncode(dBuf)
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
|
|
||||||
// elliptic curve keys.
|
|
||||||
func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
|
|
||||||
func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["keyID"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("EC PRIVATE KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) {
|
|
||||||
dB64Url, err := stringFromMap(jwk, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Private Key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWK key type (kty) has already been determined to be "EC".
|
|
||||||
// Need to extract the public key information, then extract the private
|
|
||||||
// key value 'd'.
|
|
||||||
publicKey, err := ecPublicKeyFromMap(jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := parseECPrivateParam(dB64Url, publicKey.Curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &ecPrivateKey{
|
|
||||||
ecPublicKey: *publicKey,
|
|
||||||
PrivateKey: &ecdsa.PrivateKey{
|
|
||||||
PublicKey: *publicKey.PublicKey,
|
|
||||||
D: d,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Key Generation Functions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) {
|
|
||||||
k = new(ecPrivateKey)
|
|
||||||
k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey
|
|
||||||
k.extended = make(map[string]interface{})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256.
|
|
||||||
func GenerateECP256PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P256())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-256 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-256"
|
|
||||||
k.signatureAlgorithm = es256
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384.
|
|
||||||
func GenerateECP384PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P384())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-384 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-384"
|
|
||||||
k.signatureAlgorithm = es384
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521.
|
|
||||||
func GenerateECP521PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P521())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-521 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-521"
|
|
||||||
k.signatureAlgorithm = es512
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterByHosts filters the list of PublicKeys to only those which contain a
|
|
||||||
// 'hosts' pattern which matches the given host. If *includeEmpty* is true,
|
|
||||||
// then keys which do not specify any hosts are also returned.
|
|
||||||
func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) {
|
|
||||||
filtered := make([]PublicKey, 0, len(keys))
|
|
||||||
|
|
||||||
for _, pubKey := range keys {
|
|
||||||
var hosts []string
|
|
||||||
switch v := pubKey.GetExtendedField("hosts").(type) {
|
|
||||||
case []string:
|
|
||||||
hosts = v
|
|
||||||
case []interface{}:
|
|
||||||
for _, value := range v {
|
|
||||||
h, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hosts = append(hosts, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hosts) == 0 {
|
|
||||||
if includeEmpty {
|
|
||||||
filtered = append(filtered, pubKey)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any hosts match pattern
|
|
||||||
for _, hostPattern := range hosts {
|
|
||||||
match, err := filepath.Match(hostPattern, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if match {
|
|
||||||
filtered = append(filtered, pubKey)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered, nil
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
_ "crypto/sha256" // Registrer SHA224 and SHA256
|
|
||||||
_ "crypto/sha512" // Registrer SHA384 and SHA512
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type signatureAlgorithm struct {
|
|
||||||
algHeaderParam string
|
|
||||||
hashID crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *signatureAlgorithm) HeaderParam() string {
|
|
||||||
return h.algHeaderParam
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *signatureAlgorithm) HashID() crypto.Hash {
|
|
||||||
return h.hashID
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rs256 = &signatureAlgorithm{"RS256", crypto.SHA256}
|
|
||||||
rs384 = &signatureAlgorithm{"RS384", crypto.SHA384}
|
|
||||||
rs512 = &signatureAlgorithm{"RS512", crypto.SHA512}
|
|
||||||
es256 = &signatureAlgorithm{"ES256", crypto.SHA256}
|
|
||||||
es384 = &signatureAlgorithm{"ES384", crypto.SHA384}
|
|
||||||
es512 = &signatureAlgorithm{"ES512", crypto.SHA512}
|
|
||||||
)
|
|
||||||
|
|
||||||
func rsaSignatureAlgorithmByName(alg string) (*signatureAlgorithm, error) {
|
|
||||||
switch {
|
|
||||||
case alg == "RS256":
|
|
||||||
return rs256, nil
|
|
||||||
case alg == "RS384":
|
|
||||||
return rs384, nil
|
|
||||||
case alg == "RS512":
|
|
||||||
return rs512, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("RSA Digital Signature Algorithm %q not supported", alg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPKCS1v15SignatureAlgorithmForHashID(hashID crypto.Hash) *signatureAlgorithm {
|
|
||||||
switch {
|
|
||||||
case hashID == crypto.SHA512:
|
|
||||||
return rs512
|
|
||||||
case hashID == crypto.SHA384:
|
|
||||||
return rs384
|
|
||||||
case hashID == crypto.SHA256:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return rs256
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,657 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidSignContent is used when the content to be signed is invalid.
|
|
||||||
ErrInvalidSignContent = errors.New("invalid sign content")
|
|
||||||
|
|
||||||
// ErrInvalidJSONContent is used when invalid json is encountered.
|
|
||||||
ErrInvalidJSONContent = errors.New("invalid json content")
|
|
||||||
|
|
||||||
// ErrMissingSignatureKey is used when the specified signature key
|
|
||||||
// does not exist in the JSON content.
|
|
||||||
ErrMissingSignatureKey = errors.New("missing signature key")
|
|
||||||
)
|
|
||||||
|
|
||||||
type jsHeader struct {
|
|
||||||
JWK PublicKey `json:"jwk,omitempty"`
|
|
||||||
Algorithm string `json:"alg"`
|
|
||||||
Chain []string `json:"x5c,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsSignature struct {
|
|
||||||
Header jsHeader `json:"header"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Protected string `json:"protected,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsSignaturesSorted []jsSignature
|
|
||||||
|
|
||||||
func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
|
|
||||||
func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) }
|
|
||||||
|
|
||||||
func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
|
|
||||||
ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
|
|
||||||
si, sj := jsbkid[i].Signature, jsbkid[j].Signature
|
|
||||||
|
|
||||||
if ki == kj {
|
|
||||||
return si < sj
|
|
||||||
}
|
|
||||||
|
|
||||||
return ki < kj
|
|
||||||
}
|
|
||||||
|
|
||||||
type signKey struct {
|
|
||||||
PrivateKey
|
|
||||||
Chain []*x509.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONSignature represents a signature of a json object.
|
|
||||||
type JSONSignature struct {
|
|
||||||
payload string
|
|
||||||
signatures []jsSignature
|
|
||||||
indent string
|
|
||||||
formatLength int
|
|
||||||
formatTail []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJSONSignature() *JSONSignature {
|
|
||||||
return &JSONSignature{
|
|
||||||
signatures: make([]jsSignature, 0, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payload returns the encoded payload of the signature. This
|
|
||||||
// payload should not be signed directly
|
|
||||||
func (js *JSONSignature) Payload() ([]byte, error) {
|
|
||||||
return joseBase64UrlDecode(js.payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (js *JSONSignature) protectedHeader() (string, error) {
|
|
||||||
protected := map[string]interface{}{
|
|
||||||
"formatLength": js.formatLength,
|
|
||||||
"formatTail": joseBase64UrlEncode(js.formatTail),
|
|
||||||
"time": time.Now().UTC().Format(time.RFC3339),
|
|
||||||
}
|
|
||||||
protectedBytes, err := json.Marshal(protected)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return joseBase64UrlEncode(protectedBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
|
|
||||||
buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
|
|
||||||
copy(buf, protectedHeader)
|
|
||||||
buf[len(protectedHeader)] = '.'
|
|
||||||
copy(buf[len(protectedHeader)+1:], js.payload)
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign adds a signature using the given private key.
|
|
||||||
func (js *JSONSignature) Sign(key PrivateKey) error {
|
|
||||||
protected, err := js.protectedHeader()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signBytes, err := js.signBytes(protected)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsSignature{
|
|
||||||
Header: jsHeader{
|
|
||||||
JWK: key.PublicKey(),
|
|
||||||
Algorithm: algorithm,
|
|
||||||
},
|
|
||||||
Signature: joseBase64UrlEncode(sigBytes),
|
|
||||||
Protected: protected,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignWithChain adds a signature using the given private key
|
|
||||||
// and setting the x509 chain. The public key of the first element
|
|
||||||
// in the chain must be the public key corresponding with the sign key.
|
|
||||||
func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
|
|
||||||
// Ensure key.Chain[0] is public key for key
|
|
||||||
//key.Chain.PublicKey
|
|
||||||
//key.PublicKey().CryptoPublicKey()
|
|
||||||
|
|
||||||
// Verify chain
|
|
||||||
protected, err := js.protectedHeader()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signBytes, err := js.signBytes(protected)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header := jsHeader{
|
|
||||||
Chain: make([]string, len(chain)),
|
|
||||||
Algorithm: algorithm,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cert := range chain {
|
|
||||||
header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: joseBase64UrlEncode(sigBytes),
|
|
||||||
Protected: protected,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifies all the signatures and returns the list of
|
|
||||||
// public keys used to sign. Any x509 chains are not checked.
|
|
||||||
func (js *JSONSignature) Verify() ([]PublicKey, error) {
|
|
||||||
keys := make([]PublicKey, len(js.signatures))
|
|
||||||
for i, signature := range js.signatures {
|
|
||||||
signBytes, err := js.signBytes(signature.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var publicKey PublicKey
|
|
||||||
if len(signature.Header.Chain) > 0 {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if signature.Header.JWK != nil {
|
|
||||||
publicKey = signature.Header.JWK
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("missing public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
sigBytes, err := joseBase64UrlDecode(signature.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keys[i] = publicKey
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyChains verifies all the signatures and the chains associated
|
|
||||||
// with each signature and returns the list of verified chains.
|
|
||||||
// Signatures without an x509 chain are not checked.
|
|
||||||
func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
|
|
||||||
chains := make([][]*x509.Certificate, 0, len(js.signatures))
|
|
||||||
for _, signature := range js.signatures {
|
|
||||||
signBytes, err := js.signBytes(signature.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var publicKey PublicKey
|
|
||||||
if len(signature.Header.Chain) > 0 {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediates := x509.NewCertPool()
|
|
||||||
if len(signature.Header.Chain) > 1 {
|
|
||||||
intermediateChain := signature.Header.Chain[1:]
|
|
||||||
for i := range intermediateChain {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediate, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediates.AddCert(intermediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyOptions := x509.VerifyOptions{
|
|
||||||
Intermediates: intermediates,
|
|
||||||
Roots: ca,
|
|
||||||
}
|
|
||||||
|
|
||||||
verifiedChains, err := cert.Verify(verifyOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
chains = append(chains, verifiedChains...)
|
|
||||||
|
|
||||||
sigBytes, err := joseBase64UrlDecode(signature.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return chains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWS returns JSON serialized JWS according to
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
|
|
||||||
func (js *JSONSignature) JWS() ([]byte, error) {
|
|
||||||
if len(js.signatures) == 0 {
|
|
||||||
return nil, errors.New("missing signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
jsonMap := map[string]interface{}{
|
|
||||||
"payload": js.payload,
|
|
||||||
"signatures": js.signatures,
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.MarshalIndent(jsonMap, "", " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func notSpace(r rune) bool {
|
|
||||||
return !unicode.IsSpace(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectJSONIndent(jsonContent []byte) (indent string) {
|
|
||||||
if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
|
|
||||||
quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
|
|
||||||
if quoteIndex > 0 {
|
|
||||||
indent = string(jsonContent[2 : quoteIndex+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsParsedHeader struct {
|
|
||||||
JWK json.RawMessage `json:"jwk"`
|
|
||||||
Algorithm string `json:"alg"`
|
|
||||||
Chain []string `json:"x5c"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsParsedSignature struct {
|
|
||||||
Header jsParsedHeader `json:"header"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Protected string `json:"protected"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseJWS parses a JWS serialized JSON object into a Json Signature.
|
|
||||||
func ParseJWS(content []byte) (*JSONSignature, error) {
|
|
||||||
type jsParsed struct {
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Signatures []jsParsedSignature `json:"signatures"`
|
|
||||||
}
|
|
||||||
parsed := &jsParsed{}
|
|
||||||
err := json.Unmarshal(content, parsed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(parsed.Signatures) == 0 {
|
|
||||||
return nil, errors.New("missing signatures")
|
|
||||||
}
|
|
||||||
payload, err := joseBase64UrlDecode(parsed.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
js, err := NewJSONSignature(payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
js.signatures = make([]jsSignature, len(parsed.Signatures))
|
|
||||||
for i, signature := range parsed.Signatures {
|
|
||||||
header := jsHeader{
|
|
||||||
Algorithm: signature.Header.Algorithm,
|
|
||||||
}
|
|
||||||
if signature.Header.Chain != nil {
|
|
||||||
header.Chain = signature.Header.Chain
|
|
||||||
}
|
|
||||||
if signature.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
header.JWK = publicKey
|
|
||||||
}
|
|
||||||
js.signatures[i] = jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: signature.Signature,
|
|
||||||
Protected: signature.Protected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSONSignature returns a new unsigned JWS from a json byte array.
|
|
||||||
// JSONSignature will need to be signed before serializing or storing.
|
|
||||||
// Optionally, one or more signatures can be provided as byte buffers,
|
|
||||||
// containing serialized JWS signatures, to assemble a fully signed JWS
|
|
||||||
// package. It is the callers responsibility to ensure uniqueness of the
|
|
||||||
// provided signatures.
|
|
||||||
func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
|
|
||||||
var dataMap map[string]interface{}
|
|
||||||
err := json.Unmarshal(content, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.indent = detectJSONIndent(content)
|
|
||||||
|
|
||||||
js.payload = joseBase64UrlEncode(content)
|
|
||||||
|
|
||||||
// Find trailing } and whitespace, put in protected header
|
|
||||||
closeIndex := bytes.LastIndexFunc(content, notSpace)
|
|
||||||
if content[closeIndex] != '}' {
|
|
||||||
return nil, ErrInvalidJSONContent
|
|
||||||
}
|
|
||||||
lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
|
|
||||||
if content[lastRuneIndex] == ',' {
|
|
||||||
return nil, ErrInvalidJSONContent
|
|
||||||
}
|
|
||||||
js.formatLength = lastRuneIndex + 1
|
|
||||||
js.formatTail = content[js.formatLength:]
|
|
||||||
|
|
||||||
if len(signatures) > 0 {
|
|
||||||
for _, signature := range signatures {
|
|
||||||
var parsedJSig jsParsedSignature
|
|
||||||
|
|
||||||
if err := json.Unmarshal(signature, &parsedJSig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(stevvooe): A lot of the code below is repeated in
|
|
||||||
// ParseJWS. It will require more refactoring to fix that.
|
|
||||||
jsig := jsSignature{
|
|
||||||
Header: jsHeader{
|
|
||||||
Algorithm: parsedJSig.Header.Algorithm,
|
|
||||||
},
|
|
||||||
Signature: parsedJSig.Signature,
|
|
||||||
Protected: parsedJSig.Protected,
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedJSig.Header.Chain != nil {
|
|
||||||
jsig.Header.Chain = parsedJSig.Header.Chain
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedJSig.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsig.Header.JWK = publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
|
|
||||||
// struct. JWS will need to be signed before serializing or storing.
|
|
||||||
func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
|
|
||||||
switch content.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
case struct{}:
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid data type")
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.indent = " "
|
|
||||||
|
|
||||||
payload, err := json.MarshalIndent(content, "", js.indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
js.payload = joseBase64UrlEncode(payload)
|
|
||||||
|
|
||||||
// Remove '\n}' from formatted section, put in protected header
|
|
||||||
js.formatLength = len(payload) - 2
|
|
||||||
js.formatTail = payload[js.formatLength:]
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
|
|
||||||
value, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
switch v := value.(type) {
|
|
||||||
case int:
|
|
||||||
return v, true
|
|
||||||
case float64:
|
|
||||||
return int(v), true
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
|
|
||||||
value, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
v, ok = value.(string)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePrettySignature parses a formatted signature into a
|
|
||||||
// JSON signature. If the signatures are missing the format information
|
|
||||||
// an error is thrown. The formatted signature must be created by
|
|
||||||
// the same method as format signature.
|
|
||||||
func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
|
|
||||||
var contentMap map[string]json.RawMessage
|
|
||||||
err := json.Unmarshal(content, &contentMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling content: %s", err)
|
|
||||||
}
|
|
||||||
sigMessage, ok := contentMap[signatureKey]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrMissingSignatureKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var signatureBlocks []jsParsedSignature
|
|
||||||
err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.signatures = make([]jsSignature, len(signatureBlocks))
|
|
||||||
|
|
||||||
for i, signatureBlock := range signatureBlocks {
|
|
||||||
protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("base64 decode error: %s", err)
|
|
||||||
}
|
|
||||||
var protectedHeader map[string]interface{}
|
|
||||||
err = json.Unmarshal(protectedBytes, &protectedHeader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatLength, ok := readIntFromMap("formatLength", protectedHeader)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("missing formatted length")
|
|
||||||
}
|
|
||||||
encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("missing formatted tail")
|
|
||||||
}
|
|
||||||
formatTail, err := joseBase64UrlDecode(encodedTail)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("base64 decode error on tail: %s", err)
|
|
||||||
}
|
|
||||||
if js.formatLength == 0 {
|
|
||||||
js.formatLength = formatLength
|
|
||||||
} else if js.formatLength != formatLength {
|
|
||||||
return nil, errors.New("conflicting format length")
|
|
||||||
}
|
|
||||||
if len(js.formatTail) == 0 {
|
|
||||||
js.formatTail = formatTail
|
|
||||||
} else if bytes.Compare(js.formatTail, formatTail) != 0 {
|
|
||||||
return nil, errors.New("conflicting format tail")
|
|
||||||
}
|
|
||||||
|
|
||||||
header := jsHeader{
|
|
||||||
Algorithm: signatureBlock.Header.Algorithm,
|
|
||||||
Chain: signatureBlock.Header.Chain,
|
|
||||||
}
|
|
||||||
if signatureBlock.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling public key: %s", err)
|
|
||||||
}
|
|
||||||
header.JWK = publicKey
|
|
||||||
}
|
|
||||||
js.signatures[i] = jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: signatureBlock.Signature,
|
|
||||||
Protected: signatureBlock.Protected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if js.formatLength > len(content) {
|
|
||||||
return nil, errors.New("invalid format length")
|
|
||||||
}
|
|
||||||
formatted := make([]byte, js.formatLength+len(js.formatTail))
|
|
||||||
copy(formatted, content[:js.formatLength])
|
|
||||||
copy(formatted[js.formatLength:], js.formatTail)
|
|
||||||
js.indent = detectJSONIndent(formatted)
|
|
||||||
js.payload = joseBase64UrlEncode(formatted)
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettySignature formats a json signature into an easy to read
|
|
||||||
// single json serialized object.
|
|
||||||
func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
|
|
||||||
if len(js.signatures) == 0 {
|
|
||||||
return nil, errors.New("no signatures")
|
|
||||||
}
|
|
||||||
payload, err := joseBase64UrlDecode(js.payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
payload = payload[:js.formatLength]
|
|
||||||
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
var marshalled []byte
|
|
||||||
var marshallErr error
|
|
||||||
if js.indent != "" {
|
|
||||||
marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
|
|
||||||
} else {
|
|
||||||
marshalled, marshallErr = json.Marshal(js.signatures)
|
|
||||||
}
|
|
||||||
if marshallErr != nil {
|
|
||||||
return nil, marshallErr
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
|
|
||||||
buf.Write(payload)
|
|
||||||
buf.WriteByte(',')
|
|
||||||
if js.indent != "" {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
buf.WriteString(js.indent)
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString(signatureKey)
|
|
||||||
buf.WriteString("\": ")
|
|
||||||
buf.Write(marshalled)
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
} else {
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString(signatureKey)
|
|
||||||
buf.WriteString("\":")
|
|
||||||
buf.Write(marshalled)
|
|
||||||
}
|
|
||||||
buf.WriteByte('}')
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signatures provides the signatures on this JWS as opaque blobs, sorted by
|
|
||||||
// keyID. These blobs can be stored and reassembled with payloads. Internally,
|
|
||||||
// they are simply marshaled json web signatures but implementations should
|
|
||||||
// not rely on this.
|
|
||||||
func (js *JSONSignature) Signatures() ([][]byte, error) {
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
var sb [][]byte
|
|
||||||
for _, jsig := range js.signatures {
|
|
||||||
p, err := json.Marshal(jsig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sb = append(sb, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge combines the signatures from one or more other signatures into the
|
|
||||||
// method receiver. If the payloads differ for any argument, an error will be
|
|
||||||
// returned and the receiver will not be modified.
|
|
||||||
func (js *JSONSignature) Merge(others ...*JSONSignature) error {
|
|
||||||
merged := js.signatures
|
|
||||||
for _, other := range others {
|
|
||||||
if js.payload != other.payload {
|
|
||||||
return fmt.Errorf("payloads differ from merge target")
|
|
||||||
}
|
|
||||||
merged = append(merged, other.signatures...)
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = merged
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,253 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey is a generic interface for a Public Key.
|
|
||||||
type PublicKey interface {
|
|
||||||
// KeyType returns the key type for this key. For elliptic curve keys,
|
|
||||||
// this value should be "EC". For RSA keys, this value should be "RSA".
|
|
||||||
KeyType() string
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
// The format generated by this library is a base32 encoding of a 240 bit
|
|
||||||
// hash of the public key data divided into 12 groups like so:
|
|
||||||
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
|
|
||||||
KeyID() string
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this
|
|
||||||
// Public Key. The alg parameter should identify the digital signature
|
|
||||||
// algorithm which was used to produce the signature and should be
|
|
||||||
// supported by this public key. Returns a nil error if the signature
|
|
||||||
// is valid.
|
|
||||||
Verify(data io.Reader, alg string, signature []byte) error
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
CryptoPublicKey() crypto.PublicKey
|
|
||||||
// These public keys can be serialized to the standard JSON encoding for
|
|
||||||
// JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web
|
|
||||||
// Algorithms.
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
// These keys can also be serialized to the standard PEM encoding.
|
|
||||||
PEMBlock() (*pem.Block, error)
|
|
||||||
// The string representation of a key is its key type and ID.
|
|
||||||
String() string
|
|
||||||
AddExtendedField(string, interface{})
|
|
||||||
GetExtendedField(string) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateKey is a generic interface for a Private Key.
|
|
||||||
type PrivateKey interface {
|
|
||||||
// A PrivateKey contains all fields and methods of a PublicKey of the
|
|
||||||
// same type. The MarshalJSON method also outputs the private key as a
|
|
||||||
// JSON Web Key, and the PEMBlock method outputs the private key as a
|
|
||||||
// PEM block.
|
|
||||||
PublicKey
|
|
||||||
// PublicKey returns the PublicKey associated with this PrivateKey.
|
|
||||||
PublicKey() PublicKey
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm
|
|
||||||
// supported by the private key. If the specified hashing algorithm is
|
|
||||||
// supported by this key, that hash function is used to generate the
|
|
||||||
// signature otherwise the the default hashing algorithm for this key is
|
|
||||||
// used. Returns the signature and identifier of the algorithm used.
|
|
||||||
Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error)
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The
|
|
||||||
// type is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
CryptoPrivateKey() crypto.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromCryptoPublicKey returns a libtrust PublicKey representation of the given
|
|
||||||
// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given
|
|
||||||
// key is of an unsupported type.
|
|
||||||
func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) {
|
|
||||||
switch cryptoPublicKey := cryptoPublicKey.(type) {
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
return fromECPublicKey(cryptoPublicKey)
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
return fromRSAPublicKey(cryptoPublicKey), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given
|
|
||||||
// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given
|
|
||||||
// key is of an unsupported type.
|
|
||||||
func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) {
|
|
||||||
switch cryptoPrivateKey := cryptoPrivateKey.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return fromECPrivateKey(cryptoPrivateKey)
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return fromRSAPrivateKey(cryptoPrivateKey), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust
|
|
||||||
// PublicKey or an error if there is a problem with the encoding.
|
|
||||||
func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) {
|
|
||||||
pemBlock, _ := pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, errors.New("unable to find PEM encoded data")
|
|
||||||
} else if pemBlock.Type != "PUBLIC KEY" {
|
|
||||||
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeyFromPEMBlock(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of
|
|
||||||
// PEM blocks appended one after the other and returns a slice of PublicKey
|
|
||||||
// objects that it finds.
|
|
||||||
func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) {
|
|
||||||
pubKeys := []PublicKey{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
var pemBlock *pem.Block
|
|
||||||
pemBlock, data = pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
break
|
|
||||||
} else if pemBlock.Type != "PUBLIC KEY" {
|
|
||||||
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := pubKeyFromPEMBlock(pemBlock)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeys = append(pubKeys, pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust
|
|
||||||
// PrivateKey or an error if there is a problem with the encoding.
|
|
||||||
func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) {
|
|
||||||
pemBlock, _ := pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, errors.New("unable to find PEM encoded data")
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PrivateKey
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case pemBlock.Type == "RSA PRIVATE KEY":
|
|
||||||
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
key = fromRSAPrivateKey(rsaPrivateKey)
|
|
||||||
case pemBlock.Type == "EC PRIVATE KEY":
|
|
||||||
ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
key, err = fromECPrivateKey(ecPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
addPEMHeadersToKey(pemBlock, key.PublicKey())
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic
|
|
||||||
// Public Key to be used with libtrust.
|
|
||||||
func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"decoding JWK Public Key JSON data: %s\n", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Key Type value.
|
|
||||||
kty, err := stringFromMap(jwk, "kty")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK Public Key type: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case kty == "EC":
|
|
||||||
// Call out to unmarshal EC public key.
|
|
||||||
return ecPublicKeyFromMap(jwk)
|
|
||||||
case kty == "RSA":
|
|
||||||
// Call out to unmarshal RSA public key.
|
|
||||||
return rsaPublicKeyFromMap(jwk)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"JWK Public Key type not supported: %q\n", kty,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set
|
|
||||||
// and returns a slice of Public Key objects.
|
|
||||||
func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) {
|
|
||||||
rawKeys, err := loadJSONKeySetRaw(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeys := make([]PublicKey, 0, len(rawKeys))
|
|
||||||
|
|
||||||
for _, rawKey := range rawKeys {
|
|
||||||
pubKey, err := UnmarshalPublicKeyJWK(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pubKeys = append(pubKeys, pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic
|
|
||||||
// Private Key to be used with libtrust.
|
|
||||||
func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"decoding JWK Private Key JSON data: %s\n", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Key Type value.
|
|
||||||
kty, err := stringFromMap(jwk, "kty")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK Private Key type: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case kty == "EC":
|
|
||||||
// Call out to unmarshal EC private key.
|
|
||||||
return ecPrivateKeyFromMap(jwk)
|
|
||||||
case kty == "RSA":
|
|
||||||
// Call out to unmarshal RSA private key.
|
|
||||||
return rsaPrivateKeyFromMap(jwk)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"JWK Private Key type not supported: %q\n", kty,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,255 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrKeyFileDoesNotExist indicates that the private key file does not exist.
|
|
||||||
ErrKeyFileDoesNotExist = errors.New("key file does not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readKeyFileBytes(filename string) ([]byte, error) {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = ErrKeyFileDoesNotExist
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unable to read key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Loading and Saving of Public and Private Keys in either PEM or JWK format.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// LoadKeyFile opens the given filename and attempts to read a Private Key
|
|
||||||
// encoded in either PEM or JWK format (if .json or .jwk file extension).
|
|
||||||
func LoadKeyFile(filename string) (PrivateKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PrivateKey
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
key, err = UnmarshalPrivateKeyJWK(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode private key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key, err = UnmarshalPrivateKeyPEM(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode private key PEM: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPublicKeyFile opens the given filename and attempts to read a Public Key
|
|
||||||
// encoded in either PEM or JWK format (if .json or .jwk file extension).
|
|
||||||
func LoadPublicKeyFile(filename string) (PublicKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PublicKey
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
key, err = UnmarshalPublicKeyJWK(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode public key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key, err = UnmarshalPublicKeyPEM(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode public key PEM: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveKey saves the given key to a file using the provided filename.
|
|
||||||
// This process will overwrite any existing file at the provided location.
|
|
||||||
func SaveKey(filename string, key PrivateKey) error {
|
|
||||||
var encodedKey []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
// Encode in JSON Web Key format.
|
|
||||||
encodedKey, err = json.MarshalIndent(key, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode private key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Encode in PEM format.
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode private key PEM: %s", err)
|
|
||||||
}
|
|
||||||
encodedKey = pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write private key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SavePublicKey saves the given public key to the file.
|
|
||||||
func SavePublicKey(filename string, key PublicKey) error {
|
|
||||||
var encodedKey []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
// Encode in JSON Web Key format.
|
|
||||||
encodedKey, err = json.MarshalIndent(key, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode public key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Encode in PEM format.
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode public key PEM: %s", err)
|
|
||||||
}
|
|
||||||
encodedKey = pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write public key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public Key Set files
|
|
||||||
|
|
||||||
type jwkSet struct {
|
|
||||||
Keys []json.RawMessage `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadKeySetFile loads a key set
|
|
||||||
func LoadKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
return loadJSONKeySetFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be a PEM format file
|
|
||||||
return loadPEMKeySetFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) {
|
|
||||||
if len(data) == 0 {
|
|
||||||
// This is okay, just return an empty slice.
|
|
||||||
return []json.RawMessage{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keySet := jwkSet{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &keySet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keySet.Keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadJSONKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return UnmarshalPublicKeyJWKSet(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadPEMKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
data, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return UnmarshalPublicKeyPEMBundle(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddKeySetFile adds a key to a key set
|
|
||||||
func AddKeySetFile(filename string, key PublicKey) error {
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
return addKeySetJSONFile(filename, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be a PEM format file
|
|
||||||
return addKeySetPEMFile(filename, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeySetJSONFile(filename string, key PublicKey) error {
|
|
||||||
encodedKey, err := json.Marshal(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode trusted client key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawEntries, err := loadJSONKeySetRaw(contents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawEntries = append(rawEntries, json.RawMessage(encodedKey))
|
|
||||||
entriesWrapper := jwkSet{Keys: rawEntries}
|
|
||||||
|
|
||||||
encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode trusted client keys: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeySetPEMFile(filename string, key PublicKey) error {
|
|
||||||
// Encode to PEM, open file for appending, write PEM.
|
|
||||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encoded trusted key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write(pem.EncodeToMemory(pemBlock))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write trusted keys file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,175 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientKeyManager manages client keys on the filesystem
|
|
||||||
type ClientKeyManager struct {
|
|
||||||
key PrivateKey
|
|
||||||
clientFile string
|
|
||||||
clientDir string
|
|
||||||
|
|
||||||
clientLock sync.RWMutex
|
|
||||||
clients []PublicKey
|
|
||||||
|
|
||||||
configLock sync.Mutex
|
|
||||||
configs []*tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientKeyManager loads a new manager from a set of key files
|
|
||||||
// and managed by the given private key.
|
|
||||||
func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) {
|
|
||||||
m := &ClientKeyManager{
|
|
||||||
key: trustKey,
|
|
||||||
clientFile: clientFile,
|
|
||||||
clientDir: clientDir,
|
|
||||||
}
|
|
||||||
if err := m.loadKeys(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// TODO Start watching file and directory
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientKeyManager) loadKeys() (err error) {
|
|
||||||
// Load authorized keys file
|
|
||||||
var clients []PublicKey
|
|
||||||
if c.clientFile != "" {
|
|
||||||
clients, err = LoadKeySetFile(c.clientFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load authorized keys: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add clients from authorized keys directory
|
|
||||||
files, err := ioutil.ReadDir(c.clientDir)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("unable to open authorized keys directory: %s", err)
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
if !f.IsDir() {
|
|
||||||
publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load authorized key file: %s", err)
|
|
||||||
}
|
|
||||||
clients = append(clients, publicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.clientLock.Lock()
|
|
||||||
c.clients = clients
|
|
||||||
c.clientLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTLSConfig registers a tls configuration to manager
|
|
||||||
// such that any changes to the keys may be reflected in
|
|
||||||
// the tls client CA pool
|
|
||||||
func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error {
|
|
||||||
c.clientLock.RLock()
|
|
||||||
certPool, err := GenerateCACertPool(c.key, c.clients)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("CA pool generation error: %s", err)
|
|
||||||
}
|
|
||||||
c.clientLock.RUnlock()
|
|
||||||
|
|
||||||
tlsConfig.ClientCAs = certPool
|
|
||||||
|
|
||||||
c.configLock.Lock()
|
|
||||||
c.configs = append(c.configs, tlsConfig)
|
|
||||||
c.configLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for
|
|
||||||
// libtrust identity authentication for the domain specified
|
|
||||||
func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
if err := clients.RegisterTLSConfig(tlsConfig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate cert
|
|
||||||
ips, domains, err := parseAddr(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// add domain that it expects clients to use
|
|
||||||
domains = append(domains, domain)
|
|
||||||
x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("certificate generation error: %s", err)
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{{
|
|
||||||
Certificate: [][]byte{x509Cert.Raw},
|
|
||||||
PrivateKey: trustKey.CryptoPrivateKey(),
|
|
||||||
Leaf: x509Cert,
|
|
||||||
}}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCertAuthTLSConfig creates a tls.Config for the server to use for
|
|
||||||
// certificate authentication
|
|
||||||
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err)
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
||||||
|
|
||||||
// Verify client certificates against a CA?
|
|
||||||
if caPath != "" {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
file, err := ioutil.ReadFile(caPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
|
|
||||||
}
|
|
||||||
certPool.AppendCertsFromPEM(file)
|
|
||||||
|
|
||||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
tlsConfig.ClientCAs = certPool
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLSConfig() *tls.Config {
|
|
||||||
return &tls.Config{
|
|
||||||
NextProtos: []string{"http/1.1"},
|
|
||||||
// Avoid fallback on insecure SSL protocols
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseAddr parses an address into an array of IPs and domains
|
|
||||||
func parseAddr(addr string) ([]net.IP, []string, error) {
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
var domains []string
|
|
||||||
var ips []net.IP
|
|
||||||
ip := net.ParseIP(host)
|
|
||||||
if ip != nil {
|
|
||||||
ips = []net.IP{ip}
|
|
||||||
} else {
|
|
||||||
domains = []string{host}
|
|
||||||
}
|
|
||||||
return ips, domains, nil
|
|
||||||
}
|
|
|
@ -1,427 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSA DSA PUBLIC KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rsaPublicKey implements a JWK Public Key using RSA digital signature algorithms.
|
|
||||||
type rsaPublicKey struct {
|
|
||||||
*rsa.PublicKey
|
|
||||||
extended map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromRSAPublicKey(cryptoPublicKey *rsa.PublicKey) *rsaPublicKey {
|
|
||||||
return &rsaPublicKey{cryptoPublicKey, map[string]interface{}{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyType returns the JWK key type for RSA keys, i.e., "RSA".
|
|
||||||
func (k *rsaPublicKey) KeyType() string {
|
|
||||||
return "RSA"
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
func (k *rsaPublicKey) KeyID() string {
|
|
||||||
return keyIDFromCryptoKey(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) String() string {
|
|
||||||
return fmt.Sprintf("RSA Public Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this Public Key.
|
|
||||||
// The alg parameter should be the name of the JWA digital signature algorithm
|
|
||||||
// which was used to produce the signature and should be supported by this
|
|
||||||
// public key. Returns a nil error if the signature is valid.
|
|
||||||
func (k *rsaPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
|
|
||||||
// Verify the signature of the given date, return non-nil error if valid.
|
|
||||||
sigAlg, err := rsaSignatureAlgorithmByName(alg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to verify Signature: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasher := sigAlg.HashID().New()
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
err = rsa.VerifyPKCS1v15(k.PublicKey, sigAlg.HashID(), hash, signature)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s signature: %s", sigAlg.HeaderParam(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return k.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) toMap() map[string]interface{} {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
for k, v := range k.extended {
|
|
||||||
jwk[k] = v
|
|
||||||
}
|
|
||||||
jwk["kty"] = k.KeyType()
|
|
||||||
jwk["kid"] = k.KeyID()
|
|
||||||
jwk["n"] = joseBase64UrlEncode(k.N.Bytes())
|
|
||||||
jwk["e"] = joseBase64UrlEncode(serializeRSAPublicExponentParam(k.E))
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
|
|
||||||
// RSA keys.
|
|
||||||
func (k *rsaPublicKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
|
|
||||||
func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["kid"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) AddExtendedField(field string, value interface{}) {
|
|
||||||
k.extended[field] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) GetExtendedField(field string) interface{} {
|
|
||||||
v, ok := k.extended[field]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPublicKeyFromMap(jwk map[string]interface{}) (*rsaPublicKey, error) {
|
|
||||||
// JWK key type (kty) has already been determined to be "RSA".
|
|
||||||
// Need to extract 'n', 'e', and 'kid' and check for
|
|
||||||
// consistency.
|
|
||||||
|
|
||||||
// Get the modulus parameter N.
|
|
||||||
nB64Url, err := stringFromMap(jwk, "n")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := parseRSAModulusParam(nB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the public exponent E.
|
|
||||||
eB64Url, err := stringFromMap(jwk, "e")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := parseRSAPublicExponentParam(eB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &rsaPublicKey{
|
|
||||||
PublicKey: &rsa.PublicKey{N: n, E: e},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key ID is optional, but if it exists, it should match the key.
|
|
||||||
_, ok := jwk["kid"]
|
|
||||||
if ok {
|
|
||||||
kid, err := stringFromMap(jwk, "kid")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key ID: %s", err)
|
|
||||||
}
|
|
||||||
if kid != key.KeyID() {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key ID does not match: %s", kid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := jwk["d"]; ok {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key cannot contain private exponent")
|
|
||||||
}
|
|
||||||
|
|
||||||
key.extended = jwk
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSA DSA PRIVATE KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rsaPrivateKey implements a JWK Private Key using RSA digital signature algorithms.
|
|
||||||
type rsaPrivateKey struct {
|
|
||||||
rsaPublicKey
|
|
||||||
*rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromRSAPrivateKey(cryptoPrivateKey *rsa.PrivateKey) *rsaPrivateKey {
|
|
||||||
return &rsaPrivateKey{
|
|
||||||
*fromRSAPublicKey(&cryptoPrivateKey.PublicKey),
|
|
||||||
cryptoPrivateKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey returns the Public Key data associated with this Private Key.
|
|
||||||
func (k *rsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return &k.rsaPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPrivateKey) String() string {
|
|
||||||
return fmt.Sprintf("RSA Private Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm supported
|
|
||||||
// by the RSA private key. If the specified hashing algorithm is supported by
|
|
||||||
// this key, that hash function is used to generate the signature otherwise the
|
|
||||||
// the default hashing algorithm for this key is used. Returns the signature
|
|
||||||
// and the name of the JWK signature algorithm used, e.g., "RS256", "RS384",
|
|
||||||
// "RS512".
|
|
||||||
func (k *rsaPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
|
|
||||||
// Generate a signature of the data using the internal alg.
|
|
||||||
sigAlg := rsaPKCS1v15SignatureAlgorithmForHashID(hashID)
|
|
||||||
hasher := sigAlg.HashID().New()
|
|
||||||
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
signature, err = rsa.SignPKCS1v15(rand.Reader, k.PrivateKey, sigAlg.HashID(), hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error producing signature: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
alg = sigAlg.HeaderParam()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *rsaPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
|
|
||||||
return k.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPrivateKey) toMap() map[string]interface{} {
|
|
||||||
k.Precompute() // Make sure the precomputed values are stored.
|
|
||||||
jwk := k.rsaPublicKey.toMap()
|
|
||||||
|
|
||||||
jwk["d"] = joseBase64UrlEncode(k.D.Bytes())
|
|
||||||
jwk["p"] = joseBase64UrlEncode(k.Primes[0].Bytes())
|
|
||||||
jwk["q"] = joseBase64UrlEncode(k.Primes[1].Bytes())
|
|
||||||
jwk["dp"] = joseBase64UrlEncode(k.Precomputed.Dp.Bytes())
|
|
||||||
jwk["dq"] = joseBase64UrlEncode(k.Precomputed.Dq.Bytes())
|
|
||||||
jwk["qi"] = joseBase64UrlEncode(k.Precomputed.Qinv.Bytes())
|
|
||||||
|
|
||||||
otherPrimes := k.Primes[2:]
|
|
||||||
|
|
||||||
if len(otherPrimes) > 0 {
|
|
||||||
otherPrimesInfo := make([]interface{}, len(otherPrimes))
|
|
||||||
for i, r := range otherPrimes {
|
|
||||||
otherPrimeInfo := make(map[string]string, 3)
|
|
||||||
otherPrimeInfo["r"] = joseBase64UrlEncode(r.Bytes())
|
|
||||||
crtVal := k.Precomputed.CRTValues[i]
|
|
||||||
otherPrimeInfo["d"] = joseBase64UrlEncode(crtVal.Exp.Bytes())
|
|
||||||
otherPrimeInfo["t"] = joseBase64UrlEncode(crtVal.Coeff.Bytes())
|
|
||||||
otherPrimesInfo[i] = otherPrimeInfo
|
|
||||||
}
|
|
||||||
jwk["oth"] = otherPrimesInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
|
|
||||||
// RSA keys.
|
|
||||||
func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
|
|
||||||
func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey)
|
|
||||||
k.extended["keyID"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPrivateKeyFromMap(jwk map[string]interface{}) (*rsaPrivateKey, error) {
|
|
||||||
// The JWA spec for RSA Private Keys (draft rfc section 5.3.2) states that
|
|
||||||
// only the private key exponent 'd' is REQUIRED, the others are just for
|
|
||||||
// signature/decryption optimizations and SHOULD be included when the JWK
|
|
||||||
// is produced. We MAY choose to accept a JWK which only includes 'd', but
|
|
||||||
// we're going to go ahead and not choose to accept it without the extra
|
|
||||||
// fields. Only the 'oth' field will be optional (for multi-prime keys).
|
|
||||||
privateExponent, err := parseRSAPrivateKeyParamFromMap(jwk, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
firstPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "p")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
secondPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "q")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
firstFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dp")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
secondFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dq")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
crtCoeff, err := parseRSAPrivateKeyParamFromMap(jwk, "qi")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var oth interface{}
|
|
||||||
if _, ok := jwk["oth"]; ok {
|
|
||||||
oth = jwk["oth"]
|
|
||||||
delete(jwk, "oth")
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWK key type (kty) has already been determined to be "RSA".
|
|
||||||
// Need to extract the public key information, then extract the private
|
|
||||||
// key values.
|
|
||||||
publicKey, err := rsaPublicKeyFromMap(jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey := &rsa.PrivateKey{
|
|
||||||
PublicKey: *publicKey.PublicKey,
|
|
||||||
D: privateExponent,
|
|
||||||
Primes: []*big.Int{firstPrimeFactor, secondPrimeFactor},
|
|
||||||
Precomputed: rsa.PrecomputedValues{
|
|
||||||
Dp: firstFactorCRT,
|
|
||||||
Dq: secondFactorCRT,
|
|
||||||
Qinv: crtCoeff,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if oth != nil {
|
|
||||||
// Should be an array of more JSON objects.
|
|
||||||
otherPrimesInfo, ok := oth.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("JWK RSA Private Key: Invalid other primes info: must be an array")
|
|
||||||
}
|
|
||||||
numOtherPrimeFactors := len(otherPrimesInfo)
|
|
||||||
if numOtherPrimeFactors == 0 {
|
|
||||||
return nil, errors.New("JWK RSA Privake Key: Invalid other primes info: must be absent or non-empty")
|
|
||||||
}
|
|
||||||
otherPrimeFactors := make([]*big.Int, numOtherPrimeFactors)
|
|
||||||
productOfPrimes := new(big.Int).Mul(firstPrimeFactor, secondPrimeFactor)
|
|
||||||
crtValues := make([]rsa.CRTValue, numOtherPrimeFactors)
|
|
||||||
|
|
||||||
for i, val := range otherPrimesInfo {
|
|
||||||
otherPrimeinfo, ok := val.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("JWK RSA Private Key: Invalid other prime info: must be a JSON object")
|
|
||||||
}
|
|
||||||
|
|
||||||
otherPrimeFactor, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
otherFactorCRT, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
otherCrtCoeff, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "t")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
crtValue := crtValues[i]
|
|
||||||
crtValue.Exp = otherFactorCRT
|
|
||||||
crtValue.Coeff = otherCrtCoeff
|
|
||||||
crtValue.R = productOfPrimes
|
|
||||||
otherPrimeFactors[i] = otherPrimeFactor
|
|
||||||
productOfPrimes = new(big.Int).Mul(productOfPrimes, otherPrimeFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey.Primes = append(privateKey.Primes, otherPrimeFactors...)
|
|
||||||
privateKey.Precomputed.CRTValues = crtValues
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &rsaPrivateKey{
|
|
||||||
rsaPublicKey: *publicKey,
|
|
||||||
PrivateKey: privateKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Key Generation Functions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func generateRSAPrivateKey(bits int) (k *rsaPrivateKey, err error) {
|
|
||||||
k = new(rsaPrivateKey)
|
|
||||||
k.PrivateKey, err = rsa.GenerateKey(rand.Reader, bits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k.rsaPublicKey.PublicKey = &k.PrivateKey.PublicKey
|
|
||||||
k.extended = make(map[string]interface{})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA2048PrivateKey generates a key pair using 2048-bit RSA.
|
|
||||||
func GenerateRSA2048PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 2048-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA3072PrivateKey generates a key pair using 3072-bit RSA.
|
|
||||||
func GenerateRSA3072PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(3072)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 3072-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA4096PrivateKey generates a key pair using 4096-bit RSA.
|
|
||||||
func GenerateRSA4096PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(4096)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 4096-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
|
@ -1,363 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base32"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadOrCreateTrustKey will load a PrivateKey from the specified path
|
|
||||||
func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) {
|
|
||||||
if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
trustKey, err := LoadKeyFile(trustKeyPath)
|
|
||||||
if err == ErrKeyFileDoesNotExist {
|
|
||||||
trustKey, err = GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := SaveKey(trustKeyPath, trustKey); err != nil {
|
|
||||||
return nil, fmt.Errorf("error saving key file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, file := filepath.Split(trustKeyPath)
|
|
||||||
if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {
|
|
||||||
return nil, fmt.Errorf("error saving public key file: %s", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("error loading key file: %s", err)
|
|
||||||
}
|
|
||||||
return trustKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity
|
|
||||||
// based authentication from the specified dockerUrl, the rootConfigPath and
|
|
||||||
// the server name to which it is connecting.
|
|
||||||
// If trustUnknownHosts is true it will automatically add the host to the
|
|
||||||
// known-hosts.json in rootConfigPath.
|
|
||||||
func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
trustKeyPath := filepath.Join(rootConfigPath, "key.json")
|
|
||||||
knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json")
|
|
||||||
|
|
||||||
u, err := url.Parse(dockerUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse machine url")
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "unix" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := u.Host
|
|
||||||
proto := "tcp"
|
|
||||||
|
|
||||||
trustKey, err := LoadOrCreateTrustKey(trustKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load trust key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
knownHosts, err := LoadKeySetFile(knownHostsPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load trusted hosts file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedHosts, err := FilterByHosts(knownHosts, addr, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error filtering hosts: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool, err := GenerateCACertPool(trustKey, allowedHosts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Could not create CA pool: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.ServerName = serverName
|
|
||||||
tlsConfig.RootCAs = certPool
|
|
||||||
|
|
||||||
x509Cert, err := GenerateSelfSignedClientCert(trustKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("certificate generation error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{{
|
|
||||||
Certificate: [][]byte{x509Cert.Raw},
|
|
||||||
PrivateKey: trustKey.CryptoPrivateKey(),
|
|
||||||
Leaf: x509Cert,
|
|
||||||
}}
|
|
||||||
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
|
|
||||||
testConn, err := tls.Dial(proto, addr, tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("tls Handshake error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
CurrentTime: time.Now(),
|
|
||||||
DNSName: tlsConfig.ServerName,
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
|
|
||||||
certs := testConn.ConnectionState().PeerCertificates
|
|
||||||
for i, cert := range certs {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := certs[0].Verify(opts); err != nil {
|
|
||||||
if _, ok := err.(x509.UnknownAuthorityError); ok {
|
|
||||||
if trustUnknownHosts {
|
|
||||||
pubKey, err := FromCryptoPublicKey(certs[0].PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error extracting public key from cert: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey.AddExtendedField("hosts", []string{addr})
|
|
||||||
|
|
||||||
if err := AddKeySetFile(knownHostsPath, pubKey); err != nil {
|
|
||||||
return nil, fmt.Errorf("error adding machine to known hosts: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to connect. unknown host: %s", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testConn.Close()
|
|
||||||
tlsConfig.InsecureSkipVerify = false
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// joseBase64UrlEncode encodes the given data using the standard base64 url
|
|
||||||
// encoding format but with all trailing '=' characters ommitted in accordance
|
|
||||||
// with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlEncode(b []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
|
||||||
}
|
|
||||||
|
|
||||||
// joseBase64UrlDecode decodes the given string using the standard base64 url
|
|
||||||
// decoder but first adds the appropriate number of trailing '=' characters in
|
|
||||||
// accordance with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlDecode(s string) ([]byte, error) {
|
|
||||||
s = strings.Replace(s, "\n", "", -1)
|
|
||||||
s = strings.Replace(s, " ", "", -1)
|
|
||||||
switch len(s) % 4 {
|
|
||||||
case 0:
|
|
||||||
case 2:
|
|
||||||
s += "=="
|
|
||||||
case 3:
|
|
||||||
s += "="
|
|
||||||
default:
|
|
||||||
return nil, errors.New("illegal base64url string")
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyIDEncode(b []byte) string {
|
|
||||||
s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")
|
|
||||||
var buf bytes.Buffer
|
|
||||||
var i int
|
|
||||||
for i = 0; i < len(s)/4-1; i++ {
|
|
||||||
start := i * 4
|
|
||||||
end := start + 4
|
|
||||||
buf.WriteString(s[start:end] + ":")
|
|
||||||
}
|
|
||||||
buf.WriteString(s[i*4:])
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyIDFromCryptoKey(pubKey PublicKey) string {
|
|
||||||
// Generate and return a 'libtrust' fingerprint of the public key.
|
|
||||||
// For an RSA key this should be:
|
|
||||||
// SHA256(DER encoded ASN1)
|
|
||||||
// Then truncated to 240 bits and encoded into 12 base32 groups like so:
|
|
||||||
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
hasher := crypto.SHA256.New()
|
|
||||||
hasher.Write(derBytes)
|
|
||||||
return keyIDEncode(hasher.Sum(nil)[:30])
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringFromMap(m map[string]interface{}, key string) (string, error) {
|
|
||||||
val, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%q value not specified", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
str, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%q value must be a string", key)
|
|
||||||
}
|
|
||||||
delete(m, key)
|
|
||||||
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) {
|
|
||||||
curveByteLen := (curve.Params().BitSize + 7) >> 3
|
|
||||||
|
|
||||||
cBytes, err := joseBase64UrlDecode(cB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
cByteLength := len(cBytes)
|
|
||||||
if cByteLength != curveByteLen {
|
|
||||||
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen)
|
|
||||||
}
|
|
||||||
return new(big.Int).SetBytes(cBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) {
|
|
||||||
dBytes, err := joseBase64UrlDecode(dB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
|
||||||
// octets (where n is the order of the curve). This is because the private
|
|
||||||
// key d must be in the interval [1, n-1] so the bitlength of d should be
|
|
||||||
// no larger than the bitlength of n-1. The easiest way to find the octet
|
|
||||||
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
|
|
||||||
// bit sequence right by 3, which is essentially dividing by 8 and adding
|
|
||||||
// 1 if there is any remainder. Thus, the private key value d should be
|
|
||||||
// output to (bitlength(n-1)+7)>>3 octets.
|
|
||||||
n := curve.Params().N
|
|
||||||
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
|
|
||||||
dByteLength := len(dBytes)
|
|
||||||
|
|
||||||
if dByteLength != octetLength {
|
|
||||||
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(dBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAModulusParam(nB64Url string) (*big.Int, error) {
|
|
||||||
nBytes, err := joseBase64UrlDecode(nB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(nBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeRSAPublicExponentParam(e int) []byte {
|
|
||||||
// We MUST use the minimum number of octets to represent E.
|
|
||||||
// E is supposed to be 65537 for performance and security reasons
|
|
||||||
// and is what golang's rsa package generates, but it might be
|
|
||||||
// different if imported from some other generator.
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(buf, uint32(e))
|
|
||||||
var i int
|
|
||||||
for i = 0; i < 8; i++ {
|
|
||||||
if buf[i] != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAPublicExponentParam(eB64Url string) (int, error) {
|
|
||||||
eBytes, err := joseBase64UrlDecode(eB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
// Only the minimum number of bytes were used to represent E, but
|
|
||||||
// binary.BigEndian.Uint32 expects at least 4 bytes, so we need
|
|
||||||
// to add zero padding if necassary.
|
|
||||||
byteLen := len(eBytes)
|
|
||||||
buf := make([]byte, 4-byteLen, 4)
|
|
||||||
eBytes = append(buf, eBytes...)
|
|
||||||
|
|
||||||
return int(binary.BigEndian.Uint32(eBytes)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) {
|
|
||||||
b64Url, err := stringFromMap(m, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
paramBytes, err := joseBase64UrlDecode(b64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invaled base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(paramBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) {
|
|
||||||
pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}}
|
|
||||||
for k, v := range headers {
|
|
||||||
switch val := v.(type) {
|
|
||||||
case string:
|
|
||||||
pemBlock.Headers[k] = val
|
|
||||||
case []string:
|
|
||||||
if k == "hosts" {
|
|
||||||
pemBlock.Headers[k] = strings.Join(val, ",")
|
|
||||||
} else {
|
|
||||||
// Return error, non-encodable type
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Return error, non-encodable type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pemBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) {
|
|
||||||
cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := FromCryptoPublicKey(cryptoPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addPEMHeadersToKey(pemBlock, pubKey)
|
|
||||||
|
|
||||||
return pubKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) {
|
|
||||||
for key, value := range pemBlock.Headers {
|
|
||||||
var safeVal interface{}
|
|
||||||
if key == "hosts" {
|
|
||||||
safeVal = strings.Split(value, ",")
|
|
||||||
} else {
|
|
||||||
safeVal = value
|
|
||||||
}
|
|
||||||
pubKey.AddExtendedField(key, safeVal)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue